From 82bd18461b2396902bf5500c712b45ad00c478c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 14 Apr 2022 11:08:41 +0300 Subject: [PATCH 1/3] Added Dealing with multiple implementations section --- docs/en/Dependency-Injection.md | 98 ++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/docs/en/Dependency-Injection.md b/docs/en/Dependency-Injection.md index f347dc1dd7..019366b3be 100644 --- a/docs/en/Dependency-Injection.md +++ b/docs/en/Dependency-Injection.md @@ -49,7 +49,7 @@ public class BlogModule : AbpModule } ```` -The section below explains the conventions and configurations. +The sections below explain the conventions and configurations. ### Inherently Registered Types @@ -266,6 +266,102 @@ public class MyService : ITransientDependency } ```` +### Dealing with multiple implementations + +You can register multiple implementations of the same service interface. Assume that you have an `IExternalLogger` interface with two implementations: + +````csharp +public interface IExternalLogger +{ + Task LogAsync(string logText); +} + +public class ElasticsearchExternalLogger : IExternalLogger +{ + public async Task LogAsync(string logText) + { + //TODO... + } +} + +public class AzureExternalLogger : IExternalLogger +{ + public Task LogAsync(string logText) + { + throw new System.NotImplementedException(); + } +} +```` + +In this example, we haven't registered any of the implementation classes to the dependency injection system yet. So, if we try to inject the `IExternalLogger` interface, we get an error indicating that no implementation found. + +If we register both of the `ElasticsearchExternalLogger` and `AzureExternalLogger` services for the `IExternalLogger` interface, and then try to inject the `IExternalLogger` interface, then the last registered implementation will be used. + +An example service injecting the `IExternalLogger` interface: + +````csharp +public class MyService : ITransientDependency +{ + private readonly IExternalLogger _externalLogger; + + public MyService(IExternalLogger externalLogger) + { + _externalLogger = externalLogger; + } + + public async Task DemoAsync() + { + await _externalLogger.LogAsync("Example log message..."); + } +} +```` + +Here, as said before, we get the last registered implementation. 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: + +````csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + context.Services.AddTransient(); + context.Services.AddTransient(); +} +```` + +In this case, you get an `AzureExternalLogger` instance when you inject the `IExternalLogger` interface, because the last registered implementation is the `AzureExternalLogger` class. + +When you have multiple implementation of an interface, you may want to work with all these implementations. Assume that you want to write log to all the external loggers. We can change the `MyService` implementation as the following: + +````csharp +public class MyService : ITransientDependency +{ + private readonly IEnumerable _externalLoggers; + + public MyService(IEnumerable 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` 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: + +````csharp +IEnumerable services = _serviceProvider.GetServices(); +```` + ### Releasing/Disposing Services If you used a constructor or property injection, you don't need to be concerned about releasing the service's resources. However, if you have resolved a service from ``IServiceProvider``, you might, in some cases, need to take care about releasing the service resources. From fcaa452d3cee869014cedd9f1ad9e8833b7348e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 14 Apr 2022 11:31:20 +0300 Subject: [PATCH 2/3] Dealing with Multiple Implementations of a Service in Dependency Injection --- .../2022-04-14-Dependency-Injection/POST.md | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 docs/en/Community-Articles/2022-04-14-Dependency-Injection/POST.md diff --git a/docs/en/Community-Articles/2022-04-14-Dependency-Injection/POST.md b/docs/en/Community-Articles/2022-04-14-Dependency-Injection/POST.md new file mode 100644 index 0000000000..d6f8b9a5ba --- /dev/null +++ b/docs/en/Community-Articles/2022-04-14-Dependency-Injection/POST.md @@ -0,0 +1,113 @@ +# Dealing with Multiple Implementations of a Service in Dependency Injection + +ASP.NET Core provides a built-in [dependency injection system](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection) to register your services to the dependency injection container and inject/resolve them whenever you need. ABP's [dependency injection infrastructure](https://docs.abp.io/en/abp/latest/Dependency-Injection) 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: + +````csharp +public interface IExternalLogger +{ + Task LogAsync(string logText); +} + +public class ElasticsearchExternalLogger : IExternalLogger +{ + public async Task LogAsync(string logText) + { + //TODO... + } +} + +public class AzureExternalLogger : IExternalLogger +{ + public Task LogAsync(string logText) + { + throw new System.NotImplementedException(); + } +} +```` + +An example service injecting the `IExternalLogger` interface: + +````csharp +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](https://docs.abp.io/en/abp/latest/Dependency-Injection#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: + +````csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + context.Services.AddTransient(); + context.Services.AddTransient(); +} +```` + +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: + +````csharp +public class MyService : ITransientDependency +{ + private readonly IEnumerable _externalLoggers; + + public MyService(IEnumerable 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` 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: + +````csharp +IEnumerable services = _serviceProvider.GetServices(); +```` + +## 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: + +* [ABP's Dependency Injection documentation](https://docs.abp.io/en/abp/latest/Dependency-Injection) +* [ASP.NET Core Dependency Injection best practices, tips & tricks](https://medium.com/volosoft/asp-net-core-dependency-injection-best-practices-tips-tricks-c6e9c67f9d96) +* [ASP.NET Core's Dependency Injection documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection) \ No newline at end of file From 49d4a6ed1851cd204e90f2e2d028d93479cef686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 14 Apr 2022 11:31:40 +0300 Subject: [PATCH 3/3] Update POST.md --- .../Community-Articles/2022-04-14-Dependency-Injection/POST.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Community-Articles/2022-04-14-Dependency-Injection/POST.md b/docs/en/Community-Articles/2022-04-14-Dependency-Injection/POST.md index d6f8b9a5ba..edd74c1591 100644 --- a/docs/en/Community-Articles/2022-04-14-Dependency-Injection/POST.md +++ b/docs/en/Community-Articles/2022-04-14-Dependency-Injection/POST.md @@ -1,4 +1,4 @@ -# Dealing with Multiple Implementations of a Service in Dependency Injection +# Dealing with Multiple Implementations of a Service in ASP.NET Core & ABP Dependency Injection ASP.NET Core provides a built-in [dependency injection system](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection) to register your services to the dependency injection container and inject/resolve them whenever you need. ABP's [dependency injection infrastructure](https://docs.abp.io/en/abp/latest/Dependency-Injection) is built on ASP.NET Core's DI system, automates service registration by conventions and provides some additional features.