Polly, HttpClientFactory and the Policy Registry in a console application

Full source code available here.

Want to learn more about Polly? Check out my Pluralsight course on it.

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.

1static async Task Main(string[] args)
3    var builder = new HostBuilder()
4        .ConfigureServices((hostContext, services) =>
5        {
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 -

 1IPolicyRegistry<string> registry = services.AddPolicyRegistry();
 3IAsyncPolicy<HttpResponseMessage> httWaitAndpRetryPolicy =
 4    Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
 5        .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(retryAttempt));
 7registry.Add("SimpleWaitAndRetryPolicy", httWaitAndpRetryPolicy);
 9IAsyncPolicy<HttpResponseMessage> noOpPolicy = Policy.NoOpAsync()
10    .AsAsyncPolicy<HttpResponseMessage>();
12registry.Add("NoOpPolicy", noOpPolicy);

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

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

Next, add the hosted service we want to start.

1services.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.

1await builder.RunConsoleAsync();

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

 1static async Task Main(string[] args)
 3    var builder = new HostBuilder()
 4        .ConfigureServices((hostContext, services) =>
 5        {
 6            IPolicyRegistry<string> registry = services.AddPolicyRegistry();
 8            IAsyncPolicy<HttpResponseMessage> httWaitAndpRetryPolicy =
 9                Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
10                    .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(retryAttempt));
12            registry.Add("SimpleWaitAndRetryPolicy", httWaitAndpRetryPolicy);
14            IAsyncPolicy<HttpResponseMessage> noOpPolicy = Policy.NoOpAsync()
15                .AsAsyncPolicy<HttpResponseMessage>();
17            registry.Add("NoOpPolicy", noOpPolicy);
19            services.AddHttpClient("JsonplaceholderClient", client =>
20            {
21                client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com");
22                client.DefaultRequestHeaders.Add("Accept", "application/json");
23            }).AddPolicyHandlerFromRegistry((policyRegistry, httpRequestMessage) =>
24            {
25                if (httpRequestMessage.Method == HttpMethod.Get || httpRequestMessage.Method == HttpMethod.Delete)
26                {
27                    return policyRegistry.Get<IAsyncPolicy<HttpResponseMessage>>("SimpleWaitAndRetryPolicy");
28                }
29                return policyRegistry.Get<IAsyncPolicy<HttpResponseMessage>>("NoOpPolicy");
30            });
32            services.AddSingleton<IHostedService, BusinessService>();
33        });
35    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.

1public BusinessService(IHttpClientFactory httpClientFactory)
3    _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.

 1public async Task StartAsync(CancellationToken cancellationToken)
 3    await MakeRequestsToRemoteService();
 6public async Task MakeRequestsToRemoteService()
 8    HttpClient httpClient = _httpClientFactory.CreateClient("JsonplaceholderClient");
 9    var response = await httpClient.GetAsync("/photos/1");
10    Photo photo = await response.Content.ReadAsAsync<Photo>();
11    Console.WriteLine(photo);
Full source code available here.

comments powered by Disqus