Compile-time Roslyn analyzers and code fixes for .NET HttpClient, IHttpClientFactory, AddHttpClient typed and named clients, Polly, and Microsoft.Extensions.Http.Resilience.
Catch outbound HTTP reliability bugs during development—not after deployment. The analyzer detects socket-exhaustion risks, missing PooledConnectionLifetime, stale DNS clients, DI lifetime leaks, typed-client configuration collisions, unsafe retries, undisposed responses and streams, sync-over-async, dropped cancellation tokens, unbounded fan-out, and fragile named-client strings.
The package is analyzer-only and adds no runtime dependency to your application. The analyzer stays quiet when a case is unprovable.
ASP.NET Core and .NET teams that use HttpClient, IHttpClientFactory, typed clients, Polly, or Microsoft.Extensions.Http.Resilience. It does not replace those libraries—it flags production-safety mistakes they cannot see at compile time, unlike general IDisposable rules such as CA2000.
Most .NET services use HttpClient, but many production issues come from patterns that compile cleanly and look harmless in review:
- Per-request
new HttpClient()that exhausts sockets under load - Static clients without
PooledConnectionLifetimethat hold stale DNS - Typed clients captured by singletons or registered twice
AddStandardResilienceHandlerretrying non-idempotent POST,AddStandardHedgingHandlerreplaying it concurrently, or a customAddResilienceHandlerpipeline callingAddRetry- Undisposed streaming responses and sync-over-async on outbound calls
Those failures often appear only under traffic—after deploy.
- Lifetime: per-request clients, long-lived clients without
PooledConnectionLifetime, cached factory clients - DI / typed clients: singleton injection, duplicate registrations, implicit shared names, relative URLs without
BaseAddress - Handlers:
DelegatingHandlercapturing scoped request data - Resilience / Polly: stacked handlers, unsafe-method retries, unsafe-method hedging, custom pipelines retrying unsafe methods, per-request pipeline construction
- Response ownership: undisposed
ResponseHeadersReadresponses and content streams - Correctness: unchecked failure responses, shared
DefaultRequestHeadersmutation, missingCancellationToken - Concurrency: sync-over-async and obvious unbounded
Task.WhenAllHTTP fan-out
dotnet add package HttpClient.Resilience.AnalyzersOr add an explicit package reference:
<PackageReference Include="HttpClient.Resilience.Analyzers" Version="0.1.183" PrivateAssets="all" />PrivateAssets="all" prevents the analyzer from flowing to projects that consume your project.
For a whole solution, add the package once in Directory.Build.props:
<Project>
<ItemGroup>
<PackageReference Include="HttpClient.Resilience.Analyzers" Version="0.1.183">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>Product-flow diagrams derived from the real showcase sample diagnostics (not stock IDE screenshots):
- Install the package (snippet above).
- Build normally — no service registration or runtime configuration is required.
dotnet build- Review or fix a warning. For example, a typed client can send a non-idempotent
POSTthrough the standard retry pipeline:
services.AddHttpClient<PaymentsClient>()
.AddStandardResilienceHandler();
public sealed class PaymentsClient(HttpClient httpClient)
{
public Task<HttpResponseMessage> CreateAsync(
CancellationToken cancellationToken) =>
httpClient.PostAsync("/payments", null, cancellationToken);
}HCR041 reports the retry risk. HCR042 reports the same class of incident for AddStandardHedgingHandler(), which can replay the request concurrently instead of after a failure. HCR043 reports it when a custom AddResilienceHandler pipeline calls AddRetry. If unsafe methods are not deliberately idempotent, disable their retries:
services.AddHttpClient<PaymentsClient>()
.AddStandardResilienceHandler(options =>
{
options.Retry.DisableForUnsafeHttpMethods();
});Several rules include automatic code fixes; every diagnostic links to a rule page explaining the risk, detection scope, safe alternatives, and suppression guidance.
| Area | Examples of detected risk |
|---|---|
| Client lifetime | Per-request HttpClient creation, stale long-lived connections, cached factory clients |
| Dependency injection | Typed clients held by singletons, duplicate registrations, scoped state captured by handlers |
| Resilience and Polly | Duplicate handlers, unsafe-method retries, concurrent hedging of unsafe methods, custom pipelines retrying unsafe methods |
| Response ownership | Undisposed ResponseHeadersRead responses and HTTP content streams |
| Request correctness | Unchecked failure responses, shared default-header mutation, missing cancellation |
| Async and concurrency | Sync-over-async and obvious unbounded HTTP fan-out |
| Typed and named clients | Relative URLs without BaseAddress, duplicated string client names, and implicit-name configuration collisions |
The rules intentionally focus on concrete production risks. Heuristic checks use a lower default severity, and deliberate exceptions can be configured per rule.
| Requirement | Notes |
|---|---|
| Analyzer TFM | netstandard2.0 analyzer assembly |
| Host projects | Modern .NET SDK / ASP.NET Core and class libraries using HttpClient patterns |
| Runtime dependency | None — development dependency only |
| IDE / CI | Diagnostics appear in supported IDEs and normal dotnet build / CI |
| Rule | Category | Detects | Default | Fix |
|---|---|---|---|---|
HCR001 |
Lifetime | Creating and disposing HttpClient in request paths |
Warning | Partial |
HCR002 |
Lifetime | Long-lived manual clients without PooledConnectionLifetime |
Warning | Yes |
HCR003 |
Lifetime | Cached IHttpClientFactory.CreateClient() results |
Warning | Guide |
HCR004 |
Typed clients | Typed clients injected into singleton services | Warning | Yes |
HCR005 |
Typed clients | Duplicate typed-client registrations | Warning | Yes |
HCR020 |
Handlers | DelegatingHandler capturing scoped request data |
Warning | Guide |
HCR040 |
Resilience | Duplicate resilience handlers in one client pipeline | Warning | Yes |
HCR041 |
Resilience | Unsafe HTTP methods retried without explicit configuration | Warning | Yes |
HCR042 |
Resilience | Unsafe HTTP methods hedged without explicit configuration | Warning | Yes |
HCR043 |
Resilience | Custom pipelines retrying unsafe HTTP methods | Warning | Yes |
HCR060 |
Response lifetime | Undisposed ResponseHeadersRead responses |
Warning | Yes |
HCR061 |
Response lifetime | Response content read before checking success | Warning | Partial |
HCR062 |
Response lifetime | Per-request values written to DefaultRequestHeaders |
Warning | Guide |
HCR063 |
Response lifetime | Sync-over-async around outbound HTTP | Warning | Partial |
HCR064 |
Response lifetime | HTTP calls that omit an available CancellationToken |
Warning | Yes |
HCR080 |
Concurrency | Obvious unbounded Task.WhenAll HTTP fan-out |
Suggestion | Guide |
HCR081 |
Response lifetime | Undisposed streams returned from HTTP content | Warning | Partial |
HCR082 |
Resilience | Per-request resilience pipeline construction | Warning | Guide |
HCR083 |
Typed clients | Relative URLs used without a configured BaseAddress |
Warning | Guide |
HCR084 |
Named clients | Duplicated string literals for named-client names | Warning | Guide |
HCR085 |
Typed clients | Different implementations sharing one implicit client name | Warning | Partial |
See the rules index for categories and recommended rollout order, or open an individual rule for exact detection details and limitations.
Set any rule's severity in your repository's .editorconfig:
[*.cs]
dotnet_diagnostic.HCR041.severity = error
dotnet_diagnostic.HCR042.severity = error
dotnet_diagnostic.HCR043.severity = error
dotnet_diagnostic.HCR080.severity = suggestionSupported values include error, warning, suggestion, silent, and none. Prefer a targeted override over disabling the analyzer package.
The repository includes ready-made starting profiles:
| Profile | Best for |
|---|---|
default.editorconfig |
New services and teams ready to act on production-safety warnings |
brownfield-adoption.editorconfig |
Existing applications that need a lower-noise first pass |
strict-ci.editorconfig |
Clean repositories that want production-safety findings to fail CI |
library-author.editorconfig |
Libraries with stricter response and stream ownership requirements |
For one intentional exception, keep the reason next to the code:
#pragma warning disable HCR041 // The endpoint enforces idempotency keys, so retries are safe.
services.AddHttpClient<PaymentsClient>()
.AddStandardResilienceHandler();
#pragma warning restore HCR041See configuration, adoption, and the false-positive policy for team rollout guidance.
- Install the package and build without changing CI gates.
- For an established codebase, begin with the brownfield profile.
- Fix high-confidence findings in critical outbound paths first.
- Suppress deliberate exceptions narrowly and record why they are safe.
- Move to the default profile, then strict CI, once the baseline is clean.
No. It statically checks how your code uses HttpClient, IHttpClientFactory, Polly, and the .NET resilience libraries. It does not add or replace a runtime resilience pipeline.
No. The analyzer assembly runs at compile time and the package contains no application runtime dependency.
Yes. Configure dotnet_diagnostic.HCRxxx.severity in .editorconfig. Each rule page also explains when a narrow suppression may be appropriate.
Check the rule's documented detection scope, reduce its severity if needed, and open an issue with a minimal reproduction. The project's false-positive policy treats diagnostic trust as a release requirement.
- Documentation site
- Getting started
- All analyzer rules
- Configuration
- Adoption
- FAQ
- Implementation status and limitations
- Contributing
- Code of Conduct
- Support
- Security policy
- MIT license
Contributions and focused bug reports are welcome. Please include the rule ID and a minimal C# reproduction when reporting an analyzer false positive or false negative.
