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.

5 thoughts on “Polly, HttpClientFactory and the Policy Registry in a console application

  1. Great post. One thing to note — when I implemented this, I had a Polly PolicyRegistry instance defined elsewhere and I was having a problem registering an instance of it with IServiceCollection.AddPolicyRegistry(myRegistry) — the type could not be resolved with DI.

    As it turns out, the mistake I made before reading this article was registering as the Polly concrete type of PolicyRegistry, which will not work. Instead, make sure to type the registry as IPolicyRegistry.

  2. Hi Bryan,

    I want to implement HttpClientFactory in my console application to add resilient policies to handle HTTP 429 errors. My application is a Console application where I am using StructureMap as an IoC solution, currently I am registering a HttpClient instance directly in the IoC container. Unfortunately it isn’t clear how I can leverage HttpClientFactory in my application because of the added overhead of using a HostBuilder. My Application doesn’t use HostBuilder and doesn’t use the DI functionality provided by the ServicesCollection.

    I want to know how best I can integrate HttpClientFactory in my application, are there any other options to use HttpClientFactory without dependency on HostBuilder?
    I don’t want to have to use the DI functionality MS provides as default in .NET Core mainly because of flexibility and the fact that I like the way StructureMap works. Any suggestions you have would be much appreciated!

    Thanks

    James Kibirige

  3. Hi James,

    You are not strictly tied to using the HostBuilder approach, but the HttpClientFactory does rely on the Microsoft Dependency Injection for initial registration at least. This is untested but here is a Gist that I believe should work.

    https://gist.github.com/stevejgordon/51ea36cef5ebcdc7f9d10c409ef00770

    It uses the Microsoft DI manually to register the HttpClientFactory feature, then passes that collection into the StructureMap configuration.

    I hope that helps.
    Cheers,
    Steve

Leave a Reply

Your email address will not be published. Required fields are marked *