diff --git a/docs/en/Dependency-Injection.md b/docs/en/Dependency-Injection.md index 143ec8cc19..2d042a2b2a 100644 --- a/docs/en/Dependency-Injection.md +++ b/docs/en/Dependency-Injection.md @@ -143,6 +143,36 @@ public class TaxCalculator : ITaxCalculator, ITransientDependency } ```` +### ExposeKeyedService Attribute + +``ExposeKeyedServiceAttribute`` is used to control which keyed services are provided by the related class. Example: + +````C# +[ExposeKeyedService("taxCalculator")] +[ExposeKeyedService("calculator")] +public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency +{ +} +```` + +``TaxCalculator`` class exposes ``ITaxCalculator`` interface with the key ``tax`` and ``ICalculator`` interface with the key ``calculator``. That means you can get keyed services from the ``IServiceProvider`` as shown below: + +````C# +var taxCalculator = ServiceProvider.GetRequiredKeyedService("taxCalculator"); +var calculator = ServiceProvider.GetRequiredKeyedService("calculator"); +```` + +> Notice that the ``ExposeKeyedServiceAttribute`` is only expose keyed services. So, you can not inject ``ITaxCalculator`` or ``ICalculator`` in your application. If you want to expose both keyed and non-keyed services, you can use ``ExposeServicesAttribute`` and ``ExposeKeyedServiceAttribute`` together as shown below: + +````C# +[ExposeKeyedService("taxCalculator")] +[ExposeKeyedService("calculator")] +[ExposeServices(typeof(ITaxCalculator), typeof(ICalculator))] +public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency +{ +} +```` + ### Manually Registering In some cases, you may need to register a service to the `IServiceCollection` manually, especially if you need to use custom factory methods or singleton instances. In that case, you can directly add services just as [Microsoft documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection) describes. Example: diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ConventionalRegistrarBase.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ConventionalRegistrarBase.cs index c7190e6c4d..6d1c345840 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ConventionalRegistrarBase.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ConventionalRegistrarBase.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Reflection; @@ -40,6 +39,11 @@ public abstract class ConventionalRegistrarBase : IConventionalRegistrar } protected virtual void TriggerServiceExposing(IServiceCollection services, Type implementationType, List serviceTypes) + { + TriggerServiceExposing(services, implementationType, serviceTypes.ConvertAll(t => new ServiceIdentifier(t))); + } + + protected virtual void TriggerServiceExposing(IServiceCollection services, Type implementationType, List serviceTypes) { var exposeActions = services.GetExposingActionList(); if (exposeActions.Any()) @@ -92,10 +96,16 @@ public abstract class ConventionalRegistrarBase : IConventionalRegistrar return ExposedServiceExplorer.GetExposedServices(type); } + protected virtual List GetExposedKeyedServiceTypes(Type type) + { + return ExposedServiceExplorer.GetExposedKeyedServices(type); + } + protected virtual ServiceDescriptor CreateServiceDescriptor( Type implementationType, + object? serviceKey, Type exposingServiceType, - List allExposingServiceTypes, + List allExposingServiceTypes, ServiceLifetime lifeTime) { if (lifeTime.IsIn(ServiceLifetime.Singleton, ServiceLifetime.Scoped)) @@ -108,27 +118,41 @@ public abstract class ConventionalRegistrarBase : IConventionalRegistrar if (redirectedType != null) { - return ServiceDescriptor.Describe( - exposingServiceType, - provider => provider.GetService(redirectedType)!, - lifeTime - ); + return serviceKey == null + ? ServiceDescriptor.Describe( + exposingServiceType, + provider => provider.GetService(redirectedType)!, + lifeTime + ) + : ServiceDescriptor.DescribeKeyed( + exposingServiceType, + serviceKey, + (provider, key) => provider.GetKeyedService(redirectedType, key)!, + lifeTime + ); } } - return ServiceDescriptor.Describe( - exposingServiceType, - implementationType, - lifeTime - ); + return serviceKey == null + ? ServiceDescriptor.Describe( + exposingServiceType, + implementationType, + lifeTime + ) + : ServiceDescriptor.DescribeKeyed( + exposingServiceType, + serviceKey, + implementationType, + lifeTime + ); } protected virtual Type? GetRedirectedTypeOrNull( Type implementationType, Type exposingServiceType, - List allExposingServiceTypes) + List allExposingKeyedServiceTypes) { - if (allExposingServiceTypes.Count < 2) + if (allExposingKeyedServiceTypes.Count < 2) { return null; } @@ -138,14 +162,13 @@ public abstract class ConventionalRegistrarBase : IConventionalRegistrar return null; } - if (allExposingServiceTypes.Contains(implementationType)) + if (allExposingKeyedServiceTypes.Any(t => t.ServiceType == implementationType)) { return implementationType; } - return allExposingServiceTypes.FirstOrDefault( - t => t != exposingServiceType && exposingServiceType.IsAssignableFrom(t) - ); + return allExposingKeyedServiceTypes.FirstOrDefault( + t => t.ServiceType != exposingServiceType && exposingServiceType.IsAssignableFrom(t.ServiceType) + ).ServiceType; } - } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/DefaultConventionalRegistrar.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/DefaultConventionalRegistrar.cs index 18f36bfb48..0356ab450b 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/DefaultConventionalRegistrar.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/DefaultConventionalRegistrar.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -22,16 +24,21 @@ public class DefaultConventionalRegistrar : ConventionalRegistrarBase return; } - var exposedServiceTypes = GetExposedServiceTypes(type); + var exposedServiceAndKeyedServiceTypes = GetExposedKeyedServiceTypes(type).Concat(GetExposedServiceTypes(type).Select(t => new ServiceIdentifier(t))).ToList(); - TriggerServiceExposing(services, type, exposedServiceTypes); + TriggerServiceExposing(services, type, exposedServiceAndKeyedServiceTypes); - foreach (var exposedServiceType in exposedServiceTypes) + foreach (var exposedServiceType in exposedServiceAndKeyedServiceTypes) { + var allExposingServiceTypes = exposedServiceType.ServiceKey == null + ? exposedServiceAndKeyedServiceTypes.Where(x => x.ServiceKey == null).ToList() + : exposedServiceAndKeyedServiceTypes.Where(x => x.ServiceKey?.ToString() == exposedServiceType.ServiceKey?.ToString()).ToList(); + var serviceDescriptor = CreateServiceDescriptor( type, - exposedServiceType, - exposedServiceTypes, + exposedServiceType.ServiceKey, + exposedServiceType.ServiceType, + allExposingServiceTypes, lifeTime.Value ); diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposeKeyedServiceAttribute.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposeKeyedServiceAttribute.cs new file mode 100644 index 0000000000..a7af4d0a79 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposeKeyedServiceAttribute.cs @@ -0,0 +1,25 @@ +using System; + +namespace Volo.Abp.DependencyInjection; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +public class ExposeKeyedServiceAttribute : Attribute, IExposedKeyedServiceTypesProvider + where TServiceType : class +{ + public ServiceIdentifier ServiceIdentifier { get; } + + public ExposeKeyedServiceAttribute(object serviceKey) + { + if (serviceKey == null) + { + throw new AbpException($"{nameof(serviceKey)} can not be null! Use {nameof(ExposeServicesAttribute)} instead."); + } + + ServiceIdentifier = new ServiceIdentifier(serviceKey, typeof(TServiceType)); + } + + public ServiceIdentifier[] GetExposedServiceTypes(Type targetType) + { + return new[] { ServiceIdentifier }; + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposeServicesAttribute.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposeServicesAttribute.cs index 286b9a8c76..f05ce07239 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposeServicesAttribute.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposeServicesAttribute.cs @@ -5,6 +5,7 @@ using System.Reflection; namespace Volo.Abp.DependencyInjection; +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class ExposeServicesAttribute : Attribute, IExposedServiceTypesProvider { public Type[] ServiceTypes { get; } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposedServiceExplorer.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposedServiceExplorer.cs index 126c56fca2..318af5f96e 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposedServiceExplorer.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposedServiceExplorer.cs @@ -15,12 +15,31 @@ public static class ExposedServiceExplorer public static List GetExposedServices(Type type) { - return type + var exposedServiceTypesProviders = type .GetCustomAttributes(true) .OfType() + .ToList(); + + if (exposedServiceTypesProviders.IsNullOrEmpty() && type.GetCustomAttributes(true).OfType().Any()) + { + // If there is any IExposedKeyedServiceTypesProvider but no IExposedServiceTypesProvider, we will not expose the default services. + return Array.Empty().ToList(); + } + + return exposedServiceTypesProviders .DefaultIfEmpty(DefaultExposeServicesAttribute) .SelectMany(p => p.GetExposedServiceTypes(type)) .Distinct() .ToList(); } + + public static List GetExposedKeyedServices(Type type) + { + return type + .GetCustomAttributes(true) + .OfType() + .SelectMany(p => p.GetExposedServiceTypes(type)) + .Distinct() + .ToList(); + } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IExposedKeyedServiceTypesProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IExposedKeyedServiceTypesProvider.cs new file mode 100644 index 0000000000..84879904b5 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IExposedKeyedServiceTypesProvider.cs @@ -0,0 +1,8 @@ +using System; + +namespace Volo.Abp.DependencyInjection; + +public interface IExposedKeyedServiceTypesProvider +{ + ServiceIdentifier[] GetExposedServiceTypes(Type targetType); +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IOnServiceExposingContext.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IOnServiceExposingContext.cs index 5a96160be2..0dc1c42fe2 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IOnServiceExposingContext.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IOnServiceExposingContext.cs @@ -7,5 +7,5 @@ public interface IOnServiceExposingContext { Type ImplementationType { get; } - List ExposedTypes { get; } + List ExposedTypes { get; } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/OnServiceExposingContext.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/OnServiceExposingContext.cs index 7ce197aea8..2b09430222 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/OnServiceExposingContext.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/OnServiceExposingContext.cs @@ -8,9 +8,15 @@ public class OnServiceExposingContext : IOnServiceExposingContext { public Type ImplementationType { get; } - public List ExposedTypes { get; } + public List ExposedTypes { get; } public OnServiceExposingContext([NotNull] Type implementationType, List exposedTypes) + { + ImplementationType = Check.NotNull(implementationType, nameof(implementationType)); + ExposedTypes = Check.NotNull(exposedTypes, nameof(exposedTypes)).ConvertAll(t => new ServiceIdentifier(t)); + } + + public OnServiceExposingContext([NotNull] Type implementationType, List exposedTypes) { ImplementationType = Check.NotNull(implementationType, nameof(implementationType)); ExposedTypes = Check.NotNull(exposedTypes, nameof(exposedTypes)); diff --git a/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/AbpObjectMappingModule.cs b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/AbpObjectMappingModule.cs index cfd82a5547..e572d6e155 100644 --- a/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/AbpObjectMappingModule.cs +++ b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/AbpObjectMappingModule.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; using Volo.Abp.Modularity; using Volo.Abp.Reflection; @@ -10,12 +11,12 @@ public class AbpObjectMappingModule : AbpModule { context.Services.OnExposing(onServiceExposingContext => { - //Register types for IObjectMapper if implements - onServiceExposingContext.ExposedTypes.AddRange( + //Register types for IObjectMapper if implements + onServiceExposingContext.ExposedTypes.AddRange( ReflectionHelper.GetImplementedGenericTypes( onServiceExposingContext.ImplementationType, typeof(IObjectMapper<,>) - ) + ).ConvertAll(t => new ServiceIdentifier(t)) ); }); } diff --git a/framework/src/Volo.Abp.Serialization/Volo/Abp/Serialization/AbpSerializationModule.cs b/framework/src/Volo.Abp.Serialization/Volo/Abp/Serialization/AbpSerializationModule.cs index 428f0415c8..5a178ba01b 100644 --- a/framework/src/Volo.Abp.Serialization/Volo/Abp/Serialization/AbpSerializationModule.cs +++ b/framework/src/Volo.Abp.Serialization/Volo/Abp/Serialization/AbpSerializationModule.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; using Volo.Abp.Modularity; using Volo.Abp.Reflection; @@ -10,12 +11,12 @@ public class AbpSerializationModule : AbpModule { context.Services.OnExposing(onServiceExposingContext => { - //Register types for IObjectSerializer if implements - onServiceExposingContext.ExposedTypes.AddRange( + //Register types for IObjectSerializer if implements + onServiceExposingContext.ExposedTypes.AddRange( ReflectionHelper.GetImplementedGenericTypes( onServiceExposingContext.ImplementationType, typeof(IObjectSerializer<>) - ) + ).ConvertAll(t => new ServiceIdentifier(t)) ); }); } diff --git a/framework/test/Volo.Abp.Core.Tests/Microsoft/Extensions/DependencyInjection/DependencyInjection_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Microsoft/Extensions/DependencyInjection/DependencyInjection_Tests.cs index be6b6d68f5..8e3975708c 100644 --- a/framework/test/Volo.Abp.Core.Tests/Microsoft/Extensions/DependencyInjection/DependencyInjection_Tests.cs +++ b/framework/test/Volo.Abp.Core.Tests/Microsoft/Extensions/DependencyInjection/DependencyInjection_Tests.cs @@ -79,6 +79,25 @@ public abstract class DependencyInjection_Standard_Tests : AbpIntegratedTest>().DisablePropertyInjectionService.ShouldBeNull(); } + + [Fact] + public void ExposeKeyedServices_Should_Expose_Correct_Services() + { + GetService().ShouldBeNull(); + GetService().ShouldBeNull(); + GetService().ShouldBeNull(); + + GetRequiredKeyedService("k1").ShouldNotBeNull(); + GetRequiredKeyedService("k1").ShouldNotBeNull(); + + GetRequiredKeyedService("k2").ShouldNotBeNull(); + GetRequiredKeyedService("k2").ShouldNotBeNull(); + + GetService().ShouldNotBeNull(); + GetRequiredKeyedService("k3").ShouldNotBeNull(); + GetRequiredKeyedService("k3").ShouldNotBeNull(); + } + [Fact] public void Singletons_Exposing_Multiple_Services_Should_Returns_The_Same_Instance() { @@ -86,6 +105,11 @@ public abstract class DependencyInjection_Standard_Tests : AbpIntegratedTest(); ReferenceEquals(objectByInterfaceRef, objectByClassRef).ShouldBeTrue(); + + objectByInterfaceRef = GetRequiredKeyedService("k1"); + objectByClassRef = GetRequiredKeyedService("k1"); + + ReferenceEquals(objectByInterfaceRef, objectByClassRef).ShouldBeTrue(); } [Fact] @@ -166,11 +190,40 @@ public abstract class DependencyInjection_Standard_Tests : AbpIntegratedTest("k1")] + [ExposeKeyedService("k1")] public class MySingletonExposingMultipleServices : IMySingletonExposingMultipleServices, ISingletonDependency { } + public interface IMyExposingKeyedServices + { + + } + + [ExposeKeyedService("k1")] + [ExposeKeyedService("k1")] + public class MyExposingKeyedService1 : IMyExposingKeyedServices, ITransientDependency + { + + } + + [ExposeKeyedService("k2")] + [ExposeKeyedService("k2")] + public class MyExposingKeyedService2 : IMyExposingKeyedServices, ITransientDependency + { + + } + + [ExposeServices(typeof(MyExposingKeyedService3))] + [ExposeKeyedService("k3")] + [ExposeKeyedService("k3")] + public class MyExposingKeyedService3 : IMyExposingKeyedServices, ITransientDependency + { + + } + public class TestModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context)