How to use
- Copy the installation command and review the source package before installing it in your own agent environment.
- Check the source document for required tools, credentials and supported hosts.
- Describe your input and desired deliverable; review the output against the source material.
npx skills add https://github.com/codewithmukesh/dotnet-claude-kit --skill httpclient-factoryBefore you start
- Installation does not include a model subscription, API allowance or permission to access your files.
- Repository stars and installation counts describe activity, not an independent quality or security test.
Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.
HttpClient Factory
Core Principles
- Never `new HttpClient()` per request — Raw
HttpClientcreation causes socket exhaustion under load and ignores DNS changes. UseIHttpClientFactoryto manage handler lifetimes. - Keyed clients over typed clients — Keyed DI (
.AddAsKeyed()) is the recommended pattern in .NET 10. Typed clients captured in singletons silently break handler rotation. - Resilience is not optional — Every external HTTP call needs retry, circuit breaker, and timeout.
AddStandardResilienceHandler()provides sensible defaults in one line. - DelegatingHandlers for cross-cutting concerns — Auth tokens, correlation IDs, and logging belong in the handler pipeline, not scattered across service methods.
Patterns
Named Client with Resilience
builder.Services.AddHttpClient("github", client =>
{
client.BaseAddress = new Uri("https://api.github.com/");
client.DefaultRequestHeaders.UserAgent.ParseAdd("MyApp/1.0");
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
})
.AddStandardResilienceHandler();
// Usage via factory
public sealed class GitHubService(IHttpClientFactory factory)
{
public async Task<Repo?> GetRepoAsync(string owner, string name, CancellationToken ct)
{
var client = factory.CreateClient("github");
return await client.GetFromJsonAsync<Repo>($"repos/{owner}/{name}", ct);
}
}Keyed Client (Recommended in .NET 10)
Combines named client configurability with direct injection. No string lookups.
builder.Services.AddHttpClient("payments", client =>
{
client.BaseAddress = new Uri("https://api.payments.example.com/");
})
.AddStandardResilienceHandler()
.AddAsKeyed(); // Register as keyed scoped service
// Inject directly — no IHttpClientFactory needed
app.MapPost("/charge", async (
[FromKeyedServices("payments")] HttpClient httpClient,
ChargeRequest request,
CancellationToken ct) =>
{
var response = await httpClient.PostAsJsonAsync("charges", request, ct);
return response.IsSuccessStatusCode
? TypedResults.Ok()
: TypedResults.Problem("Payment failed");
});Global opt-in: builder.Services.ConfigureHttpClientDefaults(b => b.AddAsKeyed());
Standard Resilience Handler
AddStandardResilienceHandler() chains 5 strategies:
| Strategy | Default |
|---|---|
| Rate limiter | 1000 concurrent requests |
| Total timeout | 30 seconds |
| Retry | 3 retries, exponential backoff with jitter |
| Circuit breaker | Opens at 10% failure rate |
| Attempt timeout | 10 seconds per attempt |
builder.Services.AddHttpClient("api")
.AddStandardResilienceHandler(options =>
{
options.Retry.MaxRetryAttempts = 5;
options.Retry.Delay = TimeSpan.FromSeconds(1);
options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(60);
options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(15);
// Disable retries for non-idempotent methods
options.Retry.DisableForUnsafeHttpMethods();
});DelegatingHandler for Auth Token Injection
public sealed class AuthenticationHandler(ITokenService tokenService)
: DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var token = await tokenService.GetAccessTokenAsync(cancellationToken);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
return await base.SendAsync(request, cancellationToken);
}
}
// Registration
builder.Services.AddTransient<AuthenticationHandler>();
builder.Services.AddHttpClient("api")
.AddHttpMessageHandler<AuthenticationHandler>()
.AddStandardResilienceHandler();DelegatingHandler for Correlation ID Propagation
public sealed class CorrelationIdHandler(IHttpContextAccessor httpContextAccessor)
: DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
if (httpContextAccessor.HttpContext?.Request.Headers
.TryGetValue("X-Correlation-Id", out var correlationId) is true)
{
request.Headers.Add("X-Correlation-Id", correlationId.ToString());
}
return base.SendAsync(request, cancellationToken);
}
}SocketsHttpHandler Configuration
builder.Services.AddHttpClient("advanced")
.UseSocketsHttpHandler((handler, _) =>
{
handler.PooledConnectionLifetime = TimeSpan.FromMinutes(2);
handler.PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1);
handler.MaxConnectionsPerServer = 100;
handler.AutomaticDecompression =
DecompressionMethods.GZip | DecompressionMethods.Brotli;
});Testing with Mock Handler
public sealed class MockHttpHandler(
HttpStatusCode statusCode,
string content) : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(new HttpResponseMessage(statusCode)
{
Content = new StringContent(content, Encoding.UTF8, "application/json")
});
}
}
// In test
var handler = new MockHttpHandler(HttpStatusCode.OK, """{"id":1}""");
var client = new HttpClient(handler) { BaseAddress = new Uri("https://api.test/") };
var service = new MyService(client);Anti-patterns
Don't Create HttpClient Per Request
// BAD — socket exhaustion under load, ignores DNS changes
public async Task<string> GetDataAsync()
{
using var client = new HttpClient();
return await client.GetStringAsync("https://api.example.com/data");
}
// GOOD — factory-managed
public async Task<string> GetDataAsync(CancellationToken ct)
{
var client = factory.CreateClient("api");
return await client.GetStringAsync("https://api.example.com/data", ct);
}Don't Capture Typed Clients in Singletons
// BAD — transient HttpClient captured by singleton defeats handler rotation
services.AddSingleton<MySingletonService>();
services.AddHttpClient<MySingletonService>();
// GOOD — use keyed client or IHttpClientFactory in singletons
services.AddSingleton<MySingletonService>();
services.AddHttpClient("myservice").AddAsKeyed(ServiceLifetime.Singleton);Don't Mutate DefaultRequestHeaders on Shared Clients
// BAD — not thread-safe
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
// GOOD — use DelegatingHandler or per-request HttpRequestMessage
using var request = new HttpRequestMessage(HttpMethod.Get, "/api/data");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
await httpClient.SendAsync(request, ct);Don't Forget CancellationToken
// BAD — no cancellation support
var result = await httpClient.GetFromJsonAsync<Order>("/orders/1");
// GOOD — always pass CancellationToken
var result = await httpClient.GetFromJsonAsync<Order>("/orders/1", cancellationToken);Don't Stack Multiple Resilience Handlers
// BAD — conflicting resilience strategies
builder.AddStandardResilienceHandler();
builder.AddStandardHedgingHandler();
// GOOD — one standard handler, or a custom pipeline
builder.AddStandardResilienceHandler();Decision Guide
| Scenario | Recommendation |
|---|---|
| New .NET 10 project | Keyed clients with AddAsKeyed() |
| Singleton service needs HttpClient | Named client via IHttpClientFactory or keyed singleton |
| External API calls | AddStandardResilienceHandler() on every client |
| Auth token injection | DelegatingHandler registered with AddHttpMessageHandler |
| Hedging (parallel requests) | AddStandardHedgingHandler() for latency-sensitive calls |
| Non-idempotent methods | DisableForUnsafeHttpMethods() on retry options |
| Custom retry logic | AddResilienceHandler("name", builder => ...) |
| Connection pooling control | UseSocketsHttpHandler with PooledConnectionLifetime |
| API client generation | Refit with AddRefitClient<T>() |
| Integration testing | Custom HttpMessageHandler or MockHttpMessageHandler |




