With .NET Core 2.1 the HttpClientFactory is introduced. The HttpClientFactory is a factory class which helps with managing HttpClient instances. Managing your own HttpClient correctly was not so easy. The HttpClientFactory gives you a number of options for easy management of your HttpClient instances. In this post I’ll explain how to use the HttpClientFactory in your ASP.NET Core application.
HttpClient
The HttpClient enables you to send HTTP requests and receive HTTP responses. The HttpClient is intended to be instantiated once for each uri and reused throughout the life of the application. It implements IDisposable, which seduces many developers to put it in an using block.
“Although HttpClient does indirectly implement the IDisposable interface, the standard usage of HttpClient is not to dispose of it after every request. The HttpClient object is intended to live for as long as your application needs to make HTTP requests. Having an object exist across multiple requests enables a place for setting DefaultRequestHeaders and prevents you from having to re-specify things like CredentialCache and CookieContainer on every request as was necessary with HttpWebRequest.” — From the book ‘Designing Evolvable Web APIs with ASP.NET’
HttpClient is probably the only IDisposible that should not be put into an using block. When creating unneeded HttpClient object, it can lead to a SocketException caused by open TCP/IP connections. When disposed is called, the connection will stay open for a 240 second to correctly handle the network traffic. A good explanation can be found here: You’re using HttpClient wrong and it is destabilizing your software.
However, when you try to solve it with a static member, a new problem will be introduced. It is not possible to do blue-green deployments caused by the behavior of the HttpClient. It will only renew its DNS when the connection is lost. Read Singleton HttpClient? Beware of this serious behaviour and how to fix it for more information on this.
Then in ASP.NET Core 2.1 the HttpClientFactory is introduced that manages the life cycle of the HttpClient instances. This will make developing good software so much easier. All life cycle management is done by the factory class and it also adds a very nice way to configure your HttpClients in your Startup. You are able to choose to use create an empty one with the factory class, Named Clients or Typed Clients.
Create a HttpClient
The simple way to get a HttpClient object is to just create one with the HttpClientFactory. The first thing to do is add it to your services configuration in StartUp: services.AddHttpClient();
. Next, you can use dependency injection to inject the HttpClientFactory into your class. Call the CreateClient method on the factory to create a new HttpClient.
public class MyApiWrapper { private readonly IHttpClientFactory _httpClientFactory; public MyApiWrapper(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } public Task PingResult() { var client = _httpClientFactory.CreateClient(); client.BaseAddress = new Uri("https://mycustomapiuri/"); client.DefaultRequestHeaders.Add("Accept", "application/json"); return client.GetStringAsync("/ping"); } }
As you can see in the code, the HttpClient has no configuration yet. When you configure the object, it will create a HttpClientHandler under the hood. This object is pooled within the HttpClientFactory.
Named Clients
The second option to get a HttpClient with the HttpClientFactory is by using Named Clients. The HttpClientFactory is injected into your classes like before. By passing a name into the CreateClient method, you can get the Named client. The Named clients are pre-configured in the startup method:
public void ConfigureServices(IServiceCollection services) { services.AddHttpClient("MyCustomAPI", client => { client.BaseAddress = new Uri("https://mycustomapiuri/"); client.DefaultRequestHeaders.Add("Accept", "application/json"); }); (...) }
Then the HttpClient is accessable by its name ‘MyCustomAPI’:
public class MyApiWrapper { private readonly IHttpClientFactory _httpClientFactory; public MyApiWrapper(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } public Task PingResult() { var client = _httpClientFactory.CreateClient("MyCustomAPI"); return client.GetStringAsync("/ping"); } }
Named clients give you more control over how the clients you are using in your program are configured. This makes your life already a lot easier.
Typed Clients
Typed Clients are even better as the Named Clients. They are strongly typed and do not need the HttpClientFactory to be injected. The Type Clients can be injected directly into your classes. In startup you can configure them like:
public void ConfigureServices(IServiceCollection services) { services.AddHttpClient(client => { client.BaseAddress = new Uri("https://mycustomapiuri/"); client.DefaultRequestHeaders.Add("Accept", "application/json"); }); (...) }
The HttpClient is configured when the in ConfigureServices and then used in a implementation like:
public class MyCustomClient { private readonly HttpClient _httpClient; public MyApiWrapper(HttpClient httpClient) { _httpClient = httpClient; } public Task PingResult() { return _httpClient.GetStringAsync("/ping"); } }
The MyCustomClient implementation encapsulates the HttpClient by exposing only the functional methods. This hides all implementation details from the actual user of the class.
public class MyApiWrapper { private readonly MyCustomClient _customClient; public MyApiWrapper(MyCustomClient customClient) { _customClient = customClient; } public Task PingResult() { return customClient.PingResult(); } }
Related posts
Background processing
Schedule services
Headless services
Using scoped services
Finally
By the introduction of the HttpClientFactory, the usage of the HttpClient is a lot easier. Beside what I have shown in this post, is very easy to add extra functionality to Named or Typed client, for example a retry policy or circuit breaker. Probably nice content for a later post.
Hi Peter,
Nice article, neatly explaining the differences.
Struggling to understand, in a console app, how I ultimately access/use the MyApiWrapper?
I’m missing something obvious I know 😦
Thanks
J.
LikeLike