Search code examples
c#asp.net-coredependency-injectiondotnet-httpclient

How do I access a registered HttpClient service in a lambda implementation factory?


I've been trying to research this but have come up empty. I've been trying to use elements from other questions:

Using Factory Pattern with ASP.NET Core Dependency Injection

On IServiceProvider what are the differences between the GetRequiredService and GetService methods?

ASP.NET Core DI: Resolve same instance if scoped service is registered both as service type and implementation type

It's a little bit confusing and no one has an example that exactly matches what I'm attempting to accomplish.

I have code similar to the following:

Middleware.cs


public static IServiceCollection UseAPI(thisIServiceCollection services, string key, string username, bool test = false)
{
    services.AddHttpClient<ApiClient>(client =>
    {
        // ... setting BaseAddress based on test, adding auth headers, etc
    });
    return services.AddScoped<ApiClient>(x =>
    {
        var service = x.GetRequiredService<HttpClient>();
        var ApiClient client = new ApiClient(service);
        client.Configure(key);
        return client;
    }
}

Program.cs


services.UseAPI(_settings.Key, _settings.Username, _settings.IsTest);

ApiClient.cs


public class ApiClient
{
    private readonly HttpClient httpClient;
    private string Key;
    public ApiClient(HttpClient _httpClient)
    {
        httpClient = _httpClient;
    }

    public void Configure(string key)
    {
        Key = key;
    }
}

The Key value is used as part of the process of creating a signature of the content and so can't be added to the default headers. I can't seem to figure out how to get the key from the middleware. Of course I can inject configuration and get it that way, but that's not portable and I'm building this as a package so minimizing reliance on that would be ideal.


Solution

  • Currently you are trying to register your ApiClient twice.

    The services.AddHttpClient<ApiClient>() configures a HttpClient instance that is passed to the ApiClient constructor and adds ApiClient to your service collection. There is no need to the AddScoped call afterwards. See Typed Clients.

    The AddHttpClient<TClient,TImplementation>(IServiceCollection, String, Func<HttpClient,IServiceProvider,TImplementation>) overload of HttpClient would work for your case:

    services.AddHttpClient<ApiClient, ApiClient>((httpClient, serviceProvider) =>
    {
        var apiClient = new ApiClient(httpClient);
        apiClient.Configure(key);
        return apiClient;
    });
    

    I would also consider the Options Pattern to inject the key (and other settings).

    services.AddOptions<ApiClientOptions>()
        .Configure(options => options.Key = key);
    
    services.AddHttpClient<ApiClient>(httpClientOptions =>
    {
        // Configure HTTP Client
    });
    
    // or something like
    services.AddHttpClient<ApiClient>((serviceProvider, httpClientOptions) =>
    {
        var options = serviceProvider.GetRequiredService<IOptions<ApiClientOptions>>();
        // Configure HTTP Client
    });
    
    public class ApiClientOptions
    {
        public string Key { get; set; }
    } 
    
    public class ApiClient
    {
        private readonly HttpClient _httpClient;
        private readonly string _key;
        public ApiClient(HttpClient httpClient, IOptions<ApiClientOptions> options)
        {
            _httpClient = httpClient;
            _key = options.Value.Key;
        }
    }
    

    One more suggestion: This is DI service registration / configurations injection rather than middeware, so the method name AddApi is better suited than UseApi.