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?
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.
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
.