Injecting HttpClient & Using IHttpClientFactory in .Net Core

The problems with the HttpClient surrounding socket exhaustion and DNS updates are well documented. For years, we as developers have known to create a single instance of HttpClient and re-use it throughout the entire application lifetime. This wasn't necessarily the end of it though, but thankfully from .Net Core 2.1 onwards, Microsoft added IHttpClientFactory to configure and handle HttpClient instances.

Using this factory has many benefits and are a few different ways to use it. We recommend two of these methods:

  • Inject IHttpClientFactory into class, and call CreateClient() - useful for retrofitting into existing classes
  • Create Typed Clients where a specific pre-configured client is tied to a specific class

Inject IHttpClientFactory

Register the factory in Startup.cs (or wherever you are defining your dependencies) and then add IHttpClientFactory to the constructor of your class. Whenever you need an HttpClient, simply call CreateClient()

Dependency Registration


    services.AddHttpClient();

Implementation


    public class UsingFactory : IUsingFactory
    {
        private readonly IHttpClientFactory _httpClientFactory;

        public UsingFactory(IHttpClientFactory httpClientFactory)
        {
            _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
        }

        public void DoSomething()
        {
            var httpClient = _httpClientFactory.CreateClient();

            // use ParkSquare.Extensions.Http methods on httpClient to call API endpoints
        }
    }

Typed Clients

With a typed client, the HttpClient and HttpMessageHandler can be pre-configured just for that particular class. In this example, the class we want the HttpClient injecting into is called TypedClient, which implements the ITypedClient interface. Most of the configuration shown here is optional, to illustrate what can be done. Note the way in which a configuration class (TypedClientConfig) is retrieved from the DI container.

Dependency Registration & HttpClient Configuration


    services.AddSingleton<ITypedClientConfig, TypedClientConfig>();

    services.AddHttpClient<ITypedClient, TypedClient>()
        .ConfigureHttpClient((serviceProvider, httpClient) =>
        {
            var clientConfig = serviceProvider.GetRequiredService<ITypedClientConfig>();
            httpClient.BaseAddress = clientConfig.BaseUrl;
            httpClient.Timeout = TimeSpan.FromSeconds(clientConfig.Timeout);
            httpClient.DefaultRequestHeaders.Add("User-Agent", "BlahAgent");
            httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
        })
        .SetHandlerLifetime(TimeSpan.FromMinutes(5))    // Default is 2 mins
        .ConfigurePrimaryHttpMessageHandler(x =>
            new HttpClientHandler
            {
                AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
                UseCookies = false,
                AllowAutoRedirect = false,
                UseDefaultCredentials = true,
            });

Typed Client Implementation


    public class TypedClient : ITypedClient
    {
        private readonly HttpClient _httpClient;

        public TypedClient(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }

        public void DoSomething()
        {
            // Read some properties to prove the intended HttpClient has been injectd in

            Console.WriteLine($"Base address of injected client: {_httpClient.BaseAddress}");
            Console.WriteLine($"Timeout of injected client: {_httpClient.Timeout}");

            // use ParkSquare.Extensions.Http methods on _httpClient to call API endpoints
        }
    }

Configuration Class


    public class TypedClientConfig : ITypedClientConfig
    {
        public TypedClientConfig(IConfiguration configuration)
        {
            if (configuration == null) throw new ArgumentNullException(nameof(configuration));
            configuration.Bind("TypedClient", this);
        }

        public Uri BaseUrl { get; set; }

        public int Timeout { get; set; }
    }

appSettings.json


    {
        "TypedClient": {
            "BaseUrl": "https://www.example.com",
            "Timeout":  10
        }
    }