Skip to content

HCR002

Long-lived manual HttpClient should configure PooledConnectionLifetime.

Why

A static or otherwise long-lived manual HttpClient can keep connections alive longer than DNS changes or infrastructure rotation. Configure SocketsHttpHandler.PooledConnectionLifetime when manually owning the client.

Bad

private static readonly HttpClient Client = new();
services.AddSingleton<GitHubClient>();

public sealed class GitHubClient
{
    private readonly HttpClient _client = new();
}

Better

private static readonly HttpClient Client = new(
    new SocketsHttpHandler
    {
        PooledConnectionLifetime = TimeSpan.FromMinutes(2)
    });

Current Detection

The implementation reports static HttpClient fields/properties and instance HttpClient fields/properties on known singleton services when they are initialized or assigned with a default handler. This includes direct new HttpClient(...) assignments, null-forgiving client expressions, and simple local handoffs such as var client = new HttpClient(); Client = client; when the local has not been reassigned before the long-lived assignment. Known singleton services include aliased IServiceCollection receivers, AddSingleton<TService>(), AddSingleton<TService, TImplementation>(), AddSingleton(typeof(TService), ...), and visible singleton factory registrations that directly construct an implementation with new Implementation(...); resolved registration methods must belong to the Microsoft DI or global namespace. Resolved custom types named HttpClient or SocketsHttpHandler are skipped. Qualified singleton registrations are matched against the declared containing type namespace so same-named services in other namespaces are skipped. It does not report ordinary instance members, clients initialized or assigned with a SocketsHttpHandler object initializer that sets PooledConnectionLifetime, or clients initialized from a visible local, field, or property handler whose initializer sets PooledConnectionLifetime, including null-forgiving handler expressions. Local handler variables are also treated as configured when handler.PooledConnectionLifetime = ... is assigned before the HttpClient is created and the handler local is not reassigned before that use; transparent null-forgiving syntax on the handler receiver is handled the same way.

Code Fix

For simple parameterless field HttpClient initializers, the code fix replaces the default initializer with fully qualified framework HttpClient and SocketsHttpHandler types configured with PooledConnectionLifetime = global::System.TimeSpan.FromMinutes(2). Fully qualifying the generated types avoids user-defined SocketsHttpHandler or TimeSpan names shadowing the framework APIs. When the client already receives a handler argument, or when the diagnostic is on an assignment/property shape, the analyzer reports the issue but does not offer an automatic rewrite because preserving member and handler configuration requires manual review.

Suppression

Suppress only when DNS rotation is impossible or the handler lifetime is managed elsewhere and the analyzer cannot see it.

References