My service definition:
var host = new HostBuilder().ConfigureServices(services =>
{
services
.AddHttpClient<Downloader>()
.AddPolicyHandler((services, request) =>
HttpPolicyExtensions
.HandleTransientHttpError()
.Or<SocketException>()
.Or<HttpRequestException>()
.WaitAndRetryAsync(
new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(10) },
onRetry: (outcome, timespan, retryAttempt, context) =>
{
Console.WriteLine($"Delaying {timespan}, retrying {retryAttempt}.");
}));
services.AddTransient<Downloader>();
}).Build();
Implementation of the Downloader
:
class Downloader
{
private HttpClient _client;
public Downloader(IHttpClientFactory factory)
{
_client = factory.CreateClient();
}
public Download()
{
await _client.GetAsync(new Uri("localhost:8800")); // A port that no application is listening
}
}
With this setup, I expect to see three attempts of querying the endpoint, with the logging message printed to the console (I've also unsuccessfully tried with a logger, using the console for simplicity here).
Instead of the debugging messages, I see the unhandled exception message (which I only expect to see after the retries and the printed logs).
Unhandled exception: System.Net.Http.HttpRequestException: No connection could be made because the target machine actively refused it. (127.0.0.1:8800) ---> System.Net.Sockets.SocketException (10061): No connection could be made because the target machine actively refused it.
You can register several different pre-configured HttpClient
s into the DI system:
HttpClient
which can be accessed through IHttpClientFactory
's Create
methodHttpClient
which can be accessed through either the ITypedHttpClientFactory
or through the wrapper interfaceHttpClient
which can be accessed through IHttpClientFactory
and ITypedHttpClientFactory
AddHttpClient
extension methodThis method tries to register Factories as singletons and Concrete types as transient objects. Here you can find the related source code. So, you don't need to register the concrete types either as Transient or as Scoped by yourself.
You can register a named client via the AddHttpClient
by providing a unique name
services.AddHttpClient("UniqueName", client => client.BaseAdress = ...);
You can access the registered client via the IHttpClientFactory
private readonly HttpClient uniqueClient;
public XYZService(IHttpClientFactory clientFactory)
=> uniqueClient = clientFactory.CreateClient("UniqueName");
CreateClient
call without nameIf you call the CreateClient
without name it will create a new HttpClient
which is not pre-configured. More precisely it is not pre-configured by you rather by the framework itself with some default setup.
That's the root cause of your issue, that you have created a HttpClient
which is not decorated with the policies.
You can register a typed client via the AddHttpClient<TClient>
or through the AddHttpClient<TClient, TImplementation>
overloads
services.AddHttpClient<UniqueClient>(client => client.BaseAdress = ...);
services.AddHttpClient<IUniqueClient, UniqueClient>(client => client.BaseAdress = ...);
The former can be accessed through the ITypedHttpClientFactory
private readonly UniqueClient uniqueClient;
public XYZService(ITypedHttpClientFactory<UniqueClient> clientFactory)
=> uniqueClient = clientFactory.CreateClient(new HttpClient());
The later can be accessed through the typed client's interface
private readonly IUniqueClient uniqueClient;
public XYZService(IUniqueClient client)
=> uniqueClient = client;
The implementation class (UniqueClient
) in both cases should receive an HttpClient
as a parameter
private readonly HttpClient httpClient;
public UniqueClient(HttpClient client)
=> httpClient = client;
As you could spot I've called the ITypedHttpClientFactory<UniqueClient>
's CreateClient
method with a new HttpClient
. (Side note: I could also call it with the clientFactory.CreateClient()
).
But it does not have to be a default HttpClient
. You can retrieve a named client as well. In that case you would have a named, typed client.
In this SO topic I've demonstrated how to use this technique to register the same Circuit Breaker decorated typed clients multiple times for different domains.