DEV Community

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

Posted on

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

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");
}


Enter fullscreen mode Exit fullscreen mode

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();
}


Enter fullscreen mode Exit fullscreen mode

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");
     }        
}


Enter fullscreen mode Exit fullscreen mode

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);
     }       
}


Enter fullscreen mode Exit fullscreen mode

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;
                 }
            }
        }
}


Enter fullscreen mode Exit fullscreen mode

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));


Enter fullscreen mode Exit fullscreen mode

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);


Enter fullscreen mode Exit fullscreen mode

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 ❤️

Top comments (6)

Collapse
 
dimaaan profile image
Dmitry Anshilevich • Edited

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 ... */
    }
}
Enter fullscreen mode Exit fullscreen mode

and then add it to DI:

services.AddHttpClient(/*...*/).AddHttpMessageHandler<RetryHandler>();
Enter fullscreen mode Exit fullscreen mode
Collapse
 
dlidstrom profile image
Daniel Lidström

One advantage with Polly is that you can also enable ChaosMonkey (Simmy in Polly terms). I do this for all Polly policies and activate it for development only. Great way to test your resilience.

Collapse
 
rickystam profile image
Ricky Stam

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!

Collapse
 
antdimot profile image
Antonio Di Motta

It's a very useful post.

Collapse
 
rickystam profile image
Ricky Stam

Thank you Antonio! Glad you like it :)

Collapse
 
harmyder profile image
Harmyder

How do I test SuperHeroService if it accepts HttpClient concrete class?