HTTP resilience wrapper for Polly

March 28th 2025 Polly Dependency Injection .NET

In response to my blog post about client rate limiting with Polly, a colleague brought Microsoft's HTTP resilience NuGet package to my attention. When I took a closer look at it, I was surprised that it actually depended on Polly. This made me even more curious. I wanted to see what benefits it brought over using Polly directly.

The HTTP resilience package builds on top of the recommended practice for using the HTTP client via the HTTP client factory. So, for a meaningful comparison with Polly, I had to use dependency injection in my sample code.

Of course, Polly supports dependency injection. However, when using it in combination with the HTTP client, you have to register it independently of the HTTP client (be careful to not also register the target service for HTTP client injection):

services.AddHttpClient<MyService>(client =>
{
    client.BaseAddress = new Uri(server.Urls[0]);
});
services.AddResiliencePipeline(
    RATE_LIMITING_PIPELINE_KEY,
    (builder, context) =>
    {
        builder.AddRateLimiter(
            new SlidingWindowRateLimiter(
                new SlidingWindowRateLimiterOptions
                {
                    PermitLimit = MAX_REQUESTS_PER_SECOND,
                    SegmentsPerWindow = 10,
                    Window = TimeSpan.FromSeconds(1)
                }
            )
        );
    }
);

And you also have to inject it into your service as a separate dependency, referring to its key. You can do this in one of two ways:

  • by injecting the ResiliencePipelineProvider and retrieving the pipeline from it:
    public class MyService(
        HttpClient httpClient,
        ResiliencePipelineProvider<string> pipelineProvider
    )
    {
        private readonly ResiliencePipeline rateLimitingPipeline = pipelineProvider
              .GetPipeline(RATE_LIMITING_PIPELINE_KEY);
    }
    
  • by injecting the pipeline directly using keyed services:
    public class MyService(
        HttpClient httpClient,
        [FromKeyedServices(RATE_LIMITING_PIPELINE_KEY)]
        ResiliencePipeline rateLimitingPipeline
    ) { }
    

Either way, the service code has to be aware of the pipeline to take advantage of its resilience configuration:

public async Task<string> GetAsync()
{
    return await rateLimitingPipeline.ExecuteAsync(async cancellationToken =>
        await httpClient.GetStringAsync("", cancellationToken)
    );
}

When using the HTTP resilience library, you configure the resilience pipeline as part of the HTTP client DI registration:

services
    .AddHttpClient<MyService>(client =>
    {
        client.BaseAddress = new Uri(server.Urls[0]);
    })
    .AddResilienceHandler(
        RATE_LIMITING_PIPELINE_KEY,
        builder =>
        {
            builder.AddRateLimiter(
                new SlidingWindowRateLimiter(
                    new SlidingWindowRateLimiterOptions
                    {
                        PermitLimit = MAX_REQUESTS_PER_SECOND,
                        SegmentsPerWindow = 10,
                        Window = TimeSpan.FromSeconds(1)
                    }
                )
            );
        }
    );

Still, this part of the code is almost identical to using Polly directly. The real difference is in the service using the pipeline:

  • The pipeline doesn't have to be injected as a separate dependency. You only have to inject the HTTP client:
    public class MyService(HttpClient httpClient) { }
    
  • The calling code doesn't have to be aware of the pipeline, either. It simply uses the HTTP client, as the resilience configuration is an integral a part of it:
    public async Task<string> GetAsync()
    {
        return await httpClient.GetStringAsync("");
    }
    

You can find a working sample project in my GitHub repository. It's based on my previous sample project for client-side rate limiting. The pipeline configuration is the same, as well as the test for it. But I'm using dependency injection to get the pipeline and the HTTP client in the calling code. This makes it easy to compare the code when using the HTTP resilience package and when using Polly directly without it.

The HTTP resilience wrapper is a good fit for simple cases when you only want to use a pipeline to add resilience to HTTP calls. It modifies the injected HTTP client transparently to the calling code, which is especially valuable when adding resilience to an existing codebase. When you have a more complex scenario, not supported by this wrapper, you can always fall back to using Polly directly. The two approaches can be used in parallel in the same project.

Get notified when a new blog post is published (usually every Friday):

Copyright
Creative Commons License