DotNet HttpClient Factory
Posted on Thu 09 December 2021 in dotnet
Recently at my current contract, I came accross an interesting problem, we were using typed http clients conceptually but not effectively or correctly. There are a few API's which at certain product launches can come under quite heavy load and unfortunately had both performance problems and could have exceptions thrown. The problem is in the use of the HttpClient class, it has an issue where even if you wrap its usage in a using statement, it won't release the sockets right away. The solution to this problem is to use IHttpClientFactory.
The design of my clients networking classes is hierarchical - an ApiClient class,then whatever the name of the consuming application is, lets make one up and call our application Consumer, so the class would be ConsumerApiClient which extends the partial ApiClient.
In code it looks something like this
public partial class ApiClient() : IApiClient
{
public ApiClient(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task Send()
{}
}
Then the implementing class would look something like...
public class ConsumerApiClient() : ApiClient, IConsumerApiClient
{
public ConsumerApiClient(HttpClient httpClient, AnotherClass className)
: base (httpClient)
{}
public async Task<Type> MyMethod(Params params)
=> MethodProvidedByApiCient(settings.url, params);
}
The pattern used is designed to provide small client libraries with routes, DTO's and methods to obtain bearer tokens for authenticated access in a fashion that the developer who is interacting with the API is doing so in a controlled manner. Luckily this exact setup is pretty much ready to go for (in my opinion) the cleanest method of implementing the IHttpClientFactory pattern.
The ConsumerApiClient would be instantiated using DI, with a scoped lifetime I think it was but contained down at that ApiClient class, it would new up an instance of HttpClient to make whatever requests it needed.
Now when the ConsumerApiClient was underload, my client found that they often encountered the afore mentioned socket exhaustion exceptions. Not great when you've got a new product launch.
Now there's a really simple trick with this setup to really enhance the performance of the httpClient calls, instead of setting up the ConsumerApiClient in the DI with a scoped lifetime like ...
services.AddScoped<IConsumerApiClient, ConsumerApiClient>()
we simply do
services.AddHttpClient<IConsumerApiClient, ConsumerApiClient>()
Have the constructor require an instance of HttpClient (which the DI will provide) then pass it all the way down to the base client. What this does is move the responsibility of the lifetime of the httpclient instance to IHttpClientFactory, which is handled by the dotnet runtime instead.
The end result is performance improved and socket exhaustion errors stopped!