-1

I'm using a policy to retry a call in case it fails due to certain reason.

AsyncRetryPolicy<HttpResponseMessage> GetPolicy() => Policy
  .HandleResult<HttpResponseMessage>(a => a.StatusCode is HttpStatusCode.Poof or ...)
  .WaitAndRetryAsync(stubborness, bideTime, 
    async (response, timespan, context) => { ... });

It performs just as expected for GetAsync(...), PostAsync(...), PutAsync(...).

using HttpResponseMessage response = await RetryPolicy
  .ExecuteAsync(() => Client.PutAsync(url, content));

However, it doesn't work well with the general SendAsync(...). In case the call fails, the retry kicks in but then crashes reporting an exception that the request already has been dispatched and can't be reused. While using the verb specific methods, a new request is constructed each time. Using the general one and passing a custom, pre-created request, will use it, hence stumbling. An important detail is that I need to alter the headers for each call.

    HttpRequestMessage request = new(HttpMethod.Put, new Uri(url));
    request.Content = new StringContent(GetJson(code), Encoding.UTF8, "text/json");
    request.Headers.Add("tenant", GetId(code));

    using HttpResponseMessage response = await RetryPolicy
        .ExecuteAsync(() => Client.SendAsync(request));

I have tried to provide the call specific header for each call while using the specialized methods. That failed as I haven't found any overload doing that.

I have tried altering default headers on the client for each call. That failed due to asunchronicity and wrong header being using during certain calls.

I have tried contructing a specialized client with the ID as a default header. That failed due to the gianormous number of different clients I'd end up with.

I have tried creating a new request and swoop it in to the retry. That failed because I haven't found a reference to the HTTP context nor the old request anywhere in the WaitAndRetryAsync(...) handler.

Is there a way to dance around it?

I've seen articles suggesting that SendAsync(...) isn't the most suitable choice if one needs to tampter with headers. However, I'm stubborn as duck and want to determine whether it's possible, not only if it's smoothly possible.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Konrad Viltersten
  • 36,151
  • 76
  • 250
  • 438
  • Polly's natural way of working with `HttpClient` is *within* the handlers. Is there some reason you're not doing it the natural way? – Stephen Cleary Sep 08 '22 at 17:55
  • @StephenCleary The only reason for this approach is that we did a research and this seemed to be the examples we've found. So no, nothing technically specific. Could you elaborate on what you mean by *working within the handlers*? It's a bit fuzzy what it means in this specific case. – Konrad Viltersten Sep 08 '22 at 21:04
  • Like [this](https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/implement-http-call-retries-exponential-backoff-polly?WT.mc_id=DT-MVP-5000058) or [this](https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory) or [this](https://www.stevejgordon.co.uk/httpclientfactory-using-polly-for-transient-fault-handling). The idea is to insert Polly as a delegating handler inside the `HttpClent` pipeline instead of having Polly wrap the `HttpClient` calls. – Stephen Cleary Sep 08 '22 at 21:48
  • @StephenCleary I have tried the suggested approach and it leans towards working well, so it seems to resolve the issue I had. Fell free to post an answer to be accepted. That said, I haven't seen much discussions on the builder pattern being the natural way for Polly. I'm in no way objecting to your statement - only trying to grasp what makes it *natural*. Also, it's still unclear what to do if I get e.g. 500 in all the retries. Would the "natural" way be to throw? – Konrad Viltersten Sep 09 '22 at 05:58
  • Perhaps "natural" wasn't the best choice of wording. I meant an idea more like "built-in support". Regarding 500s, that's up to your policy. The default Polly policies for HttpClient do not retry 500s. – Stephen Cleary Sep 09 '22 at 14:10

1 Answers1

0

Whenever you are calling GetAsync, PostAsync or PutAsync these methods are creating each and every time a new HttpRequestMessage whenever you call them.

On the other hand whenever you call SendAsync then you need to specify an HttpRequestMessage as a parameter. There are a lots of questions on StackOverflow which details why you can't reuse an HttpRequestMessage multiple times.

And that's basically the root cause of your problem. You want to reuse the same HRM for multiple retry attempts.

There are several workarounds:

  • Copy the original HttpRequestMessage before you initiate a call (1)
  • Have a method which creates a new HttpRequestMessage for each SendAsync call (1, 2, 3)
  • Place the retry inside a DelegatingHandler (1)
    • By the way that's what the AddPolicyHandler does (1, 2, 3)
Peter Csala
  • 17,736
  • 16
  • 35
  • 75