TL;DR: It has nothing to do with Polly.
Let's start with a single example which fails with an InvalidOperationException
at the second attempt.
var request = new HttpRequestMessage(HttpMethod.Get, "https://httpstat.us/500");
var httpClient = new HttpClient(new HttpClientHandler());
for(int i = 0; i < 3; i++)
{
var result = await httpClient.SendAsync(request);
result.StatusCode.Dump();
}
If we would move the request object creation inside the for
loop then everything would be fine. But let's keep it outside of the for
.
Let's create a simple delegating handler
public class RetryHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage result = null;
for(int i = 0; i < 3; i++)
{
result = await base.SendAsync(request, cancellationToken);
"Retried".Dump();
}
return result;
}
}
and let's use it
var handler = new RetryHandler();
handler.InnerHandler = new HttpClientHandler();
var httpClient = new HttpClient(handler);
var request = new HttpRequestMessage(HttpMethod.Get, "https://httpstat.us/500");
var result = await httpClient.SendAsync(request);
result.StatusCode.Dump();
This version won't throw any InvalidOperationException
.
So, why does the latter work and former doesn't?
The InvalidOperationException
is thrown by the CheckRequestMessage
method of the HttpClient
private static void CheckRequestMessage(HttpRequestMessage request)
{
if (!request.MarkAsSent())
{
throw new InvalidOperationException(SR.net_http_client_request_already_sent);
}
}
This method is being called by the CheckRequestBeforeSend
. And it is the very first command of the SendAsync
. This public SendAsync
calls its base's SendAsync
. Here the base
is a HttpMessageInvoker
which deals with the HttpMessageHandler
.
Long story short:
- If you want to reuse the
HttpRequestMessage
outside the HttpClient
then it will fail at the second attempt due to the CheckRequestMessage
call
- If you want to reuse the
HttpRequestMessage
inside a DelegatingHandler
(HttpMessageHandler
) then it won't fail because the HttpMessageInvoker
does not care about the reuse