39

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.

Bryan
  • 5,065
  • 10
  • 51
  • 68

5 Answers5

54

Note: This answer uses HttpClient and a HttpClientFactory as an example but easily applies to any other kind of thing. For HttpClient in particular, using the new IHttpClientFactory from Microsoft.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.

Pang
  • 9,564
  • 146
  • 81
  • 122
poke
  • 369,085
  • 72
  • 557
  • 602
  • Thanks for the suggestions. HttpClient is one of those awkward types where I want to set its base address and header parameters to different values depending on the controller that is going to use it. Especially for the header, it is easier to set these config values in startup.cs than trying to it inside a controller. – Bryan Sep 29 '17 at 14:58
  • 1
    Yeah, `HttpClient` is kind of annoying. I really hope they move forward [with that new API](https://github.com/dotnet/corefx/issues/21568) soon. – poke Sep 29 '17 at 16:30
  • The factory is the right suggestion, but one important aspect of DI container is object lifecycle management. The factory pattern above would create instance of EVERY implementation and has no semantics on when objects should be disposed of. – Andrew Stakhov May 02 '18 at 19:05
  • @poke I liked your answer and created a variation on it and blogged about it [here](http://blog.robiii.nl/2019/02/handling-multiple-implementations-of.html). It also adresses Andrew's concerns (though still has some cases where behaviour may not be as expected). – RobIII Feb 27 '19 at 11:24
  • [How to Add Generated HttpClient to ASP.NET Core Dependency Injection (Right Way)](https://medium.com/@dmitry.pavlov/how-to-add-generated-httpclient-to-asp-net-core-dependency-injection-right-way-fec21b3385f1) – Dmitry Pavlov May 27 '19 at 10:13
  • @Poke Could you please clarify why your solution might be preferred over the one in the OP's question? Since in either case we're having to new-up the clients I didn't understand what made the factory approach better. – Emilio Dec 09 '20 at 02:43
  • @Emilio OP doesn’t have a “working solution”. You cannot register multiple HttpClients as `HttpClient` with the DI container and resolve them individually. You can only get the last, or all of them. – If you read my answer you can also see that I generally recommend against keyed/named dependencies. – poke Dec 09 '20 at 08:19
  • I would like to challenge basically everything but the last paragraph of this answer - as this is much too long for the comment section, I've written a [longer response here](https://gist.github.com/mindplay-dk/a86a301c17c9341e135e3b61e0d42379). I'm fairly confident that the limitation of one allowed instance of any type is a problem - I expect any answer you can come up with is going to be some variation of a service locator. – mindplay.dk Jul 04 '22 at 16:52
  • @poke I found a way to have "named" dependencies, which I've added to my response [here](https://gist.github.com/mindplay-dk/a86a301c17c9341e135e3b61e0d42379#that-being-said) - since this is the accepted answer, you might want to consider updating your answer and example. – mindplay.dk Jul 14 '22 at 13:22
9

Use named registrations

This is exactly what named registrations are for.

Register like this:

container.RegisterInstance<HttpClient>(new HttpClient(), "ClientA");
container.RegisterInstance<HttpClient>(new HttpClient(), "ClientB");

And retrieve this way:

var clientA = container.Resolve<HttpClient>("ClientA");
var clientB = container.Resolve<HttpClient>("ClientB");

If you want ClientA or ClientB automatically injected into another registered type, see this question. Example:

container.RegisterType<ControllerA, ControllerA>(
    new InjectionConstructor(                        // Explicitly specify a constructor
        new ResolvedParameter<HttpClient>("ClientA") // Resolve parameter of type HttpClient using name "ClientA"
    )
);
container.RegisterType<ControllerB, ControllerB>(
    new InjectionConstructor(                        // Explicitly specify a constructor
        new ResolvedParameter<HttpClient>("ClientB") // Resolve parameter of type HttpClient using name "ClientB"
    )
);

Use a factory

If your IoC container lacks any ability to handle named registrations, you could inject a factory and let the controller decide how to get the instance. Here is a really simple example:

class HttpClientFactory : IHttpClientFactory
{
    private readonly Dictionary<string, HttpClient> _clients;

    public void Register(string name, HttpClient client)
    {
        _clients[name] = client;
    }

    public HttpClient Resolve(string name)
    {
        return _clients[name];
    }
}

And in your controllers:

class ControllerA
{
    private readonly HttpClient _httpClient;

    public ControllerA(IHttpClientFactory factory)
    {
        _httpClient = factory.Resolve("ClientA");
    }
}

And in your composition root:

var factory = new HttpClientFactory();
factory.Register("ClientA", new HttpClient());
factory.Register("ClientB", new HttpClient());
container.AddSingleton<IHttpClientFactory>(factory);
John Wu
  • 50,556
  • 8
  • 44
  • 80
9

Another option is to

  • use an additional generic type parameter on the interface or a new interface implementing the non generic interface,
  • implement an adapter/interceptor class to add the marker type and then
  • use the generic type as “name”

I’ve written an article with more details: Dependency Injection in .NET: A way to work around missing named registrations

Rico Suter
  • 11,548
  • 6
  • 67
  • 93
1

Really the consumer of the service should not care where about the implementation of the instance it is using. In your case I see no reason to manually register many different instances of HttpClient. You could register the type once and any consuming instance that needs an instance will get it's own instance of HttpClient. You can do that with AddTransient.

The AddTransient method is used to map abstract types to concrete services that are instantiated separately for every object that requires it

services.AddTransient<HttpClient, HttpClient>();
Igor
  • 60,821
  • 10
  • 100
  • 175
  • I want to set the HttpClient base address for each HttpClient at startup. In that case, it is very important that the right controller get the right HttpClient. – Bryan Sep 28 '17 at 19:12
  • @Bryan - Possibly. Where are the end points (`endPointA`,`endPointB`,etc) registered? Maybe there is a way to dynamically return the correct one based on a factory pattern. If you have many end point values that can change over time you want to avoid brittle code where you have to maintain the injection list of end points / HttpClients separate from the functional changes you are making in your code (adding controllers, adding methods, etc). – Igor Sep 28 '17 at 19:15
  • In some ways this is an academic problem, I have other ways of solving it. But I think there must be a way with the IServiceCollection to distinguish between named instances like Castle, Ninject or Unity can do. – Bryan Sep 28 '17 at 19:30
  • Thanks for `AddTransient()` suggestion. It works for my use case as I need multiple instances, but don't know how many. – Jari Turkia Nov 27 '22 at 11:25
0

I know this solution is not that elegant but it worked for me. In my case I just created a list of my dependencies and added a Name property to my Service.


serviceCollection.AddTransient<MyService>();

serviceCollection.AddSingleton<List<MyService>>();

var build = serviceCollection.BuildServiceProvider();

var serviceA = build.GetService<MyService>()!;
serviceA.Name = "A";

var serviceB = build.GetService<MyService>()!;
serviceB.Name = "B";

// add both services to the list
var lst= build.GetService<List<MyService>>()!;
lst.Add(serviceA);
lst.Add(serviceB);

Now if I need service b in my class for example then I will do:

class Foo{
    public Foo(List<MyService> myServices) {
          var servB = myServices.First(x=>x.Name=="B");
    }

}
Tono Nam
  • 34,064
  • 78
  • 298
  • 470