Dealing with Multiple Implementations of a Service in ASP.NET Core & ABP Dependency Injection
ASP.NET Core provides a built-in dependency injection system to register your services to the dependency injection container and inject/resolve them whenever you need. ABP's dependency injection infrastructure is built on ASP.NET Core's DI system, automates service registration by conventions and provides some additional features.
In this tutorial, I will explain how you can register multiple implementations of the same service interface and inject/resolve all these implementations when you need them.
Defining Services
Assume that you have an IExternalLogger
interface with two implementations:
public interface IExternalLogger
{
Task LogAsync(string logText);
}
public class ElasticsearchExternalLogger : IExternalLogger
{
public async Task LogAsync(string logText)
{
// TODO...
}
}
public class AzureExternalLogger : IExternalLogger
{
public async Task LogAsync(string logText)
{
// TODO...
}
}
An example service injecting the IExternalLogger
interface:
public class MyService : ITransientDependency
{
private readonly IExternalLogger _externalLogger;
public MyService(IExternalLogger externalLogger)
{
_externalLogger = externalLogger;
}
public async Task DemoAsync()
{
await _externalLogger.LogAsync("Example log message...");
}
}
In this example, we haven't registered any of the implementation classes to the dependency injection system yet. So, if we try to use the MyService
class, we get an error indicating that no implementation found for the IExternalLogger
service. We should register at least one implementation for the IExternalLogger
interface.
Registering Services
If we register both of the ElasticsearchExternalLogger
and AzureExternalLogger
services for the IExternalLogger
interface, and then try to inject the IExternalLogger
interface, the last registered implementation will be used. However, how to determine the last registered implementation?
If we implement one of the dependency interfaces (e.g. ITransientDependency
), then the registration order will be uncertain (it may depend on the namespaces of the classes). The last registered implementation can be different than you expect. So, it is not suggested to use the dependency interfaces to register multiple implementations.
You can register your services in the ConfigureServices
method of your module:
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddTransient<IExternalLogger, ElasticsearchExternalLogger>();
context.Services.AddTransient<IExternalLogger, AzureExternalLogger>();
}
In this case, you get an AzureExternalLogger
instance when you inject the IExternalLogger
interface, because the last registered implementation is the AzureExternalLogger
class.
Injecting Multiple Implementations
When you have multiple implementation of an interface, you may want to work with all these implementations. For this example, you may want to write log to all the external loggers. We can change the MyService
implementation as the following:
public class MyService : ITransientDependency
{
private readonly IEnumerable<IExternalLogger> _externalLoggers;
public MyService(IEnumerable<IExternalLogger> externalLoggers)
{
_externalLoggers = externalLoggers;
}
public async Task DemoAsync()
{
foreach (var externalLogger in _externalLoggers)
{
await externalLogger.LogAsync("Example log message...");
}
}
}
In this example, we are injecting IEnumerable<IExternalLogger>
instead of IExternalLogger
, so we have a collection of the IExternalLogger
implementations. Then we are using a foreach
loop to write the same log text to all the IExternalLogger
implementations.
If you are using IServiceProvider
to resolve dependencies, then use its GetServices
method to obtain a collection of the service implementations:
IEnumerable<IExternalLogger> services = _serviceProvider.GetServices<IExternalLogger>();
Further Reading
In this small tutorial, I explained how you can register multiple implementations of the same interface to the dependency injection system and inject/resolve all of them when you need.
If you want to get more information about ABP's and ASP.NET Core's dependency injection systems, you can read the following documents:
Comments
Farouk Belhocine 137 weeks ago
Hi Very interesting. Thanks for the simplicity.
Halil İbrahim Kalkan 136 weeks ago
You're welcome :)
Damien R 116 weeks ago
Thanks for your article, it as almost exactly what I was searching for.
A little detail was that I wanted to use Dependency Interfaces to inject instead of having explicitly to use context.Services to avoid having to add a reference for each class. The problem of ordering was irrelevant for me because I wanted all implementations to call them all.
I strugled with the naming convention. Multiple implementations class can't respect the convention because they'll have specific names. I was able to correct it by adding `[ExposeServices(typeof(IExternalLogger))]``
Alexander Nikonov 38 weeks ago
Unfortunately, this approach does not help to resolve the following issue (or I might be missing something obvious).
We have the override for
IAbpApplicationConfigurationAppService
:This override resides somewhere in the external library and is present optionally in the projects.
Later on we decided to add something on top and thus we are creating another override. The point is to have it on top, i.e. to override the functionality of the existing implementation whichever it is - initial
AbpApplicationConfigurationAppService
orService1ApplicationConfigurationAppService
or some other override.This does not work - it causes some circular dependency issues. I've tried various approaches and nothing still works.
In some cases
Service2ApplicationConfigurationAppService
constructor is not invoked at all, I don't know why. I haveat place. And of course, I want
Service2ApplicationConfigurationAppService
to be on top of the existing service, notService1ApplicationConfigurationAppService
to replaceService2ApplicationConfigurationAppService
, for instance.Liming Ma 37 weeks ago
hi Alexander Nikonov
You shouldn't get
IAbpApplicationConfigurationAppService
from DI in an implemented instance ofIAbpApplicationConfigurationAppService
. This will cause a circular dependency issue.If you want to perform some logic when getting a service from DI. You can use factory methods.
Maybe your case is not a dependency injection problem. What is your purpose for replacing
IAbpApplicationConfigurationAppService
?Have a good day.
Alexander Nikonov 37 weeks ago
You encouraged me to look for an alternative solution and looks liked it's really possible here - using Application Configuration Contributor. I hope it will work as expected. Regarding a factory approach: I will keep this in mind for future. Thank you.