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
.
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 HttpClient
should 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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 theHttpClient
cancels any outgoing request and ensures that theHttpClient
cannot be used after disposing. The factory itself tracks, caches, and disposes resources used by theHttpClient
, 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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
}).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
.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); // 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; } |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
}).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.
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
LikeLiked by 1 person
Jim, you can use the method ConfigureHttpMessageHandlerBuilder to configure an HttpClientHandler. I will update the sample to demonstrate this.
Thanks,
Christian
LikeLike