Search code examples
c#httpclient.net-5ihttpclientfactory

Puzzled by the lifetime of the httpclient in the new ihttpfactory


Refacoring from the code where the norm was singleton httpclient to the new ihttpfactory I am so much puzzled by the lifetime of the httpClient:

The typed client is registered as transient with DI. [Typed clients]

This means all my former singleton services that depended on httpclient will now follow the suit [i.e. be transient]. I can not find a backing documentation on this design choice? Wouldn't that now hurt in terms of performance as we'll have to make all the dependency graph transient?


Solution

  • Google "typed httpclient transient" is your friend: https://www.stevejgordon.co.uk/ihttpclientfactory-patterns-using-typed-clients-from-singleton-services

    To put it simply, instead of injecting and using the typed HttpClient directly, you create a factory that depends on IServiceProvider, with a method that uses that service provider to return an instance of the typed HttpClient, and inject that factory. You then invoke the factory's creation method every time you need an instance of the typed HttpClient. The service provider does all the management of scope for you.

    In other words, instead of the following:

    services.AddHttpClient<IMyHttpClient, MyHttpClient>();
    services.AddScoped<IDependsOnHttpClient, DependsOnHttpClient>();
    
    ...
    
    public class DependsOnHttpClient : IDependsOnHttpClient
    {
        private readonly IMyHttpClient _httpClient;
    
        public DependsOnHttpClient(IMyHttpClient httpClient)
            => _httpClient = httpClient;
    
        public async Task DoSomethingWithHttpClientAsync()
            => _httpClient.GetAsync("https://foo.bar");
    }
    

    you write:

    services.AddHttpClient<IMyHttpClient, MyHttpClient>();
    services.AddSingleton<IMyHttpClientFactory, MyHttpClientFactory>();
    services.AddSingleton<IDependsOnHttpClient, DependsOnHttpClient>();
    
    ...
    
    public class MyHttpClientFactory : IMyHttpClientFactory
    {
        private readonly IServiceProvider _serviceProvider;
    
        public MyHttpClientFactory(IServiceProvider serviceProvider)
            => _serviceProvider = serviceProvider;
    
        public IMyHttpClient CreateClient()
            => _serviceProvider.GetRequiredService<IMyHttpClient>();
    }
    
    public class DependsOnHttpClient : IDependsOnHttpClient
    {
        private readonly IMyHttpClientFactory _httpClientFactory;
    
        public DependsOnHttpClient(IMyHttpClientFactory httpClientFactory)
            => _httpClientFactory = httpClientFactory;
    
        public async Task DoSomethingWithHttpClientAsync()
        {
             var httpClient = _httpClientFactory.CreateClient();
    
             return await _httpClient.GetAsync("https://foo.bar");
        }
    }
    

    At this point you might be wondering "what do I gain by writing all this code versus just injecting an IHttpClientFactory and calling its methods", and I'm honestly not sure of the answer. Perhaps with Roslyn source generators, we will be able to generate typed HttpClients and the factories for them.