| title | HCR001 — Do not create and dispose HttpClient per request |
|---|---|
| description | Roslyn analyzer for per-request new HttpClient() that can cause socket exhaustion. Prefer IHttpClientFactory or a long-lived client. |
Do not create and dispose HttpClient per request.
Creating short-lived HttpClient instances in request paths can contribute to socket exhaustion and connection churn. Prefer IHttpClientFactory, typed clients, named clients, or a long-lived manually configured client.
public sealed class PaymentsService
{
public async Task<string> GetAsync(CancellationToken cancellationToken)
{
using var client = new HttpClient();
return await client.GetStringAsync("https://example.com", cancellationToken);
}
}public sealed class PaymentsService(HttpClient client)
{
public Task<string> GetAsync(CancellationToken cancellationToken)
{
return client.GetStringAsync("https://example.com", cancellationToken);
}
}The implementation reports high-confidence new HttpClient() usage when it appears in likely request-path types, inside loops, Minimal API endpoint lambdas, or using ownership patterns, including top-level statement loops and using var declarations. Minimal API endpoint detection is limited to visible MapGet/MapPost-style calls on common endpoint-builder receivers such as app or IEndpointRouteBuilder declarations, including visible route-group chains and route-group locals created from MapGroup(...); parentheses and null-forgiving operators are transparent around those receivers. Resolved custom types named HttpClient are skipped. Test types ending with Test or Tests, and common attributed xUnit, NUnit, and MSTest contexts such as [Fact], [Theory], [Test], [TestFixture], [SetUp], [TestMethod], [TestInitialize], [TestCleanup], and [TestClass], are ignored.
When the nearest containing local function or method, or the class primary constructor, already has an IHttpClientFactory parameter, the code fix replaces new HttpClient() with <factoryParameter>.CreateClient(). This nearest-scope selection keeps a local function's factory parameter from accidentally being replaced with an outer method parameter. When no parameter is in scope, the fix reuses a readable, non-nullable IHttpClientFactory-typed field or property declared on the containing type — preferring members whose name contains Factory, otherwise ordering by name — and skips members shadowed by an in-scope local or parameter. The fix is not offered in static contexts and does not add constructor parameters or change DI registrations automatically.
For example, a primary-constructor factory parameter is reused without changing the constructor:
public sealed class PaymentsService(IHttpClientFactory factory)
{
public HttpClient Create() => new HttpClient();
}The code action produces:
public sealed class PaymentsService(IHttpClientFactory factory)
{
public HttpClient Create() => factory.CreateClient();
}Suppress only for non-production sample code, tests, or deliberately isolated one-off tooling where connection reuse is not relevant.