Skip to content

How to use HttpClientFactory in dotnet

It is a common requirement when building apps and microservices using the dotnet framework to need to interact with external REST based API's, to retrieve or update data. I have previously posted Making API calls with HttpClientFactory in Console applications, which discusses the same issues but coming at it from a Console Application perspective, but ultimately addresses similar concerns and issues one can experience.

I have seen several posts on many other blog posts, illustrating how to use the HttpClientFactory, and the vast majority have been completely incorrect and in many ways are guiding people down what appears to be very convoluted and error prone paths.

Most of the confusion seems to stem from people trying to correctly Unit Test and even integration test results coming back from the HttpClient I have several issues with their approaches but sadly this is a topic for an entire series of blogs posts, which hopefully one day given the time and resources I'll get round to writing.

In order to understand how to use the HttpClientFactory in dotnet we first need to understand the issue it is primarily used to circumvent, Socket or Port Exhaustion.

What is Socket Exhaustion

Socket Exhaustion is an issue that can arise when an app or service makes use ephemeral port from its machine to connect to a well-known port defined for that application or service. The ultimate problem that arises is when all available ports or sockets have been used up, which is also known as ephemeral port exhaustion .

The primary issue with this issue, is that your Operating System will likely not give you any indication that this happening and it can usually have some very dire consequences. Services my just start failing or experiencing memory leaks etc. For the most part you'll have no idea why!

In my experience, in dotnet apps you may invariably find the culprit to be a class or that makes use of the HttpClient to make an external API call.

There are usually several causes to this type of bug, many come down to incorrect implementation from not understanding what the dotnet provides as mechanism to achieve these types of task.

The first problem that many developers encounter is that, HttpClient class implements the IDisposable interface, which leads many developers to assume that all they need to do is use the using directive when instantiating and making use of the HttpClient and the Garbage Collector will remove all references and instances of the object once it is out of scope.

Unfortunately, this is not the case because if we Dispose the HttpClient we also dispose the HttpClientHandler , which then results in every new request having to create a new HttpClient instance and a new HttpClientHandler too. The first issue here is that reopening connections leads to slow performance because new connections and HttpClientHandlers are pretty expensive when working with HttpClient.

The second issue, and the one that causes most of the problems is, by creating too many connections, leads to socket exhaustion because we're attempting to use too many sockets too quickly, and don’t have any available sockets to create a new connection.

Without naming any organisations, the code below unfortunately somewhat typical of the code you may stumble across in most Enterprise Software Environments.

using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using SomeOrg.Platform.Address.Models.Data;
using SomeOrg.Platform.Address.Models.Settings;
using SomeOrg.Platform.Address.Services.Interfaces;

namespace SomeOrg.Platform.Address.Services
{
    public class AddressProvider : IAddressProvider
    {
        private readonly ServiceSettings _settings;
        private readonly IHttpClientFactory _clientFactory;
        private HttpClient _httpClient;
        private readonly ILogger<AddressProvider> _logger;
        public AddressProvider(IOptions<ServiceSettings> settings, IHttpClientFactory clientFactory, ILogger<AddressProvider>  logger)
        {
            _settings = settings.Value;
            _clientFactory = clientFactory;
            _logger = logger;

            BuildHttpClient();
        }

        private void BuildHttpClient()
        {
            _httpClient = _clientFactory.CreateClient();
        }

        public async Task<SourceData> GetAddress(string postcode, CancellationToken cancellationToken)
        {
            try
            {
                var requestUri = $"{_settings.Endpoint}?data={_settings.Data}" +
                                 $"&CountryISO={_settings.CountryISO}" +
                                 $"&serial={_settings.Serial}" +
                                 $"&password={_settings.Password}" +
                                 $"&task=fastfind" +
                                 $"&fields=simple" +
                                 $"&format=json&lookup={postcode}";

                var response = await _httpClient.GetAsync(requestUri, cancellationToken);

                return response.IsSuccessStatusCode ? JsonConvert.DeserializeObject<SourceData>(await response.Content.ReadAsStringAsync(cancellationToken)) : JsonConvert.DeserializeObject<SourceData>(null);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "{AddressProvider} error", typeof(AddressProvider));
                throw;
            }
        }
    }
}
    

I know many Clean Code Purists will be having kittens evaluating the code, and for good measure. However, I would say that even following the Clean Code principles there is still a lot wrong with the code above, but for the purpose of this article the main focus is actually how the developers involved in this scenario made use of the Dotnet objects which also ultimately lead them down so many wrong paths which ultimately lead to a very poor implementation.

How to use the HttpClientFactory

The first issue here, is that although the developers actually found the right object to use, they unfortunately used in the wrong place entirely!

The developers obviously realised that the HttpClientFactory is used to create and manage new HttpClient instances and the underlying HttpClientHandlers. The primary use case of the HttpClientFactory is to centralise the creation of HttpClients and allow consumers to reuse already existing connections.

Benefits of the HttpClientFactory
  • Central location for naming and configuring logical HttpClients
  • Codify the concept of outgoing middleware via delegating handlers in HttpClient and implementing Polly-based middleware to take advantage of Polly's policies for resiliency.
  • Manage the lifetime of HttpMessageHandler to avoid the mentioned problems/issues that can occur when managing HttpClient lifetimes yourself.

Check out Use IHttpClientFactory to implement resilient HTTP requests

The most logical place to register these clients using Dependency Injection, the name of the class may have provided a hint as to the intention. In fact when creating a Dotnet application the IServiceCollection provides an extension method AddHttpClient which registers the internal DefaultHttpClientFactory class to be used as a Singleton for the interface IHttpClientFactory

Dependency Injection

Principles, Practices, and Patterns

teaches you to use DI to reduce hard-coded dependencies between application components. 

Refactor the Address Provider class

Understanding these concepts always better, with an actual example. So first before we get onto the configuration of the HttpClient lets do a little refactoring of the above AddressProvider class to enable an improved approach.

Our initial refactoring steps is we will aim to declutter the constructor because there is no need to actually inject the HttpClientFactory and the IOptions<ServiceSetting> because we don't need them, we you'll find out we will make use of Dependency Injection.

Warning

If you see classes in your app that seem to require IOptions<T> to be injected in then the chances are that class is in dire need of refactoring, because there are undounbtedly issues and probably point to wider concerns

using Newtonsoft.Json;
using SomeOrg.Platform.Address.Models.Data;
using SomeOrg.Platform.Address.Services.Interfaces;

namespace SomeOrg.Platform.Address.Services;

public class AddressProvider : IAddressProvider
{
    private readonly HttpClient _httpClient;
    public AddressProvider(HttpClient client) => _httpClient = client;

    public async Task<SourceData> GetAddress(string postcode, CancellationToken cancellationToken)
    {
        var requestUri = $"&lookup={postcode}";

        var response =
            await _httpClient.GetAsync(string.Concat(_httpClient.BaseAddress, requestUri), cancellationToken);
        response.EnsureSuccessStatusCode();

        return JsonConvert.DeserializeObject<SourceData>(
            await response.Content.ReadAsStringAsync(cancellationToken));

    }
}

The refactoring we've done here is to simplify the class a little and to implement a pattern which known as Typed Client which is quite simply a HttpClient that's pre-configured for some specific use. This configuration can include specific values such as the base server, HTTP headers or time outs etc. For the purpose of this article we will aim to keep this simple and clear but providing just enough complexity to resemble a real world implementation.

The API that we're making use of essentially requires appending an additional query string parameter with the value of the item we need. In this case it is a UK based postcode. The rest of the code is just basically calling the api then dealing with the response. We trimmed the logic of the class down and removed several unnecessary dependencies and responsibilities.

We can head on to configuring our HttpClient by making use of the HttpClientFactory which is achieved by configuring the DI container. To do this go to the start point of your application i.e. Program.cs or Startup.cs for which version of the dotnet you're using 5,6 or 7.

In this case I'm going to be using Program.cs and make use of the Top-Level function configuration to do my configuration.

We will tidy up a number of aspects of the configuration the first you'll notice is we first remove magic strings and have declared a constant value for the AfdSettings name, which matches our object name. The settings are stored in appsettings.json .

I won't go into detail on how to set and get the values from secrets, primarily because I discuss this in detail in How to manage secrets in dotnet , and also creating a class to enable the serialising theses values that should be fairly common knowledge.

// Configure the AFD Postcode Look up service Typed Client 
builder.Services.Configure<AfdSettings>(builder.Configuration.GetSection(Constants.AfdSettings))
       .AddHttpClient<IAddressDataProvider, AddressDataProvider>()
       .ConfigureHttpClient((provider, client) =>
       {
          var settings = provider.GetRequiredService<IOptions<AfdSettings>>().Value;
          var afdBaseAddress = new UriBuilder(settings.Endpoint);

          var parameters = new AfdParameterBuilder()
             .Create()
             .Data(settings.Data)
             .CountryCode(settings.CountryISO)
             .Serial(settings.Serial)
             .Password(settings.Password)
             .Build();

             afdBaseAddress.Query = parameters;
             client.BaseAddress = afdBaseAddress.Uri;
         });


The crucial element of our code is the AddHttpClient and ConfigureHttpClient this is where we set it up for dependency injection.

We make use of the dotnet UriBuilder to build our BaseAddress, and we created our own simple Builder Pattern to build our Afd API parameters, AfdParameterBuilder. All this builder fundamentally does is tidy up and manage the builder of the long parameter string.

/// <summary>
///     Simple builder implementation to build the URL string required for the AFD API Parameters
/// </summary>
public class AfdParameterBuilder
{
    private StringBuilder? _parameterString;
 
    public AfdParameterBuilder Create(string? baseQuery = default)
    {
        _parameterString = string.IsNullOrEmpty(baseQuery) ? new StringBuilder(baseQuery?.Substring(1, baseQuery.Length)) : new StringBuilder();
        return this;
    }

   
    public AfdParameterBuilder Data(string data)
    {
        ConcatParameter( $"{nameof(data)}={data}");
        return this;
    }

    public AfdParameterBuilder CountryCode(string countryISO)
    {
        ConcatParameter( $"{nameof(countryISO)}={countryISO}");
        return this;
    }

    public AfdParameterBuilder Serial(string serial)
    {
        ConcatParameter($"{nameof(serial)}={serial}");
        return this;
    }

    public AfdParameterBuilder Password(string password)
    {
        ConcatParameter($"{nameof(password)}={password}");
        return this;
    }
    public AfdParameterBuilder Task(string task)
    {
        ConcatParameter($"{nameof(task)}={task}");
        return this;
    }
    
    public AfdParameterBuilder Format(string format)
    {
        ConcatParameter($"{nameof(format)}={format}");
        return this;
    }
    public AfdParameterBuilder Lookup(string lookup)
    {
        ConcatParameter($"{nameof(lookup)}={lookup}");
        return this;
    }
    public AfdParameterBuilder Fields(string fields)
    {
        ConcatParameter($"{nameof(fields)}={fields}");
        return this;
    }
  
    public string Build()
    {
        return _parameterString?.ToString() ?? string.Empty;
    }

    /// <summary>
    /// Check if we are the start of the string to evaluate whether we need to append a an ampersand
    /// to string or not. 
    /// </summary>
    /// <param name="param"></param>
    private void ConcatParameter(string param)
    {
        _parameterString?.Append(_parameterString.Length.Equals(0) ? param : string.Concat("&", param));
    }
}

Thats basically it now we can simply make use of , our AddressProvider class where ever we need it. In this case we'll inject it into a Mediatr handler and use it to query for addresses in a postcode area. This follows a similar pattern defined in the API Template Pack

public class Handler : IRequestHandler<Query, ApiResponse<Response>>
{
    private readonly IMapper _mapper;
    private readonly IAddressDataProvider _provider;

    public Handler(IMapper mapper, IAddressDataProvider provider)
    {
        _mapper = mapper;
        _provider = provider;
    }
    public async Task<ApiResponse<Response>> Handle(Query request, CancellationToken cancellationToken)
    {
        
        var addresses = await _provider.GetByPostCode(request.PostCode, cancellationToken);
        return new SingleResponse<Response>(new Response { Addresses = _mapper.Map<Address[]>(addresses.Item)});
    }
}

Final Refactor of the Address Provider

Lets now shift to the final refactor we can make to the AddressProvider class. We want to clean up the logic totally removing any magic strings and try to make more use of the Utilities actually provided to us by the Dotnet framework to make our lives easier and also to make use of our AfdParameterBuilder class to build our query string we'll use to query the API.

using Common;
using Domain;
using Newtonsoft.Json;

namespace Services;

public class AddressProvider : IAddressDataProvider
{
    private readonly HttpClient _httpClient;

    public AddressProvider(HttpClient client)
    {
        _httpClient = client;
    }

    public async Task<Source?> GetByPostCode(string postcode, CancellationToken cancellationToken)
    {
        var response =
            await _httpClient?.GetAsync(RequestPath(postcode), cancellationToken)!;
        response.EnsureSuccessStatusCode();

        return JsonConvert.DeserializeObject<Source>(
            await response.Content.ReadAsStringAsync(cancellationToken));
    }

    private string RequestPath(string postcode)
    {

        var updateRequested = new UriBuilder(_httpClient?.BaseAddress?.ToString()!)
        {
            Query = new AfdParameterBuilder()
                .Create(_httpClient?.BaseAddress?.Query)
                .Lookup(postcode)
                .Build()
        };

        return updateRequested.Uri.ToString();

    }
}

Conclusion

I realise there is a lot of ground covered in this post and there may be concepts and implementations that I have completely glossed over. However, the primary focus of this post is an attempt to explain how to make use of the HttpClientFactory to implement the Typed Client pattern in dotnet.

Gary Woodfine
Latest posts by Gary Woodfine (see all)
Tags: