Multiple implementations of an interface in ASP.NET Core DI container

If you worked with ASP.NET Core before chances are you used ASP.NET Core's
dependency injection features. Most commonly you register a single
implementation of an interface as a service type. However, at times you may want
to register multiple implementations of an interface as service types. In this
article you learn how to accomplish the task.
Let's first understand what we are trying to accomplish.
Suppose you have an interface as shown below:
public interface IHelloService
{
string SayHello();
}
The IHelloService interface contains just a single method named SayHello().
Let's further assume that you have three implementations of IHelloService
with you as outlined below:
public class HelloA:IHelloService
{
public string SayHello()
{
return "Hello World!";
}
}
public class HelloB : IHelloService
{
public string SayHello()
{
return "Hello Galaxy!";
}
}
public class HelloC : IHelloService
{
public string SayHello()
{
return "Hello Universe!";
}
}
The three implementations namely HelloA, HelloB, and HelloC simply return
three different strings to the caller - Hello World!, Hello Galaxy!, and Hello
Universe!.
How do you register these three concrete types with the ASP.NET Core's DI
container?
One of the ways is this :
services.AddScoped<HelloA>();
services.AddScoped<HelloB>();
services.AddScoped<HelloC>();
In your ConfigureServices() you add the above code and register the concrete
types HelloA, HelloB, and HelloC with the DI container. Although all of them
implement IHelloService, while registering them they are registered as
independent concrete types.
To grab one or more implementations of IHelloService you can use constructor
injection like this :
public IndexModel(HelloA hello1, HelloB hello2,
HelloC hello3)
{
string msg1 = hello1.SayHello();
string msg2 = hello2.SayHello();
string msg3 = hello3.SayHello();
}
The above code shows a constructor of Index razor page. The constructor has
three parameters - one for each concrete type. In this case the DI container
will supply the respective types as expected and the three string variables will
hold - Hello World!, Hello Galaxy!, and Hello Universe! respectively.
So far so good.
Now, change the way you registered the services in the ConfigureServices().
services.AddScoped<IHelloService, HelloA>();
services.AddScoped<IHelloService, HelloB>();
services.AddScoped<IHelloService, HelloC>();
Note that AddScoped() now registers multiple implementations of IHelloService.
To accommodate this change you also modify the constructor as shown below:
public IndexModel(IHelloService hello1, IHelloService hello2,
IHelloService hello3)
{
string msg1 = hello1.SayHello();
string msg2 = hello2.SayHello();
string msg3 = hello3.SayHello();
}
What's the outcome?
You will find that all the string variables contain Hello Universe! This is
because DI container injects only HelloC objects in the constructor since it's
the last implementation of IHelloService registered in the ConfigureServices().
To inject all the implementations of IHelloServices interface you need to
modify the constructor like this:
public IndexModel(IEnumerable<IHelloService> hello)
{
foreach(var obj in hello)
{
string msg = obj.SayHello();
}
}
As you can see, the constructor now takes an IEnumerable<IHelloService>. This
way all the registered implementations of IHelloService are injected into the
constructor. Inside, you can loop through the supplied implementations and
invoke SayHello() on each. This time you will find that SayHello() correctly
returns Hello World!, Hello Galaxy!, and Hello Universe! during the
corresponding iterations. You can also check the type of obj to decide whether
to use that implementation of IHelloService or not.
That's it for now! Keep coding!!