HTTP Client Factory with .NET Core 2.1

The HttpClient class can be easily used in a way how it’s not meant to be. While this class is disposable, using it with the using statement is often not the best choice. Disposing the HttpClient, the underlying socket is not immediately released. The HttpClient class is designed to be reused for multiple requests. Having the need for different base addresses, different HTTP headers, and for error recovery, it helps having multiple instances.
To make the management of HttpClient instances easier, .NET Core 2.1 offers a new HTTP Client Factory – which creates, caches, and disposes HttpClient instances. This article demonstrates different ways on using the IHttpCientFactory.

Networking

Why it’s not a good idea to use the HttpClient class with the using statement is clearly demonstrated with this blog article from Simon Timms.

Using the IHttpClientFactory

To get rid of self-creating of HttpClient instances, after adding the NuGet package Microsoft.Extensions.Http, the extension method AddHttpClient for IServiceCollection is available. This extension method registers the DefaultHttpClientFactory to be used as a singleton for the interface IHttpClientFactory. It defines a transient configuration for the HttpMessageHandlerBuilder. This message handler is used by the HttpClient returned from the factory.


public FactorySample() => AppServices = ConfigureServices();
private ServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddLogging(builder =>
{
builder.AddFilter((category, level) => true);
builder.AddConsole(options => options.IncludeScopes = true);
});
services.AddHttpClient("cni", client =>
{
client.BaseAddress = new Uri("https://www.cninnovation.com");
});
services.AddTransient<ControllerUsingClientFactory>();
return services.BuildServiceProvider();
}
public IServiceProvider AppServices { get; private set; }

You can call AddHttpClient multiple times – with different configurations. The sample code defines the name cni for HttpClients using the base address https://www.cninnovation.com. You can also add HTTP headers that should be used with all the HttpClient instances from this group.

With the class where the HttpClientshould be used, the IHttpClientFactory is injected in the constructor. From there, the HttpClient is retrieved by invoking the CreateClient method of this interface. Depending on the different HttpClient instances needed, the HttpClient can be retrieved in the constructor of the class, or in the method where the HttpClient is used. You don’t need to deal with the HTTP headers needed, or the base address – just the name of the registration needs to be used invoking the CreateClient method to get the HttpClient with the correct configuration.


class ControllerUsingClientFactory
{
private readonly IHttpClientFactory _httpClientFactory;
public ControllerUsingClientFactory(IHttpClientFactory clientFactory)
{
_httpClientFactory = clientFactory ?? throw new ArgumentNullException(nameof(clientFactory));
}
public async Task CallServerAsync()
{
HttpClient client = _httpClientFactory.CreateClient("cni");
var response = await client.GetAsync("/downloads/Racers.xml");
response.EnsureSuccessStatusCode();
string content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}

Disposing of the HttpClient can be done, but is not required. Disposing of the HttpClient cancels any outgoing request and ensures that the HttpClient cannot be used after disposing. The factory itself tracks, caches, and disposes resources used by the HttpClient, that’s why disposing is not required.

Using a Typed Client

Another way on using the factory is by using a typed client. A typed client makes use of a HttpClient with a specific configuration, and the HttpClient is injected in the constructor. With the following code snippet, in the TypedClient class, the HttpClient is injected in the constructor, and used like before in the CallServerAsync method.


class TypedClient
{
private readonly HttpClient _httpClient;
public TypedClient(HttpClient httpClient)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
public async Task CallServerAsync()
{
var response = await _httpClient.GetAsync("/downloads/Racers.xml");
response.EnsureSuccessStatusCode();
string content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}

With the dependency injection container configuration, now the method AddTypedClient is invoked passing the TypedClient class. This is an extension method for the IHttpClientBuilder interface, and thus can be used with the return value of the AddHttpClient method. This way, the configuration of the HttpClient maps to the typed client.


public TypedClientSample() => AppServices = ConfigureServices();
private ServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddLogging(builder =>
{
builder.AddFilter((category, level) => true);
builder.AddConsole(options => options.IncludeScopes = true);
});
services.AddHttpClient("cni", client =>
{
client.BaseAddress = new Uri("https://www.cninnovation.com&quot;);
}).AddTypedClient<TypedClient>();
return services.BuildServiceProvider();
}
public IServiceProvider AppServices { get; private set; }

Using Polly

With this factory, The Polly Project can be used to handle faults. The NuGet package Microsoft.Extensions.Http.Polly offers extension methods to use Polly with the factory.
Using the IHttpClientBuilder returned from the method AddHttpClient, policies can be defined. Invoking the method AddPolicyHandler passing the Policy.TimoutAsync policy, the timout can be specified used by the HttpClient.
The method AddTransientHttpErrorPolicy deals with transient errors – network failures and HTTP 5xx and HTTP 408 errors. The code sample makes use of the wait and retry policy and passes three different timespans – 1, 5, and 10 seconds. If a transient error occurs, a retry is done after 1 second. If the error still happens after 1 second, retries happen after 5 and 10 seconds. If the error still happens after 10 seconds, an exception is thrown that is dealt with when using the HttpClient.


public PolicySample() => AppServices = ConfigureServices();
private ServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddLogging(builder =>
{
builder.AddFilter((category, level) => true);
builder.AddConsole(options => options.IncludeScopes = true);
});
services.AddHttpClient("cni", client =>
{
client.BaseAddress = new Uri("https://www.cninnovation1.com&quot;); // not found server
})
//.AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10)))
.AddTransientHttpErrorPolicy(policy => policy.WaitAndRetryAsync(new [] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10) })) // network failures, HTTP 5xx, HTTP 408
.AddTypedClient<PolicyClient>();
return services.BuildServiceProvider();
}
public IServiceProvider AppServices { get; private set; }

view raw

PolicySample.cs

hosted with ❤ by GitHub

The Polly Project offers rich policies to handle errors.

Using a HttpClientHandler

The HTTP Client Factory offers a lot ways for customization. One way is to add a HttpClientHandler that you are used to pass to the constructor of the HttpClient with the HttpMessageHandler constructor.

To define to use a HttpClientHandler, you just need to invoke the method ConfigureHttpMessageHandlerBuilder, and return a HttpClientHandler from the implemention of the delegate parameter. Here, you can configure the HttpClientHandler, e.g. with automatic decompression:


private ServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddLogging(builder =>
{
builder.AddFilter((category, level) => true);
builder.AddConsole(options => options.IncludeScopes = true);
});
services.AddHttpClient("cni", client =>
{
client.BaseAddress = new Uri("https://www.cninnovation.com&quot;);
}).ConfigureHttpMessageHandlerBuilder(config => new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.GZip
}).AddTypedClient<Client>();
return services.BuildServiceProvider();
}

Summary

The HTTP Client Factory fixes issues on using the HttpClient and make it easy to use it with different configurations needed – including policies to specify retry logic on errors. Using the HttpClient, it just needs to be injected in the classes where you need this type. Microsoft.Extensions.Http gives you extension methods to integrate with the .NET Core dependency injection container, and Microsoft.Extensions.Http.Polly offers an integration to the Polly Project.

The complete Source code is available at Github.

More information on Network programming with .NET Core in my book Professional C# 7 and .NET Core 2.0 and with my trainings.

Enjoy coding!
Christian

If you found this information valuable and want to return me a favor, then buy me a coffee.

Buy Me A Coffee

10 thoughts on “HTTP Client Factory with .NET Core 2.1

  1. Thanks for the article Christian.

    I want to use the HttpClientFactory but I also want to use the HttpClientHandler to utilize the AutomaticDecompression.

    I am struggling because the .AddHttpMessageHandler takes a DelegatingHandler not a HttpClientHandler.

    Any ideas how to use both the factory and be HttpClientHandler?

    Thanks,
    Jim

    Liked by 1 person

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.