In the previous article, we used the HttpClient, and in this article, we used the IHttpClientFactory.
Here is the link to the previous article to verify the use of the HttpClient class.
Introduction
You can use an IHttpClientFactory to construct the HttpClient instance rather than controlling the HttpClient lifespan manually.
Send your HTTP requests by calling the CreateClient method and utilizing the HttpClient object that is returned.
Why is this a superior strategy?
The real message handler, HttpMessageHandler, is the most costly component of the HttpClient. There is a reusable internal HTTP connection pool available to every HttpMessageHandler.
When a new instance of the HttpClient is created, the IHttpClientFactory will reuse the HttpMessageHandler that is cached.
It's crucial to remember that HttpClient instances produced by IHttpClientFactory are intended to be temporary.
You need to install the below package in your application.
Microsoft.Extensions.HTTP
Once the installation is done, we need to register those dependencies in our Program.cs file.
builder.Services.AddHttpClient();
Here is the code for executing the request.
var client = _factory.CreateClient();
client.BaseAddress = new Uri("https://localhost:7061");
var employee = await client.GetFromJsonAsync<Employee>($"/api/Employee/{id}");
return employee ?? new Employee();
HttpClient registers with the name
The majority of problems with manually building an HttpClient can be resolved by using IHttpClientFactory. Every time we get a new HttpClient from the CreateClient method, we must still set up the default request parameters.
By invoking the AddHttpClient function and providing the desired name, you can configure a named client. You can set the default parameters on the HttpClient instance by using the delegate that the AddHttpClient receives.
builder.Services.AddHttpClient("emp", (serviceProvider, client) =>
{
var settings = serviceProvider
.GetRequiredService<IOptions<TestClientSettings>>().Value;
client.DefaultRequestHeaders.Add("Authorization", settings.Token);
client.DefaultRequestHeaders.Add("User-Agent", settings.UserAgent);
client.BaseAddress = new Uri("https://localhost:7061");
});
Here is the code to create the object of the HTTP client and send the request.
public class EmployeeService : IEmployeeService
{
private readonly IHttpClientFactory _factory;
public EmployeeService(IHttpClientFactory factory)
{
_factory = factory;
}
public async Task<Employee> GetEmployeeAsync(Guid id)
{
try
{
var client = _factory.CreateClient("emp");
var employee = await client.GetFromJsonAsync<Employee>($"/api/Employee/{id}");
return employee ?? new Employee();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
throw;
}
}
}
When we are creating a new object, we need to pass the name.
Replaced Named clients with the Typed clients
Having to resolve a HttpClient by sending in a name each time is a drawback of using named clients.
Setting up a typed client is a more effective method of accomplishing the same goal. Calling the AddClient<TClient> method and setting up the service that will use the HttpClient will allow you to accomplish this.
This still uses a named client internally, with the name being the same as the type name.
Additionally, EmployeeService will be registered with a transient lifetime.
builder.Services.AddHttpClient<EmployeeService>((serviceProvider, client) =>
{
var settings = serviceProvider
.GetRequiredService<IOptions<TestClientSettings>>().Value;
client.DefaultRequestHeaders.Add("Authorization", settings.Token);
client.DefaultRequestHeaders.Add("User-Agent", settings.UserAgent);
client.BaseAddress = new Uri("https://localhost:7061");
});
You inject and use the typed HttpClient object, which will have all of the configuration applied, inside of EmployeeService.
You can stop manually constructing HttpClient instances and interacting with IHttpClientFactory.
Here is the employee service logic.
public class EmployeeService
{
private readonly HttpClient _client;
public EmployeeService(HttpClient client)
{
_client = client;
}
public async Task<Employee> GetEmployeeAsync(Guid id)
{
try
{
var employee = await _client.GetFromJsonAsync<Employee>($"/api/Employee/{id}");
return employee ?? new Employee();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
throw;
}
}
}
The recommended method for utilizing a typed client in a singleton service is to set up the PooledConnectionLifetime and utilize SocketsHttpHandler as the primary handler.
Because the SocketsHttpHandler will handle connection pooling, you may turn off recycling at the IHttpClientFactory level by setting HandlerLifetime to Timeout.InfiniteTimeSpan.
builder.Services.AddHttpClient<EmployeeService>((serviceProvider, client) =>
{
var settings = serviceProvider
.GetRequiredService<IOptions<TestClientSettings>>().Value;
client.DefaultRequestHeaders.Add("Authorization", settings.Token);
client.DefaultRequestHeaders.Add("User-Agent", settings.UserAgent);
client.BaseAddress = new Uri("https://localhost:7061");
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new SocketsHttpHandler()
{
PooledConnectionLifetime = TimeSpan.FromMinutes(15)
};
})
.SetHandlerLifetime(Timeout.InfiniteTimeSpan);
In this article, we learn how to create the object IHttpClientFactory and use it to send requests. We also learn how to register a name-based HTTP client and use that client to send the request. In the next article, we will learn how to add the retry mechanism if the request fails due to a technical breach or other issues.
We learned the new technique and evolved together.
Happy coding!