Polly, HttpClientFactory and the Policy Registry in a console application

Full source code available here.

How to use the HttpClientFactory with a console application is not immediately obvious. I thought it would be a simple matter, but it’s not because it relies on the dependency injection infrastructure you get with a web application. I’ve written about using HttpClientFactory with Polly in a Web Api here.

The easiest way to use HttpClientFactory within a console application is inside a HostBuilder. This gives you access to the services collection, now everything is easy.

Start with a standard console application, if you’re wondering about the async Task on my Main method, this was introduced in C# 7.1.

static async Task Main(string[] args)
{
    var builder = new HostBuilder()
        .ConfigureServices((hostContext, services) =>
        {

Inside the ConfigureServices, we configure the HttpClientFactory in the same way I showed in my previous post. You can also configure other things like logging, configuration sources, more DI, etc.

But first off, I’m going to add a Polly registry –

IPolicyRegistry<string> registry = services.AddPolicyRegistry();
	
IAsyncPolicy<HttpResponseMessage> httWaitAndpRetryPolicy =
    Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
        .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(retryAttempt));

registry.Add("SimpleWaitAndRetryPolicy", httWaitAndpRetryPolicy);

IAsyncPolicy<HttpResponseMessage> noOpPolicy = Policy.NoOpAsync()
    .AsAsyncPolicy<HttpResponseMessage>();

registry.Add("NoOpPolicy", noOpPolicy);

Then add the HttpClientFactory, passing in the lambda to pick the right policy based on the HTTP verb.

services.AddHttpClient("JsonplaceholderClient", client =>
{
    client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
}).AddPolicyHandlerFromRegistry((policyRegistry, httpRequestMessage) =>
{
    if (httpRequestMessage.Method == HttpMethod.Get || httpRequestMessage.Method == HttpMethod.Delete)
    {
        return policyRegistry.Get<IAsyncPolicy<HttpResponseMessage>>("SimpleWaitAndRetryPolicy");
    }
    return policyRegistry.Get<IAsyncPolicy<HttpResponseMessage>>("NoOpPolicy");
});

Next, add the hosted service we want to start.

services.AddSingleton<IHostedService, BusinessService>();

A hosted service is a class that implements IHostedService, more on this below.

Finally at the end of the the Main method, start the hosted service.

await builder.RunConsoleAsync();

For clarity, here is the full listing of the main method –

static async Task Main(string[] args)
{
    var builder = new HostBuilder()
        .ConfigureServices((hostContext, services) =>
        {
            IPolicyRegistry<string> registry = services.AddPolicyRegistry();

            IAsyncPolicy<HttpResponseMessage> httWaitAndpRetryPolicy =
                Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
                    .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(retryAttempt));

            registry.Add("SimpleWaitAndRetryPolicy", httWaitAndpRetryPolicy);

            IAsyncPolicy<HttpResponseMessage> noOpPolicy = Policy.NoOpAsync()
                .AsAsyncPolicy<HttpResponseMessage>();

            registry.Add("NoOpPolicy", noOpPolicy);

            services.AddHttpClient("JsonplaceholderClient", client =>
            {
                client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com");
                client.DefaultRequestHeaders.Add("Accept", "application/json");
            }).AddPolicyHandlerFromRegistry((policyRegistry, httpRequestMessage) =>
            {
                if (httpRequestMessage.Method == HttpMethod.Get || httpRequestMessage.Method == HttpMethod.Delete)
                {
                    return policyRegistry.Get<IAsyncPolicy<HttpResponseMessage>>("SimpleWaitAndRetryPolicy");
                }
                return policyRegistry.Get<IAsyncPolicy<HttpResponseMessage>>("NoOpPolicy");
            });
                
            services.AddSingleton<IHostedService, BusinessService>();
        });

    await builder.RunConsoleAsync();
}

The hosted service
The hosted service is where you put your business logic, it is a simple c# class that implements IHostedService giving it two methods, StartAsync and StopAsync.

Its constructor takes an IHttpClientFactory as a parameter, which is satisfied by the dependency injection infrastructure.

public BusinessService(IHttpClientFactory httpClientFactory)
{
    _httpClientFactory = httpClientFactory;
}

From StartAsync, you can do anything you need.

In this example I call another method which in turn uses the HttpClientFactory to get an instance of a HttpClient to make requests to the the remote server. The requests are executed inside the appropriate Polly policy.

public async Task StartAsync(CancellationToken cancellationToken)
{
    await MakeRequestsToRemoteService();
}

public async Task MakeRequestsToRemoteService()
{
    HttpClient httpClient = _httpClientFactory.CreateClient("JsonplaceholderClient");
    var response = await httpClient.GetAsync("/photos/1");
    Photo photo = await response.Content.ReadAsAsync<Photo>();
    Console.WriteLine(photo);
}

Full source code available here.

Polly, HttpClientFactory and the Policy Registry – choosing the right policy based on the HTTP request

Full source code available here.

The release of .NET Core 2.1 has made using HttpClient much easier. If you have been using HttpClient for a while you will know about its limitations around reuse and DNS expiry, I wrote about this some time ago. The HttpClientFactory takes care of the problems addressed in that post.

Along with those improvements the HttpClientFactory now makes it very easy to add Polly policies that will be executed whenever you create a HttpClient with the factory. This means you define the polices in one place, add them to the factory and forget about them! No more code is needed to add resilience to each of your calls. The factory takes care of creating the client and applying the policy, you can’t even tell that Polly is protecting your call where you make it.

For more on the HttpClientFactory check out Steve Gordon’s series of articles.

A simple example
Here’s a simple example of the HttpClientFactory in use. This adds the factory to our dependency injection container, sets the base address of the remote server and lets us configure the client. It also adds a simple retry policy that checks if the response is NOT a success code and retries up to three times.

services.AddHttpClient("OrderApiServer", client =>
{
    client.BaseAddress = new Uri("http://localhost:57696/api/");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
}).AddPolicyHandler(Policy.HandleResult<HttpResponseMessage>
    (r => !r.IsSuccessStatusCode).RetryAsync(3));

When you create a HttpClient now, it will include the policy.

var httpClient = _httpClientFactory.CreateClient("OrderApiServer");

And when you execute a request it the policy will retry up to three times if necessary.

var result = await httpClient.GetAsync("api/SomeEndpoint);

This might be great if all you are going to is perform GET’s and every call you make is safe and idempotent. But what if you want to use GET, PUT, POST and DELETE, you won’t want to retry all of those requests?

This is easy too. You add a policy registry will all the policies you want to use. Say, a retry policy, a wait and retry policy and no op policy, and provide a selector method to pick the right one based on the HTTP verb (or even the endpoint you are requesting).

Using a Policy Registry with the HttpClientFactory

Step 1

Add the policy registry to the Service Collection and add the policies to the registry.

public void ConfigureServices(IServiceCollection services)
{
    IPolicyRegistry<string> registry = services.AddPolicyRegistry(); // creates the registry and adds it to the service collection

    IAsyncPolicy<HttpResponseMessage> httpRetryPolicy =
        Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
            .RetryAsync(3);

    registry.Add("SimpleHttpRetryPolicy", httpRetryPolicy);

    IAsyncPolicy<HttpResponseMessage> httWaitAndpRetryPolicy =
        Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
            .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(retryAttempt));

    registry.Add("SimpleWaitAndRetryPolicy", httWaitAndpRetryPolicy);

    IAsyncPolicy<HttpResponseMessage> noOpPolicy = Policy.NoOpAsync()
        .AsAsyncPolicy<HttpResponseMessage>();

    registry.Add("NoOpPolicy", noOpPolicy);
    //snip..

Step 2
Now that we have the three polices and the registry, lets add the HttpClientFactory to the Service Collection.

services.AddHttpClient("OrderApiServer", client =>
{
    client.BaseAddress = new Uri("http://localhost:57696/api/");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
}).AddPolicyHandlerFromRegistry(PolicySelector);

Note the PolicySelector, this is the method I use to choose the right policy for each request.

Step 3
This method is simple, it takes the registry and the HttpRequest as arguments and grabs the policy from the registry based on the HTTP verb.

private IAsyncPolicy<HttpResponseMessage> PolicySelector(IReadOnlyPolicyRegistry<string> policyRegistry, HttpRequestMessage httpRequestMessage)
{
    if (httpRequestMessage.Method == HttpMethod.Get)
    {
        return policyRegistry.Get<IAsyncPolicy<HttpResponseMessage>>("SimpleHttpRetryPolicy");
    }
    else if (httpRequestMessage.Method == HttpMethod.Post)
    {
        return policyRegistry.Get<IAsyncPolicy<HttpResponseMessage>>("NoOpPolicy");
    }
    else
    {
        return policyRegistry.Get<IAsyncPolicy<HttpResponseMessage>>("SimpleWaitAndRetryPolicy");
    }
}         

You could use a lambda instead of a method when creating the HttpClientFactory. For example if if you had two policies to choose from –

.AddPolicyHandlerFromRegistry((policyRegistry, message) => policyRegistry.Get<IAsyncPolicy<HttpResponseMessage>>(message.Method == HttpMethod.Get ? "SimpleHttpRetryPolicy" : "NoOpPolicy"));

That’s the hard work done.

Step 4
Let’s take a look at the controller.

public class OrderController : ControllerBase
{
    private readonly IHttpClientFactory _httpClientFactory;

    public OrderController(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }


    [HttpGet("{orderId}")]
    public async Task<ActionResult> Get(int orderId)
    {
        //snip
        string requestEndpoint = $"invoice/{orderId}";

        var httpClient = _httpClientFactory.CreateClient("OrderApiServer");

        HttpResponseMessage response = await httpClient.GetAsync(requestEndpoint);

		//handle response
    }	
}

The only thing I’m passing into the constructor is the HttpClientFactory, I don’t have any Polly using statements.

In the GET method you can’t even tell that there is a policy around the HttpClient.GetAsync(..)

All the Polly work is done in the Startup.cs, the policies are defined, added to the registry, the registry is added to the HttpClientFactory and the HttpClientFactory is added to the services collections.

Now all your HttpClient requests are executed inside a policy.

For more on this, check out Dylan’s Polly and HttpClientFactory documentation.

Full source code available here.