Table of contents
- Using HttpClient Wrong Can Break Your App
Using HttpClient Wrong Can Break Your App
Let’s talk about something that might seem simple but can cause BIG problems if you’re not careful: HTTP clients. These are the tools your app uses to talk to other services on the internet. But if you use them the wrong way, your app can crash.
The Wrong Way: Creating a New HttpClient Every Time ❌
Imagine you’re building an app that needs to call an API. You think, “I’ll just create a new HttpClient
every time I need to make a request. Easy, right?” So you write something like this:
public async Task<User> GetUserAsync(string username)
{
using var client = new HttpClient(); // Danger! 🚨
client.BaseAddress = new Uri("https://api.coolservice.com");
client.DefaultRequestHeaders.Add("Authorization", "Bearer my-token");
var user = await client.GetFromJsonAsync<User>($"users/{username}");
return user;
}
Looks simple, right? But here’s the problem: HttpClient is not meant to be used this way!
Why This Is Bad
Port Exhaustion: Every time you create a new
HttpClient
, it opens a new connection. If you do this too much, your app can run out of available ports. When this happens, your app crashes.Wasted Resources: Creating and disposing of
HttpClient
over and over is like buying a new phone every time you want to make a call. It’s wasteful and inefficient.
The Big Problem with Disposing Handlers
Here’s where things get tricky. When you dispose of a HttpClient
, it also disposes of its HttpMessageHandler
by default. This might seem fine, but remember: the handler manages the underlying connections. If you dispose of it, those connections are closed, and new ones have to be created the next time you make a request.
This is why creating and disposing of HttpClient
instances over and over can lead to port exhaustion and DNS issues. It’s like constantly tearing down and rebuilding a bridge instead of reusing it.
HttpClientFactory
is smart. When it creates a HttpClient
, it sets disposeHandler
to false
. This means the handler isn’t disposed of when the HttpClient
is disposed. Instead, the factory manages the handler’s lifecycle, reusing it when possible and disposing of it only when it’s no longer need.
explore source code : HttpClientLibrary
TL;DR: The Key Takeaways
HttpClient’s constructors control whether the handler is disposed of.
Disposing handlers closes connections, which can lead to port exhaustion.
HttpClientFactory sets
disposeHandler
tofalse
, reusing handlers and connections.Reusing handlers is the secret sauce to keeping your app fast and reliable.
The Right Way: Use HttpClientFactory ✅
Instead of creating HttpClient
manually, you should use HttpClientFactory. Think of it as a smart manager that handles all the connections for you. It reuses connections, avoids port exhaustion, and makes your life easier.
public class CoolService
{
private readonly IHttpClientFactory _httpClientFactory;
public CoolService(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task<User> GetUserAsync(string username)
{
var client = _httpClientFactory.CreateClient(); //No more "using"
client.BaseAddress = new Uri("https://api.coolservice.com");
client.DefaultRequestHeaders.Add("Authorization", "Bearer my-token");
var user = await client.GetFromJsonAsync<User>($"users/{username}");
return user;
}
}
Why This Is the best way:
Reuses Connections: HttpClientFactory manages a pool of connections, so you don’t waste resources.
Short-Lived Clients: The
HttpClient
instances are meant to be used quickly and then discarded. The factory handles the cleanup for you.Handles DNS Changes: If the API’s IP address changes (common in cloud environments), HttpClientFactory adapts automatically. No more broken connections!
Level Up: Named and Typed Clients 🚀
Want to make your code even cleaner? Use named clients or typed clients. These let you pre-configure your HttpClient
so you don’t have to repeat the same setup code every time.
Named Clients
services.AddHttpClient("CoolService", client =>
{
client.BaseAddress = new Uri("https://api.coolservice.com");
client.DefaultRequestHeaders.Add("Authorization", "Bearer my-token");
});
Then, use it in your service:
public async Task<User> GetUserAsync(string username)
{
var client = _httpClientFactory.CreateClient("CoolService"); //pass in name of the client
var user = await client.GetFromJsonAsync<User>($"users/{username}");
return user;
}
Typed Clients
Even better, you can create a typed client. This is like having a custom-made HttpClient
for your service.
First, define your typed client:
public class CoolService
{
private readonly HttpClient _client;
public CoolService(HttpClient client)
{
_client = client;
}
public async Task<User> GetUserAsync(string username)
{
return await _client.GetFromJsonAsync<User>($"users/{username}");
}
}
Then, configure it in Startup.cs
:
services.AddHttpClient<CoolService>(client =>
{
client.BaseAddress = new Uri("https://api.coolservice.com");
client.DefaultRequestHeaders.Add("Authorization", "Bearer my-token");
});
Now, you can inject CoolService
directly, and the HttpClient
will be ready to go. No more repetitive setup!
Watch Out: Typed Clients in Singleton Services ⚠️
Here’s a pro tip: Don’t use typed clients in singleton services. Why? Because typed clients are meant to be short-lived, and if they’re stuck in a singleton, they won’t react to DNS changes. It’s like using an old map forever, even though the city has changed. 🗺️
If you use a typed client in a singleton, configure the PooledConnectionLifetime
to refresh connections periodically:
services.AddHttpClient<CoolService>(client =>
{
client.BaseAddress = new Uri("https://api.coolservice.com");
client.DefaultRequestHeaders.Add("Authorization", "Bearer my-token");
})
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(15) // Refresh connections
})
.SetHandlerLifetime(Timeout.InfiniteTimeSpan);
TL;DR: Why HttpClientFactory Wins
No Port Exhaustion: Reuses connections instead of creating new ones.
Handles DNS Changes: Keeps your app up-to-date with changing IP addresses.
Cleaner Code: Named and typed clients make your code easier to read and maintain.
No Disposal Drama: Let the factory handle the lifecycle of
HttpClient
.
So, next time you’re tempted to create a new HttpClient
, remember: HttpClientFactory is your friend. Use it, and keep your app running smoothly.