Search code examples
c#asp.net-core.net-coredotnet-httpclientpolly

Can the httpclient factory wire up be re-used


I have a third party api I am using for a number of my Services in .NET Core Web API project.
So, I have something like below:

services.AddHttpClient<IUserService, UserService>(client =>
{
    client.BaseAddress = new Uri(Environment.GetEnvironmentVariable("EXTERNAL_SERVICE_BASE_URL") ??
                                 throw new ArgumentNullException("EXTERNAL_SERVICE_BASE_URLcannot be null"));

    client.DefaultRequestHeaders.Add("removed", "removed");

}).ConfigurePrimaryHttpMessageHandler(() =>
{
    HttpClientHandler clientHandler = new HttpClientHandler
    {
        ClientCertificateOptions = ClientCertificateOption.Manual,
        ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
    };
    clientHandler.ClientCertificates.Add(new X509Certificate2(securitySession.CertificateData.Data, securitySession.CertificateData.Password));
    return new HttpClientXRayTracingHandler(clientHandler);
})
.AddPolicyHandler(HttpPolicies.GetRetryPolicy())
.SetHandlerLifetime(TimeSpan.FromSeconds(1));

This is fine and I can access the HttpClient in UserService and call my External API fine. However what I am going to need now is more Services in my code - but they use the same EXTERNAL_API - so lets say and AccountService and a CustomerService - and i'll end up with this:

services.AddHttpClient<IAccountService, AccountService>(client =>
{
    client.BaseAddress = new Uri(Environment.GetEnvironmentVariable("EXTERNAL_SERVICE_BASE_URL") ??
                                 throw new ArgumentNullException("EXTERNAL_SERVICE_BASE_URLcannot be null"));
    client.DefaultRequestHeaders.Add("removed", "removed");
}).ConfigurePrimaryHttpMessageHandler(() =>
{
    HttpClientHandler clientHandler = new HttpClientHandler
    {
        ClientCertificateOptions = ClientCertificateOption.Manual,
        ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
    };
    clientHandler.ClientCertificates.Add(new X509Certificate2(securitySession.CertificateData.Data, securitySession.CertificateData.Password));
    return new HttpClientXRayTracingHandler(clientHandler);
})
.AddPolicyHandler(HttpPolicies.GetRetryPolicy())
.SetHandlerLifetime(TimeSpan.FromSeconds(1));
services.AddHttpClient<ICustomerService, CustomerService>(client =>
{
    client.BaseAddress = new Uri(Environment.GetEnvironmentVariable("EXTERNAL_SERVICE_BASE_URL") ??
                                 throw new ArgumentNullException("EXTERNAL_SERVICE_BASE_URLcannot be null"));
    client.DefaultRequestHeaders.Add("removed", "removed");
}).ConfigurePrimaryHttpMessageHandler(() =>
{
    HttpClientHandler clientHandler = new HttpClientHandler
    {
        ClientCertificateOptions = ClientCertificateOption.Manual,
        ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
    };
    clientHandler.ClientCertificates.Add(new X509Certificate2(securitySession.CertificateData.Data, securitySession.CertificateData.Password));
    return new HttpClientXRayTracingHandler(clientHandler);
})
.AddPolicyHandler(HttpPolicies.GetRetryPolicy())
.SetHandlerLifetime(TimeSpan.FromSeconds(1));

The only change being the Service in my App that is using the HttpClient. Is there a way I can move all the common plumbing of the wire up to a private method that can be called when I am adding the http client to each of my services?


Solution

  • I would suggest to have a single named client

    services.AddHttpClient("commonClient", client =>
    {
        client.BaseAddress = new Uri(Environment.GetEnvironmentVariable("EXTERNAL_SERVICE_BASE_URL") ??
                                     throw new ArgumentNullException("EXTERNAL_SERVICE_BASE_URL cannot be null"));
    
        client.DefaultRequestHeaders.Add("removed", "removed");
    
    }).ConfigurePrimaryHttpMessageHandler(() =>
    {
        HttpClientHandler clientHandler = new HttpClientHandler
        {
            ClientCertificateOptions = ClientCertificateOption.Manual,
            ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
        };
        clientHandler.ClientCertificates.Add(new X509Certificate2(securitySession.CertificateData.Data, securitySession.CertificateData.Password));
        return new HttpClientXRayTracingHandler(clientHandler);
    })
    .AddPolicyHandler(HttpPolicies.GetRetryPolicy())
    .SetHandlerLifetime(TimeSpan.FromSeconds(1));
    

    and 3 typed clients

    services.AddHttpClient<IUserService, UserService>();
    services.AddHttpClient<IAccountService, AccountService>();
    services.AddHttpClient<ICustomerService, CustomerService>();
    

    The usage of these components are a bit different than using just a named or just a typed client

    readonly IUserService client;
    public XYZController(IHttpClientFactory namedClientFactory, ITypedHttpClientFactory<UserService> typedClientFactory)
    {
        var namedClient = namedClientFactory.CreateClient("commonClient");
        client = typedClientFactory.CreateClient(namedClient);
    }
    
    • First via the IHttpClientFactory we retrieve the "commonClient"
    • Then we create a new instance of UserService by passing the previously retrieved named client

    NOTE: The type parameter of ITypedHttpClientFactory must be the concrete type not the interface otherwise you would receive an InvalidOperationException