Registering Multiple Services with a Single Interface in .NET Core
In .NET Core, dependency injection is a first-class citizen. It’s built into the framework and it’s easy to use. But what if you have multiple implementations of an interface and you want to inject a specific implementation based on certain conditions? In this blog post, we’ll explore how to do just that.
Defining the Interface and Implementations
First, let’s define an interface and a couple of implementations. For this example, we’ll create a simple IService
interface and two implementations, Service1
and Service2
.
public interface IService
{
void DoWork();
}
public class Service1 : IService
{
public void DoWork()
{
Console.WriteLine("Service1 is doing work");
}
}public class Service2 : IService
{
public void DoWork()
{
Console.WriteLine("Service2 is doing work");
}
}
Registering the Services
Next, we’ll register our services in the Startup.cs
file. We’ll use the AddSingleton
method to register our services, but you could also use AddScoped
or AddTransient
depending on your needs.
Here’s the key part: instead of registering our services with the IService
interface, we’ll register them with their concrete types. This allows us to retrieve a specific implementation later.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<Service1>();
services.AddSingleton<Service2>();
}
Retrieving a Specific Service
Now, we’ll register a delegate that takes a string key and returns the corresponding service based on the key. This delegate uses the IServiceProvider
to get the required service.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<Service1>();
services.AddSingleton<Service2>();
services.AddSingleton<Func<string, IService>>(serviceProvider => key =>
{
switch (key)
{
case "Service1":
return serviceProvider.GetService<Service1>();
case "Service2":
return serviceProvider.GetService<Service2>();
default:
throw new KeyNotFoundException(); // or maybe return null, up to you
}
});
}
Note: When you register multiple services with the same interface using AddSingleton
, AddScoped
, or AddTransient
, the last registration will overwrite the previous ones. So, you should register your services with their concrete types like this
services.AddSingleton<Service1>();
services.AddSingleton<Service2>();
Using the Service
Finally, we can retrieve a specific service instance in our controllers or wherever we need it. We just pass the key of the service we want to the delegate.
public class MyController : Controller
{
private readonly IService _service;
public MyController(Func<string, IService> serviceAccessor)
{
_service = serviceAccessor("Service1"); // or "Service2"
} public void SomeMethod()
{
_service.DoWork();
}
}
And that’s it! We’ve successfully registered multiple services with a single interface and retrieved a specific implementation based on a key. This is a powerful technique that can help you write more flexible and maintainable code.
I used this basic example to explain this topic and I hope this blog has been helpful in understanding this aspect of .NET Core.
Update: Apart from above workaround, I’ve created a video tutorial demonstrating how to use Keyed Services in .NET 8 for this exact use case. In the video, I walk you through the entire process of creating a Web API project, registering services with keys, and using the [FromKeyedServices]
attribute in action methods to resolve the appropriate service. Check out the video.