Skip to content

Latest commit

 

History

History
46 lines (28 loc) · 3.92 KB

File metadata and controls

46 lines (28 loc) · 3.92 KB
title HCR004 — Do not inject typed HttpClient clients into singletons
description Typed HttpClient clients injected into singleton services can capture the wrong handler lifetime and leak scoped state.

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 reports AddHostedService<TWorker>() registrations whose worker consumes a typed client, because hosted services run with singleton lifetime, as well as hosted-service factory registrations that visibly resolve a typed client from the factory IServiceProvider. 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.

Code Fix

A code fix rewrites the flagged AddSingleton registration into AddScoped, preserving type arguments and surrounding trivia, so the consuming service shares the scoped lifetime of the typed client it resolves. Fix All renames every flagged singleton registration in the document at once. The fix is only offered when the diagnostic points at a visible AddSingleton call; hosted-service registrations cannot be re-scoped and must be restructured manually, as must factory-heavy or dynamically composed registrations.

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.

References