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:
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.
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.
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.
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:
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 ❤️
Top comments (6)
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
and then add it to DI:
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.
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!
It's a very useful post.
Thank you Antonio! Glad you like it :)
How do I test SuperHeroService if it accepts HttpClient concrete class?