Using Polly to retry HTTP requests in .net

2020-12-28

Polly is a .net library / SDK which can be used to perform policies on HTTP requests, for example, retrying a request until a specific response code is returned. The Polly GitHub Documentation on the capabilities of this library along with examples can be found on their Polly GitHub Documentation page.

I will implement a policy that waits 5 seconds and then retries a request. This will happen indefinitely and will timeout according to the infrastructure policies, e.g. if this were hosted as an Azure function the timeout will default to what is set in Azure. The current retry logic is based on the response status code and will retry when it receives a 200 status code.

  • 1. .net core 3.1
  • 2. Polly 7.2.1
  • 3. Http Extensions 5.0.0

This is based on using a API hosted using .net core's web api calling a down stream API. The first step is to install the relevant libraries, run the following command to install the Polly SDK and the HTTP extensions into the web api application.

dotnet add package Polly 
dotnet add package Microsoft.Extensions.Http

Create a new class called ServiceCollectionExtensions for applying the registration and configuration of the Polly policies within the application. This will use the policy registry which will allow you to create a factory of policies that can be retrieved by using their names. This code uses .net core's .net core HTTP client API to make the downstream request.

using System;
using System.net;
using System.net.Http;
using Microsoft.Extensions.DependencyInjection;
using Polly;
using Polly.Registry;

namespace WebApiWork
{
	public static class ServiceCollectionExtensions
	{
		public static IServiceCollection AddRetryPolicy(this IServiceCollection services)
		{
			const int retryIntervalInSeconds = 5;

			var retryPolicy = Policy
				.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.OK)
				.WaitAndRetryForeverAsync(retries =>
				{
					Console.WriteLine($"Request attempt: {retries}");
					return TimeSpan.FromSeconds(retryIntervalInSeconds);
				});

			var policyRegistry = new PolicyRegistry();
			policyRegistry.Add("InfiniteRetryPolicy", retryPolicy);
			return services.AddScoped<IReadOnlyPolicyRegistry<string>>(s => policyRegistry);
		}
	}
}

Next, we'll need to add the classes responsible for making the HTTP requests and utilise the Polly retry logic. For this we'll create the following classes: IBlogPostService.cs, BlogPostService.cs and Post.cs

Create a IBlogPostService.cs class with the following code:

using System.Collections.Generic;
using System.Threading.Tasks;

namespace WebApiWork
{
	public interface IBlogPostService
	{
		Task<IEnumerable<Post>> GetPosts();
	}
}

Next create a Post.cs class with the following

namespace WebApiWork
{
	public class Post
	{
		public string Heading { get; set; }
		public string Title { get; set; }
	}
}

Lastly, create the BlogPostService.cs class with the code below

using System.Collections.Generic;
using System.net.Http;
using System.Threading.Tasks;
using Polly;
using Polly.Registry;
using static System.Text.Json.JsonSerializer;

namespace WebApiWork
{
	public class BlogPostService : IBlogPostService
	{
		private readonly HttpClient _client;
		private readonly IAsyncPolicy<HttpResponseMessage> _retryPolicy;

		public BlogPostService(HttpClient client, IReadOnlyPolicyRegistry<string> policyRegistry)
		{
			_client = client;

			_retryPolicy = policyRegistry.Get<IAsyncPolicy<HttpResponseMessage>>("InfiniteRetryPolicy");
		}

		public async Task<IEnumerable<Post>> GetPosts()
		{
			var response = await _retryPolicy.ExecuteAsync(() => _client.GetAsync("api/posts"));

			var converted = await response.Content.ReadAsStringAsync();

			var posts = Deserialize<IEnumerable<Post>>(converted);

			return posts;
		}
	}
}

We now have the basis for implementing the retry Polly logic on our downstream API. The final step is to wire all this logic together in the Startup.cs file; update the ConfigureServices method to look like the following:

public void ConfigureServices(IServiceCollection services)
{
	services.AddControllers();
	services.AddSwaggerGen(c =>
	{
		c.SwaggerDoc("v1", new OpenApiInfo { Title = "webapi", Version = "v1" });
	});

	services.AddHttpClient<IBlogPostService, BlogPostService>(client =>
	{
		client.BaseAddress = new Uri(Configuration["BaseUrl"]);
	});

	services.AddRetryPolicy();
}

Once you've added an endpoint URI to the appsettings.json file you will be able to run the code and via the console logs view logs similar to

info: System.net.Http.HttpClient.IBlogPostService.LogicalHandler[101]
		End processing HTTP request after 8822.8192ms - 200
System.net.Http.HttpClient.IBlogPostService.LogicalHandler: Information: End processing HTTP request after 8822.8192ms - 200
Request attempt: 1