Why not HttpClient?
We have been using the HttpClient
class for REST API calls and I am not saying this is bad practice but most of the times developer use it wrongly and it has some limitations which cause serious issues, I have noted a few of them:
- Even we dispose of instance underlying socket doesn't release immediately, which leads to socket exhaustion.
using(var client = new HttpClient())
{
//do something with HTTP client
}
Do you think creating an instance using the using
block will work? The answer is it won't work. If you are curious about what is the current state or wanted to confirm that the underline socket is closed or not then run the below command and it will show a list of connections that are in a wait or established state.
> netstat -na | find 'Port_Number'
To avoid socket exhaustion if you create a singleton or static instance throughout the application lifetime then it's not the ultimate solution because when you reuse the instance which means you are reusing the connection until the socket is closed. So the connection won't get updates from the server, effectively it will fail to handle DNS changes when we switch from
staging
toproduction
environment. You can find more details here.Creating an instance inside the
using
directive is not good practice as it tries to dispose of instances ofHttpClient
along with that it will dispose of theHttpClientHandler
as well. Under the hood,HttpClient
uses theHttpClientHandler
for actual socket connection, but underhood implementation is out of scope for this article but I will create a separate detailed article on it.
To address the above issue .Net core introduced the IHttpClientFactory
interface which we can use for HttpClient
instance creation using DI.
Different ways to use IHttpClientFactory:
1. Basic usage:
You can register IHttpClientFactory
using AddHttpClient
extension method.
Program.cs
or Startup.cs
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddHttpClient();
TestController.cs
: DI will inject the dependency in the constructor.
class TestController : Controller
{
private readonly IHttpClientFactory _factory;
public TestController(IHttpClientFactory factory)
{
_factory = factory;
}
[HttpGet]
public async Task<IActionResult> Get()
{
var httpClient = _factory.CreateClient();
}
}
2. Named Clients:
If you need a different configuration for each HttpClient then you can use named client DI and get an instance using the same name. Let's say you need to interact with 2 third-party APIs using HTTP REST calls and both need to be configured differently using the named client as below.
Startup.cs
:
builder.Services.AddHttpClient("ClientA", httpClient =>
{
httpClient.BaseAddress = new Uri("https://apiA.com/");
//ToDo more configuration
});
builder.Services.AddHttpClient("ClientB", httpClient =>
{
httpClient.BaseAddress = new Uri("https://apiB.com/");
//ToDo more configuration
});
TestController.cs
:
var httpClientA = _factory.CreateClient("ClientA");
var httpClientB = _factory.CreateClient("ClientB");
3. Typed Clients
It provides the same capabilities as the named client but instead of identifying the client based on the key it actually intelligently resolve HttpClient in a specific HttpHandler
class. Another advantage of this approach is you don't need to create HttpClient
manually using IHttpClientFactory.CreateClient()
method.
Let's say you decided to write one custom HttpHandler
which would be the single source for every REST call, so your implementation would look something like below.
HttpCommands.cs
:
public class HttpCommands
{
private readonly HttpClient _httpClient;
public HttpCommands(HttpClient httpClient)
{
_httpClient = httpClient;
}
}
Look at the constructor we are injecting HttpClient
itself and not IHttpClientFactory
, which means _factory.CreateClient()
is not required anymore because .Net core is smart enough to resolve the dependency.
Startup.cs
: To register typed HttpClient
using generic version of AddHttpClient()
extension method.
services.AddHttpClient<HttpCommands>();
//Or if you need custom configuration you can configure that too as we did for the named client
services.AddHttpClient<HttpCommands>(client =>
{
client.BaseAddress = new Uri("https://apiA.com/");
});
Conclusion
- What are the issues in traditional
HttpClient
usage? - What are the issues with the
static
orsingleton
HttpClient
instance. - What are the different ways to use the
IHttpClientFactory
.
Happy Coding!
Top comments (0)