HCR004¶
Do not inject typed HttpClient clients into singleton services.
Why¶
Typed clients are usually registered as transient services. Injecting them into singletons can accidentally promote them to singleton lifetime and hold onto dependencies longer than intended.
Bad¶
services.AddHttpClient<PaymentsClient>();
services.AddSingleton<PaymentJob>();
public sealed class PaymentJob(PaymentsClient paymentsClient)
{
}
Better¶
Inject IHttpClientFactory, change the consuming service lifetime, or create a scoped boundary for the typed client.
Current Detection¶
The implementation builds a lightweight IServiceCollection registration model across the compilation and reports AddSingleton<TConsumer>() or AddSingleton<TService, TImplementation>() registrations when the singleton implementation has a constructor or primary constructor parameter matching a typed client registered with AddHttpClient<TClient>() or AddHttpClient<TService, TImplementation>(). It also unwraps common single-argument deferred and collection wrappers such as Func<T>, Lazy<T>, and IEnumerable<T> before checking constructor parameters or factory resolutions. It reports lambda or anonymous singleton factory registrations that visibly resolve a typed client from the factory IServiceProvider with the Microsoft DI GetService<T>() or GetRequiredService<T>() extensions, including parenthesized or null-forgiving provider receivers, or that directly construct an implementation whose constructor consumes a typed client.
This catches traditional Startup methods, minimal hosting configuration, extension methods, registrations split across files, nullable constructor parameters, visible singleton factories including typeof(...) singleton factory overloads and new Implementation(...) factory bodies, aliased IServiceCollection registration receivers, aliased System.IServiceProvider factory parameters, and visible qualified type names such as Clients.PaymentsClient. Registration receivers and both inferred and explicitly typed factory parameters are validated semantically when their declarations are available, and resolved registration methods must belong to the Microsoft DI or global namespace. Qualified singleton implementation registrations and resolved singleton constructor or factory resolution types are matched against declared namespaces, so same-named typed clients, singleton classes, service-provider interfaces, inferred custom factory types, or custom registration extensions in other namespaces are skipped.
Suppression¶
Suppress only when the consuming singleton is not actually created by DI or the typed-client dependency is never retained beyond a safe scope.