Add an overload with a Func<IServiceProvider, T> to AddHttpClient<TClient, TImplementation>
See original GitHub issueWe have a multi-tenant web app that calls to our multi-tenant Web Api. I’m changing the api client used in the web app to use IHttpClientFactory, using typed clients. However, as it’s a multi-tenant software, we have logic to call to different URLs depending on where the request is coming from. Something like this:
public class MyApiClient : IMyApiClient
{
public MyApiClient(HttpClient client, string baseUrl) {}
}
I used to do DI like this before:
services.AddScoped<IMyApiClient>(sp =>
{
// decide which url to use in the client
return new MyApiClient(url);
}
However, now with .AddHttpClient
, since there’s no overload that accepts a Func<IServiceCollection, T>
. there’s no way I can do that anymore, and I’m left with three non-optimal solutions:
-
Change to use named clients instead of typed clients. However, that’s not good not only because well, it’s named clients, but also because I already have plenty of dependencies to
IMyApiClient
in a lot of places. -
Copy the source code from
.AddHttpClient
and do the base URL selection just like before. -
Create a whole new class just to represent the API base URL so I can register it on the IoC container and ask for that instead of a string on the
MyApiClient
constructor.
Note that I can’t just set the HttpClient base URL using the Action<HttpClient> configureClient
overload for two reasons:
- In order to select the base URL, I need another dependency. Thus, I would need an instance of
IServiceProvider
. - Even if I could change my logic to not need any dependency when selecting the base URL, it still wouldn’t work, because the base URL can’t be changed after the HttpClient has executed a request.
EDIT:
I ended up going with option 2. This is what I came up with:
services.AddHttpClient("MyApiClient")
.AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(4, attempt => TimeSpan.FromSeconds(Math.Pow(1.45, attempt)) + TimeSpan.FromMilliseconds(_xoroshiro.Next(0, 100))))
.AddTransientHttpErrorPolicy(p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(15)));
services.AddTransient<IMyApiClient>(sp =>
{
// logic to select URL
var context = sp.GetRequiredService<IHttpContextAccessor>().HttpContext;
string[] host = context.Request.Host.Host.Replace("someurl", "").Split(new char[] { '.' });
string subdomain= host.Length > 1 ? host[1] : host[0];
string url = Configuration.GetSection("MyApiBaseUrl")?[subdomain] ?? Configuration.GetSection("MyApiBaseUrl")?["Default"];
// actual DI logic
var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient("MyApiClient");
return new MyApiClient(url, httpClient);
});
What’s different from the ASP.NET implementation is that I don’t use .GetRequiredService<ITypedHttpClientFactory<TClient>>();
and .Activator(_services, new object[] { httpClient });
, instead just new
ing the api client up. Is there any practical difference to how this will behave?
Issue Analytics
- State:
- Created 5 years ago
- Comments:6 (3 by maintainers)
Top GitHub Comments
Here’s the API proposal to review.
Summary
This change adds an accelerator for some functionality that’s hard to discover. We’ve had a few bugs reported now to the effect of “HTTP Client Factory doesn’t have what I need” people people didn’t find these APIs where they are currently exposed.
Before
After
APIs
note: the
TImplementation
type parameter is needed to avoid a really nasty source-breaking change. This means that practically speaking, both type parameters will need to be specified. I think this still accomplishes the goal of making these APIs more discoverable because they will be part of the overload set ofAddHttpClient
(today they are hiding onAddTypedClient
where folks don’t look)I’ve got a similar problem. I need to send requests authenticated with Digest Authentication. However, in order to do that with
HttpClient
(and not manually, which is a pain) I need to pass the credentials to the HttpClient constructor. Unfortunately though, there is no oveload ofAddHttpClient
currently that accepts aFunc<HttpClient>
(onlyAction<HttpClient>
for configuration).