HttpClient Factory
SkillAI & modelsThis skill gives your AI the know-how to set up HTTP connections in .NET 10 applications using IHttpClientFactory and typed clients. Once added, your AI can configure different client types, add retry and circuit breaker policies, and follow standard testing patterns. Load it when you need to configure HTTP clients or make outgoing requests more resilient.
Available today. Use it from your connected AI after setup.
No other account needed.
Add the skill, then tell your AI what you need, such as configuring a typed client or adding retry policies, and it will apply the covered setup patterns to your .NET 10 project.
Then ask your AI: use the HttpClient Factory skill
What your AI can do with it
- Configure named, typed, or keyed HTTP clients in .NET 10 apps
- Add retry and circuit breaker policies so requests recover from failures
- Set up resilience with Microsoft.Extensions.Http.Resilience
- Create DelegatingHandlers to customize how requests are processed
- Apply testing patterns for HTTP client code
What this skill tells your AI
The instructions your AI receives, as published by codewithmukesh/dotnet-claude-kit in skills/httpclient-factory/SKILL.md and read by ahel’s review.
Core Principles
- Never
new HttpClient()per request — RawHttpClientcreation 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 |
Signals
- GitHub stars
- 710
- Forks
- 167
- Last commit
- Aug 2026
Others that do the same job
Advanced
- Catalog kind
- skill
- Gateway key
httpclient-factory- Source
- github.com/codewithmukesh/dotnet-claude-kit