Search code examples
c#.netresthttpshttpclientfactory

How to resolve unknown differences between new HttpClient instance and HttpClientFactory instance


Currenty I'm building a solution which is basically an integration between multiple external REST services. Since the solution is having multiple external service, I want to use the .NET ClientFactory to create HttpClients whenever they are wanted. Unfortunately an HttpClient instance created from the ClientFactory always results in an Unauthorized. This is a bit strange since directly creating a new HttpClient results in a success. I red the Microsoft documentation about the HttpClientFactory to determine the differences and possible use cases.

I compared the two different HttpClient instances by extracting the entire objects and did a meld comparison without any differences. Also with postman the call to the service succeeds.

The code which extracts and creates the HttpClient instance (which fails) looks like this:

Program.cs

 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

 services.AddHttpClient("ClientName", client =>
 {
     client.BaseAddress = new Uri("https://my.domain.com");
     var credentials = Convert.ToBase64String(Encoding.UTF8.GetBytes("username:password"));
     client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials);
     client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
 });

TicketRepository.cs

public class TicketsRepository(
    IHttpClientFactory _httpClientFactory) : ITicketsRepository
{
    private readonly HttpClient _httpClient = _httpClientFactory.CreateClient("ClientName");

    public async Task<Tickets> GetTicketByNumber(
        int ticket,
        CancellationToken cancellationToken)
    {
        HttpResponseMessage response = await _httpClient.GetAsync(
            $"{Constants.TicketsUrlTemplate}/{ticket}",
            cancellationToken);

        if (!response.IsSuccessStatusCode)
        {
            _logger.LogError("Could not successfully retrieve data. Invalid response data is: {response}", response.ToString());

            throw new HttpRequestException($"Server did not respond with an http response status 200: {response}");
        }

        return new Tickets();
    }
}

The code which creates an HttpClient instance on every method call and succeeds extracting data looks like this:

public async Task GetTicketByNumber(
    int ticket,
    CancellationToken cancellationToken)
{
    using (HttpClient client = new HttpClient())
    {

        client.BaseAddress = new Uri("https://my.domain.com");
        var credentials = Convert.ToBase64String(Encoding.UTF8.GetBytes("username:password"));
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials);
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        try
        {
            HttpResponseMessage response = await client.GetAsync($"/urlTemplate/{ticket}/", cancellationToken);

            string responseContent = await response.Content.ReadAsStringAsync();
            Console.WriteLine("Response Content: " + responseContent);
        }
        catch (HttpRequestException e)
        {
            Console.WriteLine("Request error: " + e.Message);
        }
    }
}

I did a blank comparison with the Response Objects and HttpClient Objects with identical headers, version, host etc... I must have overlooked something stupid since the ClientFactory works for other Rest Services configured on startup. It is hard to monitor stuf on the server side, since I do not have any physical acccess towards that REST Service, nor do I have access to the source of it. Hopefully someone can point me in the right direction.


Solution

  • As my investigation continues over the weekend (problem accured before) I have to admit that I should have looked further. As I compared my HttpResponseMessages between the newly created HttpClient and the one from the ClientFactory I notices that the returned Header WwwAuthenticate from the succeeding call was empty and the one from the HttpClientFactory had the following header value Basic realm="api". Therefor I searched some more and found out this solution, Authentication per request using HttpClientFactory which pointed me towards How to use HttpClientHandler with IHttpClientFactory. It turns out that the following additional configuration on the ClientFactory worked out successfully since the returned WwwAuthenticate header must be empty:

    ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
    
    services.AddHttpClient("Server", client =>
    {
        client.BaseAddress = new Uri("https://my.domain.com");
        var credentials = Convert.ToBase64String(Encoding.UTF8.GetBytes("username:password"));
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials);
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    }).ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new HttpClientHandler()
        {
            UseDefaultCredentials = true,
            Credentials = new NetworkCredential("", ""),
        };
    });
    

    Although this works, I also must admit that I need to investigate more about basic auth on such a server and still the request must difer from the directly created HttpClient instance, since I want to know more about this. Soo, unfortunately for me not really a proper answer yet, but for the POC it's a working solution. Any comments on a more technical dept for using the additional ConfigurePrimaryHttpMessageHandler including the default credentials appreciated.