Basically, does the internal HttpMessageHandler expires also when the wrapping HttpHandler is used as a Singleton?
WARNING: Keeping HttpClient instances alive through Singletons is not safe.
The configured HttpMessageHandler instance will expire after two minutes. Any HttpClient that is created after the the expiry date will get a new HttpMessageHandler containing a new expiry date. But this doesn't help a HttpClient instance that is kept alive.
IMPORTANT: HttpClient will keep using the same HttpMessageHandler for as long as it lives and will, therefore,
not respect DNS changes. Only new HttpMessageHandler instances will see DNS updates.
This means that as long as you keep the HttpClient alive, that HttpClient will miss DNS changes, which is why HttpClient instances should only live for a short period of time. Creating and cleaning up HttpClient instances is very cheap as long as you reuse the underlying HttpMessageHandler instances (which is what the infrastructure does for you).
In your case, unfortunately, your HttpClient is injected into the CatalogService, which is injected into a Singleton consumer. The Singleton keeps the CatalogService alive and, therefore, indirectly keeps the HttpClient alive for the duration of the application—Your HttpClient is now a Captive Dependency.
What you're experiencing is an unfortunate design flaw in the new .NET Core IHttpClientFactory infrastructure. I consider this a flaw because IMO the infrastructure should prevent you from holding HttpClient instances captive, e.g. by registering the client (your CatalogService) as Scoped. I reported this issue back in January 2019. Microsoft has acknowledged the problem but has, as of this writing, no solution available.
As there is no solution yet, you have to be very careful not to cause HttpClient to be kept alive as Captive Dependency. This means you can't inject it into Singleton consumers (even indirect one's).
A good way to prevent this is by registering the client (your CatalogService) as Scoped. This allows the framework's configuration system to validate whether it is injected into a Singleton. But since there is no direct support for this, you will have to make this registration manually, by using the following extension method for instance:
public static IHttpClientBuilder AddTypedClientScoped<TClient>(
this IHttpClientBuilder builder)
where TClient : class
{
...
builder.Services.AddScoped<TClient>(s =>
{
var httpClientFactory = s.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient(builder.Name);
var factory = s.GetRequiredService<ITypedHttpClientFactory<TClient>>();
return factory.CreateClient(httpClient);
});
return builder;
}
While starting an ASP.NET Core website while debugging, the framework will validate scopes for you, which causes an exception when the client is injected into a singleton.
But please be warned that the use this extension method doesn't detect all captive HttpClient instances. That's because there are places in ASP.NET Core where components are registered as Transient will still being kept alive for the duration of the application. One such case is hosted services. The AddHostedService extension method is such example. Hosted services are registered as Transient while being kept alive as Singleton. In my optinion another design flaw. But that means that you should be careful while directly or indirectly injecting HttpClient instances into hosted services.