Modern Microservices constantly talk to each other. Your API might need to hit Stripe's API to process a card, or an AWS endpoint to grab an image. While manually typing new HttpClient() seems easy, it is a catastrophic memory-leak anti-pattern that will eventually crash your entire server via Socket Exhaustion.
Whenever you instantiate new HttpClient(), under the hood it opens physical network sockets on the operating system to send the bytes. When you dispose of the client after the request, the OS physically keeps that socket "half-open" (in a TIME_WAIT state) for up to 4 minutes.
If your API receives 5,000 requests, and each request instantiates a new HttpClient(), you just exhausted 5,000 network sockets. The server has run out of ports. The 5,001st request will throw an extreme fatal Exception and the server halts.
Instead of manually creating clients, ASP.NET Core introduced IHttpClientFactory. It maintains a secure internal pool of HTTP connections. Instead of destroying the socket, it recycles it efficiently.
var builder = WebApplication.CreateBuilder(args);
// Register the factory into the Dependency Injection container
builder.Services.AddHttpClient();
[ApiController]
[Route("api/external")]
public class ExternalDataController : ControllerBase
{
private readonly IHttpClientFactory _httpClientFactory;
public ExternalDataController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
[HttpGet("github-profile")]
public async Task<IActionResult> GetGithubData()
{
// 1. Ask the factory for a safe client
var client = _httpClientFactory.CreateClient();
// 2. Make the external request
var response = await client.GetAsync("https://api.github.com/users/octocat");
if (response.IsSuccessStatusCode)
{
// Safely read the JSON payload
var content = await response.Content.ReadAsStringAsync();
return Ok(content);
}
return StatusCode(500, "Failed to reach GitHub API.");
}
}
Constantly typing out base URLs and API Keys into every controller logic is messy. We should encapsulate the HttpClient into its own dedicated Service Class.
public class GithubApiService
{
private readonly HttpClient _client;
// The factory injects the HttpClient directly into this constructor!
public GithubApiService(HttpClient client)
{
_client = client;
_client.BaseAddress = new Uri("https://api.github.com/");
_client.DefaultRequestHeaders.Add("User-Agent", "MyCompany-App");
}
public async Task<string> GetProfileAsync(string username)
{
return await _client.GetStringAsync($"users/{username}");
}
}
// Automatically wires the factory to this specific service!
builder.Services.AddHttpClient<GithubApiService>();
Q: "When an external API goes completely offline, our `await client.GetAsync()` call might hang for 100 seconds before timing out, blocking our internal threads. How do we prevent cascading failures when external services die?"
Architect Answer: "We use the Polly library to implement the Circuit Breaker pattern. Using the `IHttpClientFactory`, we attach Polly to the `AddHttpClient` registration. We define rules saying: 'If GitHub fails 5 times in a row, physically break the circuit.' Instead of letting the 6th HTTP Request wait 100 seconds for a timeout, the Circuit Breaker instantly intercepts the 6th request and immediately returns a failure. This prevents our web server from starving its Thread Pool while waiting for a definitively dead external service."