HttpClient in .NET– is there a best way ?
By Mirek on (tags: ASP.NET, HttpClient, HttpClientFactory, .NET 6.0, categories: architecture, code, web)HttpClient is probably one of the most important and most commonly used class in .NET ecosystem. However, it looks like not every developer (including myself) knows how to properly use it.
Well, in my opinion the reason for that, is also partially caused by not fully clear recomendation from the .NET team on how the HttpClient should be used. During last few years some problems and concerns about the HttpClient poped up, which made it even more confusing.
- First of all, HttpClient class implements IDisposable, so according to good practices and recomendation it should be instanciated within a using statement and disposed as soon as not needed. Basically it means you must create new instance of HttpClient every time you need to make an external call.
- Then the problem with sockets exhaustion arised (well described for example in this post) and, as it turned out, disposing HttpClient object does not free up all resources and not neccesairly closes underlying connection, which in some circumstances lead to exhaust of all available http connections.
The proposed solution for that is to use HttpClient as static singleton application wide! And that is still a Microsoft recomendation, for example when you go to the Call a Web API From a .NET Client guidance or Make HTTP requests in a .NET console app using C# tutorial. - Unfortunatelly that has not solved all of the problems. Singleton HttpClient doesn't respect DNS changes issue describes a problem that HttpClient is not able to handle DNS changes properly when it’s lifetime is long and in some circumstances may result in connection failure.
- Finally, we have the cure for all of the these problems. According to the latest recomendation here and here the way to go with http connections is to use IHttpClientFactory in .NET Core (already since .NET Core 2.1).
Let’s quickly see how we can use IHttpClientFactory in .NET. We will use a console app and generic host, but that obviously applies to all .NET project types. The only requirement is to use dependency injection.
First create new console app project and import two nuget packages Microsoft.Extensions.Hosting and Microsoft.Extensions.Http. Then put following code in Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
IHost _host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddHttpClient();
services.AddTransient<MyService>();
})
.Build();
_host.Start();
var ip = await _host.Services.GetRequiredService<MyService>().GetMyIP();
Console.WriteLine($"My IP is {ip}...");
Console.ReadKey();
public class MyService
{
private readonly IHttpClientFactory httpFactory;
public MyService(IHttpClientFactory httpFactory)
{
this.httpFactory = httpFactory;
}
public async Task<string> GetMyIP()
{
var http = httpFactory.CreateClient();
return await http.GetStringAsync(https://api.ipify.org);
}
}
The code is quite self explanatory, I think. First we need to register HttpClientFactory with use of extension method AddHttpClient. Then we have a service class called MyService which call ipify.org web service and returns our IP address. The service class gets the http client factory and calls CreateClient on every call. This is the recommended way, but if we are sure that the service class is short-lived we can make it even simpler and get the HttpClient directly to the service
public class MyService
{
private readonly HttpClient httpClient;
public MyService(HttpClient httpClient)
{
this.httpClient = httpClient;
}
public async Task<string> GetMyIP()
{
return await httpClient.GetStringAsync(https://api.ipify.org);
}
}
Further we can setup named or typed HttpClient’s or even configure the underlying HttpMessageHandler.
Thanks!
Matt
2/19/2024 9:25 PM
I see you register MyService as Transient. Should it also be a singleton?