Search code examples
c#pollyflurl

Set a default Polly policy with Flurl


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?


Solution

  • 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.