In ASP.NET Core 2 Web Api, I want to use dependency injection to inject httpClientA
instance of HttpClient
to ControllerA
, and an instance httpClientB
of the HttpClient
to ControllerB
.
The DI registration code would look something like:
HttpClient httpClientA = new HttpClient(); httpClientA.BaseAddress = endPointA; services.AddSingleton<HttpClient>(httpClientA); HttpClient httpClientB = new HttpClient(); httpClientB.BaseAddress = endPointB; services.AddSingleton<HttpClient>(httpClientB);
I know I could subclass HttpClient
to make a unique type for each controller, but that doesn't scale very well.
What is a better way?
UPDATE Specifically regarding HttpClient Microsoft seems to have something in the works
https://github.com/aspnet/HttpClientFactory/blob/dev/samples/HttpClientFactorySample/Program.cs#L32 - thanks to @mountain-traveller (Dylan) for pointing this out.
NET Core provides three kinds of dependency injection, based in your lifetimes: Transient: services that will be created each time they are requested. Scoped: services that will be created once per client request (connection) Singleton: services that will be created only at the first time they are requested.
AddTransient() - This method creates a Transient service. A new instance of a Transient service is created each time it is requested. AddScoped() - This method creates a Scoped service. A new instance of a Scoped service is created once per request within the scope.
ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies. For more information specific to dependency injection within MVC controllers, see Dependency injection into controllers in ASP.NET Core.
AddHttpClient<SubClient1>(c => c. BaseAddress = new System. Uri("https://myapi.com")); With the above line, my SubClient1 will be injected into my public ApiClient with the proper HttpClient instance already set up with BaseAddress .
Note: This answer uses
HttpClient
and aHttpClientFactory
as an example but easily applies to any other kind of thing. ForHttpClient
in particular, using the newIHttpClientFactory
fromMicrosoft.Extensions.Http
is preferred.
The built-in dependency injection container does not support named dependency registrations, and there are no plans to add this at the moment.
One reason for this is that with dependency injection, there is no type-safe way to specify which kind of named instance you would want. You could surely use something like parameter attributes for constructors (or attributes on properties for property injection) but that would be a different kind of complexity that likely wouldn’t be worth it; and it certainly wouldn’t be backed by the type system, which is an important part of how dependency injection works.
In general, named dependencies are a sign that you are not designing your dependencies properly. If you have two different dependencies of the same type, then this should mean that they may be interchangeably used. If that’s not the case and one of them is valid where the other is not, then that’s a sign that you may be violating the Liskov substitution principle.
Furthermore, if you look at those dependency injection containers that do support named dependencies, you will notice that the only way to retrieve those dependencies is not using dependency injection but the service locator pattern instead which is the exact opposite of inversion of control that DI facilitates.
Simple Injector, one of the larger dependency injection containers, explains their absence of named dependencies like this:
Resolving instances by a key is a feature that is deliberately left out of Simple Injector, because it invariably leads to a design where the application tends to have numerous dependencies on the DI container itself. To resolve a keyed instance you will likely need to call directly into the Container instance and this leads to the Service Locator anti-pattern.
This doesn’t mean that resolving instances by a key is never useful. Resolving instances by a key is normally a job for a specific factory rather than the Container. This approach makes the design much cleaner, saves you from having to take numerous dependencies on the DI library and enables many scenarios that the DI container authors simply didn’t consider.
With all that being said, sometimes you really want something like this and having a numerous number of subtypes and separate registrations is simply not feasible. In that case, there are proper ways to approach this though.
There is one particular situation I can think of where ASP.NET Core has something similar to this in its framework code: Named configuration options for the authentication framework. Let me attempt to explain the concept quickly (bear with me):
The authentication stack in ASP.NET Core supports registering multiple authentication providers of the same type, for example you might end up having multiple OpenID Connect providers that your application may use. But although they all share the same technical implementation of the protocol, there needs to be a way for them to work independently and to configure the instances individually.
This is solved by giving each “authentication scheme” a unique name. When you add a scheme, you basically register a new name and tell the registration which handler type it should use. In addition, you configure each scheme using IConfigureNamedOptions<T>
which, when you implement it, basically gets passed an unconfigured options object that then gets configured—if the name matches. So for each authentication type T
, there will eventually be multiple registrations for IConfigureNamedOptions<T>
that may configure an individual options object for a scheme.
At some point, an authentication handler for a specific scheme runs and needs the actual configured options object. For this, it depends on IOptionsFactory<T>
whose default implementation gives you the ability to create a concrete options object that then gets configured by all those IConfigureNamedOptions<T>
handlers.
And that exact logic of the options factory is what you can utilize to achieve a kind of “named dependency”. Translated into your particular example, that could for example look like this:
// container type to hold the client and give it a name public class NamedHttpClient { public string Name { get; private set; } public HttpClient Client { get; private set; } public NamedHttpClient (string name, HttpClient client) { Name = name; Client = client; } } // factory to retrieve the named clients public class HttpClientFactory { private readonly IDictionary<string, HttpClient> _clients; public HttpClientFactory(IEnumerable<NamedHttpClient> clients) { _clients = clients.ToDictionary(n => n.Name, n => n.Client); } public HttpClient GetClient(string name) { if (_clients.TryGet(name, out var client)) return client; // handle error throw new ArgumentException(nameof(name)); } } // register those named clients services.AddSingleton<NamedHttpClient>(new NamedHttpClient("A", httpClientA)); services.AddSingleton<NamedHttpClient>(new NamedHttpClient("B", httpClientB));
You would then inject the HttpClientFactory
somewhere and use its GetClient
method to retrieve a named client.
Obviously, if you think about this implementation and about what I wrote earlier, then this will look very similar to a service locator pattern. And in a way, it really is one in this case, albeit built on top of the existing dependency injection container. Does this make it better? Probably not, but it’s a way to implement your requirement with the existing container, so that’s what counts. For full defense btw., in the authentication options case above, the options factory is a real factory, so it constructs actual objects and doesn’t use existing pre-registered instances, so it’s technically not a service location pattern there.
Obviously, the other alternative is to completely ignore what I wrote above and use a different dependency injection container with ASP.NET Core. For example, Autofac supports named dependencies and it can easily replace the default container for ASP.NET Core.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With