.NETMedium
IHttpClientFactory in ASP.NET Core — why you should never new HttpClient()
new HttpClient() every call exhausts sockets (TIME_WAIT). Holding a single static HttpClient ignores DNS changes. IHttpClientFactory solves both.
What it does
- Pools
HttpMessageHandlerinstances (default 2-minute lifetime). - Rotates handlers periodically so DNS refresh actually kicks in.
- Wires Polly policies, headers, and base URLs into named/typed clients.
Three usage patterns
1. Named client
builder.Services.AddHttpClient("github", c => {
c.BaseAddress = new Uri("https://api.github.com");
c.DefaultRequestHeaders.UserAgent.ParseAdd("MyApp/1.0");
});
// Consume
public class GhController(IHttpClientFactory factory) : ControllerBase
{
public async Task<IActionResult> Repos()
{
var c = factory.CreateClient("github");
return Ok(await c.GetFromJsonAsync<object>("/repos"));
}
}
2. Typed client (preferred)
public class GitHubClient(HttpClient http)
{
public Task<IList<Repo>?> ListAsync() => http.GetFromJsonAsync<IList<Repo>>("/repos");
}
builder.Services.AddHttpClient<GitHubClient>(c => c.BaseAddress = new("https://api.github.com"));
3. With Polly resilience
builder.Services.AddHttpClient<GitHubClient>()
.AddStandardResilienceHandler(); // .NET 8+, replaces the old AddTransientHttpErrorPolicy
Why static HttpClient is also wrong
A singleton HttpClient reuses the same socket connection forever. If the DNS record changes (failover, scale-out, blue/green), your app keeps talking to the dead IP until the process restarts. IHttpClientFactory rotates handlers so DNS does refresh.
Lifetime cheat sheet
| Approach | Sockets exhausted? | DNS refresh? | Use? |
|---|---|---|---|
new HttpClient() per call | ❌ yes | ✅ yes | ❌ never |
Static HttpClient | ✅ no | ❌ no | ❌ rarely |
IHttpClientFactory | ✅ no | ✅ yes | ✅ always |