简体   繁体   中英

What is the lifetime of a typed HttpClient instance from IHttpClientFactory where the type that will receive it is registered as "AddScoped"?

TL;DR

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>();

Context

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.

Questions

  1. What is the lifetime of a typed HttpClient instance from IHttpClientFactory where the type that will receive it is registered as "AddScoped"?
  2. Is this behaviour correct? Shouldn't the HttpClient configuration (eg Base Address) be cached somehow or saved somewhere, since it is typed?
  3. Wouldn't it create some GC pressure (lots of clients being created for the same type) if we would have a more extreme scenario?

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

Is this behaviour correct? Shouldn't the HttpClient configuration (eg 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 specific 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM