Search code examples
c#.netdotnet-httpclientpolly

C# Polly.Core choose pipeline based on Http request URI


in the old version of Polly, I used this function to get a policy based on the request URI:

private static IAsyncPolicy<HttpResponseMessage> PolicySelector(IReadOnlyPolicyRegistry<string> policyRegistry,
    HttpRequestMessage httpRequestMessage)
{
    Guard.Against.Null(httpRequestMessage);
    Guard.Against.Null(httpRequestMessage.RequestUri);

    if (httpRequestMessage.RequestUri.AbsoluteUri.Contains(_consentsSettings.GetConsentsRequestUri))
    {
        return policyRegistry.Get<IAsyncPolicy<HttpResponseMessage>>(Constants.PollyPipelineKey.GetConsents);
    }

    if (httpRequestMessage.RequestUri.AbsoluteUri.Contains(_consentsSettings.SetConsentsRequestUri))
    {
        return Policy.NoOpAsync<HttpResponseMessage>();
    }

    throw new InvalidOperationException("Polly policy for consents not found");
}

and on my HttpClient, I called this with the following code:

services.AddHttpClient(ConsentsService.ConsentsClientName, client =>
{
    client.BaseAddress = new Uri(settings.BaseUri);
})
.AddPolicyHandlerFromRegistry(PolicySelector);

Now, I would like to use the new Polly.Core package. I have this code:

services.AddHttpClient(ConsentsService.ConsentsClientName, client =>
{
    client.BaseAddress = new Uri(settings.BaseUri);
})
.AddResilienceHandler(Constants.PollyPipelineKey.GetConsents, (builder, context) =>
{
    var factory = context.ServiceProvider.GetRequiredService<IHttpPollyPipelineFactory>();
    var pipeline = factory.CreateResiliencePipeline();

    builder.AddPipeline(pipeline);
})

But I don't know if it is possible to apply this resilience handler only when calling a specific URI, or if the only solution is to check the URI before the client call to PostAsync and wrap it with the pipeline's ExecuteAsync method.I hope I was clear. Thanks


Solution

  • At the time of writing, there is no AddResilienceHandler overload which allows you to access the HttpRequestMessage. So, currently you can't do the exact same branching during the pipeline registration like what you did in case of Polly V7.

    BUT, there are two things worth mentioning.

    SelectPipelineByAuthority

    There is an extension method called SelectPipelineByAuthority. This method caches and assigns pipeline for each authority (schema + protocol + port). This feature was designed mainly for circuit breakers but can be used elsewhere as well.

    Here we have documented how to use it.

    Multiple named clients

    You could register multiple named clients. One which is decorated with your resilience pipeline the other without it.

    Here the key thing is to find meaningful and expressive names to ease the selection of the proper client for given use cases.


    UPDATE #1

    There is a workaround. Since 8.2.0 the Microsoft.Extensions.Http.Resilience package exposes the ResilienceHandler class, which was internal before this release.

    This class has a ctor which allows you to access the request and define which pipeline you want to use. With that you can do something like this:

    services
        .AddHttpClient(ConsentsService.ConsentsClientName, client => { ... })
        .AddHttpMessageHandler(sp => new ResilienceHandler(req => YourPipelineSelector(sp, req)));
    

    The NoOp equivalent for V8 is the ResiliencePipeline.Empty.