Skip to content

HCR001

Do not create and dispose HttpClient per request.

Why

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.

Bad

public sealed class PaymentsService
{
    public async Task<string> GetAsync(CancellationToken cancellationToken)
    {
        using var client = new HttpClient();
        return await client.GetStringAsync("https://example.com", cancellationToken);
    }
}

Better

public sealed class PaymentsService(HttpClient client)
{
    public Task<string> GetAsync(CancellationToken cancellationToken)
    {
        return client.GetStringAsync("https://example.com", cancellationToken);
    }
}

Current Detection

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.

Code Fix

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. The fix 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();
}

Suppression

Suppress only for non-production sample code, tests, or deliberately isolated one-off tooling where connection reuse is not relevant.

References