2

I'm currently using Polly and Flurl together, but I have a common retry policy that I have to add to every request. I notice that Polly allows you to set a default using AddPolicyHandler(...) but this requires an IHttpClientBuilder and I can't see any way of getting hold of this from Flurl.

I thought overloading DefaultHttpClientFactory might be the way to go, but that only gives me access to the HttpClient, not the IHttpClientBuilder.

I know I could make my own HttpClients and pass them into Flurl, but I'd rather avoid that if I can as I'd like Flurl to manage their lifecycle.

Is there currently a way of doing what I want to do?

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Iain Brown
  • 1,079
  • 3
  • 11
  • 23

1 Answers1

9

Great question. Flurl gives you all the necessary hooks to do this. First define a DelegatingHandler that takes a Polly policy:

public class PollyHandler : DelegatingHandler
{
    private readonly IAsyncPolicy<HttpResponseMessage> _policy;

    public PollyHandler(IAsyncPolicy<HttpResponseMessage> policy) {
        _policy = policy;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
        return _policy.ExecuteAsync(ct => base.SendAsync(request, ct), cancellationToken);
    }
}

Then create a custom IHttpClientFactory that returns your custom handler with the default handler as its InnerHandler:

public class PollyFactory : DefaultHttpClientFactory
{
    private readonly IAsyncPolicy<HttpResponseMessage> _policy;

    public PollyFactory(IAsyncPolicy<HttpResponseMessage> policy) {
        _policy = policy;
    }

    public override HttpMessageHandler CreateMessageHandler() {
        return new PollyHandler(_policy) {
            InnerHandler = base.CreateMessageHandler()
        };
    }
}

Finally, on app startup, define your policy and register it with Flurl:

var policy = Policy
    .Handle<HttpRequestException>()
    .OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
    .RetryAsync(5);

FlurlHttp.Configure(settings => settings.HttpClientFactory = new PollyFactory(policy));

One important note is that this approach will not work with a policy that handles FlurlHttpException. That's because you're intercepting calls at the HttpMessageHandler level here. Flurl converts responses and errors to FlurlHttpExceptions higher up the stack, so those won't get trapped/retried with this approach. The policy in the example above traps HttpRequestException and HttpResponseMessage (with non-2XX status codes), which will work.

Todd Menier
  • 37,557
  • 17
  • 150
  • 173
  • When is FlurlHttpException thrown vs HttpRequestException? – Robin Apr 15 '19 at 10:34
  • @Robin HttpRequestException is thrown inside any message handler you have configured, like above. `FlurlHttpException` is thrown by Flurl upon completion of any HTTP call, after those handlers have run. If you're doing try/catch within the normal flow of your app logic, `FlurlHttpException` is what you want to catch. – Todd Menier Apr 15 '19 at 21:17
  • How do I use it without the factory? This only calls it once `string s = await Policy .Handle () .OrResult (r => !r.IsSuccessStatusCode) .RetryAsync (5) .ExecuteAsync (() => { Console.WriteLine ("Retry"); return "http://127.0.0:7071/".GetAsync (); }) .ReceiveString () .ConfigureAwait (false);` – Robin Apr 16 '19 at 07:09
  • @Robin Please ask a new question. – Todd Menier Apr 16 '19 at 12:45
  • Good idea, the formatting doesn't work in comments , took over many edits and unable to format properly https://stackoverflow.com/questions/55718410/how-to-use-flurl-with-polly-without-factory – Robin Apr 17 '19 at 00:50
  • @ToddMenier According to Stephen Cleary it is not a good idea to use Polly inside the HttpClient: https://stackoverflow.com/questions/56769241/polly-waitandretryasync-hangs-after-one-retry – AndersBaumann Dec 15 '19 at 19:15