HCR003¶
Do not cache IHttpClientFactory.CreateClient() results long-term.
Why¶
Factory-created clients are intended to be requested when needed while the factory manages handler lifetime underneath. Caching those clients in static fields or known singleton objects can bypass the intended lifetime model and reintroduce stale-client behavior.
Bad¶
public sealed class ClientCache
{
private static HttpClient _client = null!;
public static void Initialize(IHttpClientFactory factory)
{
_client = factory.CreateClient("github");
}
}
public sealed class ClientCache
{
private static readonly HttpClient Client = Factory.CreateClient("github");
}
Better¶
public sealed class ClientRunner(IHttpClientFactory factory)
{
public async Task RunAsync(CancellationToken cancellationToken)
{
var client = factory.CreateClient("github");
using var response = await client.GetAsync("/repos", cancellationToken);
}
}
Current Detection¶
The implementation reports assignments or initializers from visible IHttpClientFactory.CreateClient(...) calls into static HttpClient fields/properties, into instance HttpClient fields/properties on types whose names end with Singleton, and into instance HttpClient fields/properties on services known from compilation-wide IServiceCollection-shaped singleton registrations. This includes direct assignments and simple local handoffs such as var client = factory.CreateClient(...); _client = client; when the local has not been reassigned before the long-lived assignment; parentheses and null-forgiving operators are transparent around those factory-client expressions. Singleton registration matching includes 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. Factory receivers are matched as simple IHttpClientFactory, explicitly qualified System.Net.Http.IHttpClientFactory, or resolved members whose type is IHttpClientFactory. Qualified singleton registrations are matched against the declared containing type namespace so same-named services in other namespaces are skipped, non-HttpClient destination members are skipped, and lookalike CreateClient(...) methods on other factory types, custom extension overloads, or unresolved provider chains are skipped.
Suppression¶
Suppress only when the analyzer cannot see a custom lifetime boundary and the client is not actually cached long-term.