Introduction
the very first time Microsoft launched the HttpClient in .NET Framework 4.5, it became the most popular way to consume a Web API HTTP request, such as Get, Put, Post, and Delete in your .NET server-side code.
However, it has some serious issues, for example, disposing of the object like HttpClient object doesn’t dispose of the socket as soon as it is closed.
There are also too many instances open so that it affecting the performance and private HttpClientor shared HttpClient instance not respecting the DNS Time.
When Microsoft released dotnet core 2.1, it introduced HttpClientFactory that solves all these problems.
Basically, it provides a single place (central place) for configuration and consumption HTTP Verbs Client in your application IHttpClientFactory offers the following benefits,
- Naming and configuring HttpClient instances.
- Build an outgoing request middleware to manage cross-cutting concerns around HTTP requests.
- Integrates with Polly for transient fault handling.
- Avoid common DNS problems by managing HttpClient lifetimes.
There are the following ways to use IHttpClientFactory.
- Direct HttpClientFactory
- Named Clients
- Typed Clients
We will see an example one by one for all 3 types...
Direct HttpClientFactory
In dotnet core, we have Startup.cs class, and inside this class, we have the ConfigureService method. In this method we use middleware, where we inject some inbuilt/custom pipeline.
So for HttpClientFactory we need to register HttpClient like below:
- services.AddHttpClient();
Now the question is how to use this in our API controller.
So here is the example of Direct HttpClientFactory use in controller:
- public class HttpClientFactoryController: Controller {
- private readonly IHttpClientFactory _httpClientFactory;
- public HttpClientFactoryController(IHttpClientFactory httpClientFactory) {
- _httpClientFactory = httpClientFactory;
- }
- [HttpGet]
- public async Task < ActionResult > Get() {
- var client = _httpClientFactory.CreateClient();
- client.BaseAddress = new Uri("http://api.google.com");
- string result = await client.GetStringAsync("/");
- return Ok(result);
- }
- }
Here in this example we have pass IHttpClientFactory is a dependency injection and directly use _httpClientFactory.CreateClient();
This example is better in this situation when we need to make a quick request from a single place in the code
Named Clients
Just above I have explained how to register the middleware in startup.cs class in configureService method for HttpClient same we can use for Named Clients as well, but this is useful when we need to make multiple requests from multiple locations.
We can also do some more configuration while registering, like this:
- services.AddHttpClient("g", c =>
- {
- c.BaseAddress = new Uri("https://api.google.com/");
- c.DefaultRequestHeaders.Add("Accept", "application/json");
- });
Here in this configuration, we use two parameter names and an Action delegate taking a HttpClient
We can use the named client in the API controller in this way:
- public class NamedClientsController: Controller {
- private readonly IHttpClientFactory _httpClientFactory;
- public NamedClientsController(IHttpClientFactory httpClientFactory) {
- _httpClientFactory = httpClientFactory;
- }
- [HttpGet]
- public async Task < ActionResult > Get() {
- var client = _httpClientFactory.CreateClient("g");
- string result = await client.GetStringAsync("/");
- return Ok(result);
- }
- }
Note
"g" indicates names client that I use in during registration and also call from the API action method
Typed Clients
A types client provides the same capabilities as named clients but without the need to use strings as keys in configuration. Due to this it also provide IntelliSense and compiler help when consuming clients. It provides a single location to configure and interact with a particular httpclient
It works with dependency injection and can be injected where required in the application.
A typed client accepts an HttpClient parameter in its constructor,
We can see here by an example that I have defined custom class for httpclient:
- public class TypedCustomClient {
- public HttpClient Client {
- get;
- set;
- }
- public TypedCustomClient(HttpClient httpClient) {
- httpClient.BaseAddress = new Uri("https://api.google.com/");
- httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
- httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
- Client = httpClient;
- }
- }
Now we can register this as a typed client using in this way in startup.cs clss under configureService method.
- services.AddHttpClient<TypedCustomClient>();
Now this time we see how we can use it in API controller,
- public class TypedClientController: Controller {
- private readonly TypedCustomClient _typedCustomClient;
- public TypedClientController(TypedCustomClient typedCustomClient) {
- _typedCustomClient = typedCustomClient;
- }
- [HttpGet]
- public async Task < ActionResult > Get() {
- string result = await _typedCustomClient.client.GetStringAsync("/");
- return Ok(result);
- }
- }
Now we have learned all three types to use.
But here is one better way to use typeclient using an interface.
We will create an interface and encapsulate all the logic here, that also helps in writing UTC as well
Here is an example:
- public interface ICustomClient
- {
- Task<string> GetData();
- }
Now inherit this interface in custom class
- public class TypedCustomClient: ICustomClient {
- public HttpClient Client {
- get;
- set;
- }
- public TypedCustomClient(HttpClient httpClient) {
- httpClient.BaseAddress = new Uri("https://api.google.com/");
- httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
- httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
- Client = httpClient;
- }
- }
Register this in startup class:
- services.AddHttpClient<ICustomClient, TypedCustomClient>();
- public class CustomController: Controller {
- private readonly ICustomClient _iCustomClient;
- public ValuesController(ICustomClient iCustomClient) {
- _iCustomClient = iCustomClient;
- }
- [HttpGet]
- public async Task < ActionResult > Get() {
- string result = await iCustomClient.GetData();
- return Ok(result);
- }
- }
Here we are using the interface for the same, so it would be good to go for repository mock.
Thank you for taking your valuable time to read the full article!