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.