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)
2{
3 var builder = new HostBuilder()
4 .ConfigureServices((hostContext, services) =>
5 {
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();
2
3IAsyncPolicy<HttpResponseMessage> httWaitAndpRetryPolicy =
4 Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
5 .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(retryAttempt));
6
7registry.Add("SimpleWaitAndRetryPolicy", httWaitAndpRetryPolicy);
8
9IAsyncPolicy<HttpResponseMessage> noOpPolicy = Policy.NoOpAsync()
10 .AsAsyncPolicy<HttpResponseMessage>();
11
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 =>
2{
3 client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com");
4 client.DefaultRequestHeaders.Add("Accept", "application/json");
5}).AddPolicyHandlerFromRegistry((policyRegistry, httpRequestMessage) =>
6{
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");
12});
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)
2{
3 var builder = new HostBuilder()
4 .ConfigureServices((hostContext, services) =>
5 {
6 IPolicyRegistry<string> registry = services.AddPolicyRegistry();
7
8 IAsyncPolicy<HttpResponseMessage> httWaitAndpRetryPolicy =
9 Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
10 .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(retryAttempt));
11
12 registry.Add("SimpleWaitAndRetryPolicy", httWaitAndpRetryPolicy);
13
14 IAsyncPolicy<HttpResponseMessage> noOpPolicy = Policy.NoOpAsync()
15 .AsAsyncPolicy<HttpResponseMessage>();
16
17 registry.Add("NoOpPolicy", noOpPolicy);
18
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 });
31
32 services.AddSingleton<IHostedService, BusinessService>();
33 });
34
35 await builder.RunConsoleAsync();
36}
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)
2{
3 _httpClientFactory = httpClientFactory;
4}
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)
2{
3 await MakeRequestsToRemoteService();
4}
5
6public async Task MakeRequestsToRemoteService()
7{
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);
12}
Full source code available here.