Skip to content

HCR020

DelegatingHandler should not capture scoped request data.

Why

DelegatingHandler instances can be cached as part of an HTTP handler pipeline. Capturing request-scoped state such as IHttpContextAccessor, HttpContext, ClaimsPrincipal, or ISession can leak stale request data across outbound calls.

Bad

public sealed class UserHeaderHandler(IHttpContextAccessor accessor) : DelegatingHandler
{
}
services.AddScoped<IUserContext, UserContext>();

public sealed class UserHeaderHandler(IUserContext userContext) : DelegatingHandler
{
}

Better

Pass per-request values through HttpRequestMessage.Options or construct headers outside the cached handler pipeline.

Current Detection

The implementation reports constructor and primary-constructor parameters on DelegatingHandler implementations when the parameter type is one of the high-confidence request-scoped types or a service known from compilation-wide IServiceCollection-shaped scoped registrations. When no scoped constructor parameter has already been reported for the handler, it also reports scoped field and property declarations so property injection and direct member captures are covered without duplicating the common constructor-capture diagnostic. Scoped registration matching includes AddScoped<TService>(), AddScoped<TService, TImplementation>(), AddScoped(typeof(TService), ...), and visible scoped factory registrations that directly construct an implementation with new Implementation(...), including parenthesized or null-forgiving construction expressions. It also unwraps common single-argument deferred and collection wrappers such as Func<T>, Lazy<T>, IEnumerable<T>, IReadOnlyCollection<T>, and IReadOnlyList<T> before checking the captured type. Handler implementations include direct DelegatingHandler subclasses and classes that inherit from a visible handler base type in the same compilation. Registration receivers are validated semantically as visible IServiceCollection shapes when their declaration is available, including receiver type aliases, and resolved registration methods must belong to the Microsoft DI or global namespace. It compares visible simple, qualified, and nullable type names for scoped service registrations; explicitly qualified framework request-scoped parameter types must match their expected namespaces, and resolved custom request-like types in other namespaces are skipped unless they are registered as scoped services. Resolved handler base types must be System.Net.Http.DelegatingHandler or a visible derived handler base, and qualified unresolved base names must match the framework handler or a visible derived handler base, so unrelated lookalike base classes are skipped.

Suppression

Suppress only when the dependency is not request scoped in the consuming application or the handler is not part of a cached HttpClientFactory pipeline.

References