loading...
Cover image for .NET Core: Use HttpClientFactory and Polly to build rock solid services

.NET Core: Use HttpClientFactory and Polly to build rock solid services

rickystam profile image Ricky Stam ・4 min read

In this post I will explain what is HttpClientFactory and Polly retry policies and why you should use them in your next project.

Let's start with IHttpClientFactory

A lot of clients call me to help them resolve issues regarding sudden and random request failures. They put a new service in production, it works fine for the first few days, but out the the blue they start seeing a lot of SocketException in their Application Insights.

Most of the time these failures are the result of wrong usage of HttpClient that is causing socket exhaustion.

HttpClient is intended to be instantiated once and reused throughout the life of an application. Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads.

So please avoid using code like this:

using (var httpClient = new HttpClient())
{
    var result = await httpClient.GetAsync("https://www.bing.com");
}

You can read more details about this issue here:
https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

So, in order to mitigate this problem the .NET Core team introduced in .NET Core 2.1 the IHttpClientFactory that provides the following benefits:

  1. Provides a central location for naming and configuring logical HttpClient objects. For example, you may configure a client (Service Agent) that's pre-configured to access a specific microservice.

  2. Codify the concept of outgoing middleware via delegating handlers in HttpClient and implementing Polly-based middleware to take advantage of Polly's policies for resiliency.

  3. HttpClient already has the concept of delegating handlers that could be linked together for outgoing HTTP requests. You can register HTTP clients into the factory and you can use a Polly handler to use Polly policies for Retry, CircuitBreakers, and so on.

  4. Manage the lifetime of HttpMessageHandler to avoid the mentioned problems/issues that can occur when managing HttpClient lifetimes yourself.

So let's see a very simple example that leverages IHttpClientFactory.
In my example, I will use a very cool and free API the SuperHero (you can find many more free APIs to play around at https://apilist.fun/).

In a new Web API project, go to Startup.cs change ConfigureServices method like this:

public void ConfigureServices(IServiceCollection services)
{
    // the AddHttpClient() will provide us with an instance of HttpClient
    // available for Dependancy Injection in our services
    services.AddHttpClient<ISuperHeroService, SuperHeroService>(o =>                                    
                          o.BaseAddress = new Uri(Configuration["SuperHeroApiConfig:BaseUrl"]));

    services.AddControllers();
}

Let's create now our SuperHeroService:

public class SuperHeroService : ISuperHeroService
{

    private readonly HttpClient _httpClient;
    private readonly ISuperHeroApiConfig _superHeroApiConfig;

    public SuperHeroService(HttpClient httpClient, ISuperHeroApiConfig superHeroApiConfig)
    {
         _httpClient = httpClient;
         _superHeroApiConfig = superHeroApiConfig;
    }

     public async Task<PowerStats> GetPowerStats(int id)
     {
         return await _httpClient.GetFromJsonAsync<PowerStats>(
                $"{_superHeroApiConfig.AccessToken}/{id}/powerstats");
     }        
}

Last but not least let's create our SuperHeroController:

[ApiController]
[Route("api/[controller]")]
public class SuperHeroController : ControllerBase
{
    private readonly ILogger<SuperHeroController> _logger;
    private readonly ISuperHeroService _superHeroService;

    public SuperHeroController(ILogger<SuperHeroController> logger, ISuperHeroService superHeroService)
    {
        _logger = logger;
        _superHeroService = superHeroService;
    }

     [HttpGet("GetPowerStats/{id}")]
     public async Task<ActionResult> GetPowerStats(int id)
     {
         var result = await _superHeroService.GetPowerStats(id);

         return Ok(result);
     }       
}

You can use Postman or any other app to test your Super Hero service:

Postman example

Now that we saw how easy it is to overcome the old HttpClient issues with IHttpClientFactory, let's make our Super Hero service more resilient to failures using Polly.

Polly

Polly is a .NET resilience and transient-fault-handling library that allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

I think most of us, at some point in time, we saw code like this, trying to implement some kind of retry logic.

public async Task<PowerStats> GetPowerStats(int id)
{
      var currentRetry = 0;

       while (true)
       {
            try
            {
                return await _httpClient.GetFromJsonAsync<PowerStats>(
                                $"{_superHeroApiConfig.AccessToken}/{id}/powerstats");

             }
             catch (Exception ex)
             {
                currentRetry++;

                 if (currentRetry > 3)
                 {
                     throw;
                 }
            }
        }
}

However, we can all agree that this approach is not clean and easily maintainable, especially in a big project with many different services and endpoints.

Polly comes to the rescue!

Step 1: Add the Polly nuget pachage Microsoft.Extensions.Http.Polly.

Step 2: Create your custom policy inside ConfigureServices method of Startup.cs.

// Create the retry policy we want
var retryPolicy = HttpPolicyExtensions
                .HandleTransientHttpError() // HttpRequestException, 5XX and 408
                .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(retryAttempt));

Step 3: Apply it to our SuperHeroService, it's super easy!

services.AddHttpClient<ISuperHeroService, SuperHeroService>(o => 
                            o.BaseAddress = new Uri(Configuration["SuperHeroApiConfig:BaseUrl"]))
                .AddPolicyHandler(retryPolicy);

So what will happen now when our SuperHeroService gets a TransientHttpError (HttpRequestException, 5XX, 408) ?

Our HttpClient will retry the request.
The 1st time it will wait 1 second and retry.
The 2nd time it will wait 2 seconds and retry.
The 3rd and last time it will wait 3 seconds and retry.
If it fails again it will not retry and just return.

This is a very simple example just to demonstrate basic Polly usage, but you can do many more things.
You can tailor your retry policies as you see fit, apply Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback with just a few lines of code.

Now you have the knowledge to build a rock solid service, use it!

You can find the full code on my GitHub

This post was written with love ❤️

Posted on May 25 by:

rickystam profile

Ricky Stam

@rickystam

Technical Consultant @Microsoft. I love to work with C# and .NET but also enjoy writing JavaScript and Angular. Opinions are my own.

Discussion

markdown guide
 

Though Polly provides it out-of-the-box with reasonable defaults, you don't need 3rd party libraries to reuse retry logic with HttpClient.
Just extract it to DelegatingHandler

public class RetryHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        /* your retry logic ... */
    }
}

and then add it to DI:

services.AddHttpClient(/*...*/).AddHttpMessageHandler<RetryHandler>();
 

Hi Dmitry,

Thank you very much for your comment, it is a very nice addition to the post.
It is always nice to have more options on the table!

 
 

Thank you Antonio! Glad you like it :)