In .NET 6:
What is the lifetime of a typed HttpClient
instance from IHttpClientFactory
where the type that will receive it is registered as "Scoped"?
Shouldn't it be kind of a "timed singleton" (regardless of the lifetime of the class that will use it) when we register it like the excerpt below? Or is the HttpClient
Transient - and .NET doesn't cache any of its configurations and only the Handler is pooled?
services.AddHttpClient<IServiceInterface, ServiceImpl>(client =>
{
client.BaseAddress = "<some absolute URL here>";
}
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
services.AddScoped<IServiceInterface, ServiceImpl>();
The application I'm working on accesses several external APIs at different addresses. I've encapsulated each service access logic into Service classes with their respective interfaces, so they could be injected at runtime. As prescribed by Microsoft, I'm using Typed HttpClients, and I wrote a helper method to configure them at the Startup.cs
:
public static IServiceCollection ConfigureHttpClientForService<TInterface, TImpl>
(this IServiceCollection services, Func<IServiceProvider, Uri> func)
where TInterface : class
where TImpl : class, TInterface
{
services.AddHttpClient<TInterface, TImpl>((provider, client) =>
{
var uri = func(provider);
client.BaseAddress = uri;
})
// Polly Rules here and other stuff
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
return services;
}
Then, on Startup.cs
ConfigureServices
method I call it like this:
services
.ConfigureHttpClientForService<IServiceInterface, ServiceImpl>(provider =>
{
if (!Uri.TryCreate(
settings.UrlObjConfig.Url,
UriKind.RelativeOrAbsolute,
out var uri))
{
throw new UriFormatException("Invalid URL");
}
return uri;
});
At runtime, I've noticed that the Action<HttpClient>
that configures the HttpClient
(AddHttpClient<TClient,TImplementation>(IServiceCollection, Action<HttpClient>)
- docs) is being called every single time when I call a method from a service that uses the typed client - in other words, every time a Scoped service gets instantiated, that Action is being run.
HttpClient
instance from IHttpClientFactory
where the type that will receive it is registered as "AddScoped"?What is the lifetime of a typed HttpClient instance from
IHttpClientFactory
where the type that will receive it is registered as "AddScoped"?
If you look at the related source codes then you can see that
IHttpClientFactory
, IHttpMessageHandlerFactory
Is this behaviour correct? Shouldn't the HttpClient configuration (e.g. Base Address) be cached somehow or saved somewhere, since it is typed?
The AddHttpClient
does not provide an explicit interface to set the BaseAddress
. Rather it allows the consumer of the API to specify a method (Action<HttpClient> configureClient
) to configure the HttpClient
as (s)he wishes.
So, the BaseAddress
is not cached. The HttpClient
's life cycle is also short, but the underlying HttpClientMessageHandler
s are pooled. (related source code)
Manages the pooling and lifetime of underlying HttpClientMessageHandler instances. Automatic management avoids common DNS (Domain Name System) problems that occur when manually managing HttpClient lifetimes
Side-note: there is a cache inside DefaultTypedHttpClientFactory
which helps the creation of the typed http clients.
Wouldn't it create some GC pressure (lots of clients being created for the same type) if we would have a more extreme scenario?
As it was discussed above your typed clients and HttpClient
s come and go. The underlying HttpMessageHandler
s are pooled.
Most probably you will first hit the limit of the outgoing concurrent calls rather than causing too much pressure on the GC. But with some stress testing you can make sure which issue hits first.
There is also a cleanup process which runs every 10 seconds by default.