| title | HCR065 — Do not send the same HttpRequestMessage more than once |
|---|---|
| description | An HttpRequestMessage can only be sent once; a second SendAsync throws InvalidOperationException. |
Do not send the same HttpRequestMessage more than once.
HttpClient.SendAsync marks the request message as sent. Sending the same instance again throws InvalidOperationException ("The request message was already sent") — a guaranteed crash. This is the canonical bug inside hand-rolled DelegatingHandler retry loops that call base.SendAsync(request, ...) on every iteration, and it also appears when a request local is reused across two sends in one method.
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
for (var attempt = 0; attempt < 3; attempt++)
{
var response = await base.SendAsync(request, cancellationToken);
if (response.IsSuccessStatusCode)
{
return response;
}
}
return response;
}var request = new HttpRequestMessage(HttpMethod.Post, "/orders");
await client.SendAsync(request, cancellationToken);
await client.SendAsync(request, cancellationToken); // throwsClone the request per attempt — the standard resilience handlers do exactly this:
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
for (var attempt = 0; attempt < 3; attempt++)
{
using var attemptRequest = await CloneRequestAsync(request, cancellationToken);
var response = await base.SendAsync(attemptRequest, cancellationToken);
if (response.IsSuccessStatusCode)
{
return response;
}
}
}Or prefer AddStandardResilienceHandler/AddResilienceHandler, which already implement retry with request cloning.
The implementation reports a second Send/SendAsync on the same HttpRequestMessage local or parameter within one block when the variable is not reassigned between the sends, and a send inside a loop when the request variable is not assigned a fresh instance inside the loop body. Receivers are validated with Roslyn type information (HttpClient, HttpMessageInvoker, HttpMessageHandler, DelegatingHandler), with a syntactic fallback for unresolved base.SendAsync overrides and visibly HttpClient-typed receivers. Reassigned variables, distinct request instances, and convenience methods (GetAsync, PostAsync, …) that build their own request are skipped.
Suppress only when the second send is provably unreachable (for example inside a branch that always returns first). Prefer cloning or a resilience handler over suppression.