Browse Source

Introduce `ExposeKeyedServicesAttribute`.

pull/18819/head
maliming 3 years ago
parent
commit
2218e9d7a1
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 30
      docs/en/Dependency-Injection.md
  2. 61
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ConventionalRegistrarBase.cs
  3. 17
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/DefaultConventionalRegistrar.cs
  4. 25
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposeKeyedServiceAttribute.cs
  5. 1
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposeServicesAttribute.cs
  6. 21
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposedServiceExplorer.cs
  7. 8
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IExposedKeyedServiceTypesProvider.cs
  8. 2
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IOnServiceExposingContext.cs
  9. 8
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/OnServiceExposingContext.cs
  10. 7
      framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/AbpObjectMappingModule.cs
  11. 7
      framework/src/Volo.Abp.Serialization/Volo/Abp/Serialization/AbpSerializationModule.cs
  12. 53
      framework/test/Volo.Abp.Core.Tests/Microsoft/Extensions/DependencyInjection/DependencyInjection_Tests.cs

30
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<ITaxCalculator>("taxCalculator")]
[ExposeKeyedService<ICalculator>("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<ITaxCalculator>("taxCalculator");
var calculator = ServiceProvider.GetRequiredKeyedService<ICalculator>("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<ITaxCalculator>("taxCalculator")]
[ExposeKeyedService<ICalculator>("calculator")]
[ExposeServices(typeof(ITaxCalculator), typeof(ICalculator))]
public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency
{
}
````
### Manually Registering ### 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: 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:

61
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ConventionalRegistrarBase.cs

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Reflection; using Volo.Abp.Reflection;
@ -40,6 +39,11 @@ public abstract class ConventionalRegistrarBase : IConventionalRegistrar
} }
protected virtual void TriggerServiceExposing(IServiceCollection services, Type implementationType, List<Type> serviceTypes) protected virtual void TriggerServiceExposing(IServiceCollection services, Type implementationType, List<Type> serviceTypes)
{
TriggerServiceExposing(services, implementationType, serviceTypes.ConvertAll(t => new ServiceIdentifier(t)));
}
protected virtual void TriggerServiceExposing(IServiceCollection services, Type implementationType, List<ServiceIdentifier> serviceTypes)
{ {
var exposeActions = services.GetExposingActionList(); var exposeActions = services.GetExposingActionList();
if (exposeActions.Any()) if (exposeActions.Any())
@ -92,10 +96,16 @@ public abstract class ConventionalRegistrarBase : IConventionalRegistrar
return ExposedServiceExplorer.GetExposedServices(type); return ExposedServiceExplorer.GetExposedServices(type);
} }
protected virtual List<ServiceIdentifier> GetExposedKeyedServiceTypes(Type type)
{
return ExposedServiceExplorer.GetExposedKeyedServices(type);
}
protected virtual ServiceDescriptor CreateServiceDescriptor( protected virtual ServiceDescriptor CreateServiceDescriptor(
Type implementationType, Type implementationType,
object? serviceKey,
Type exposingServiceType, Type exposingServiceType,
List<Type> allExposingServiceTypes, List<ServiceIdentifier> allExposingServiceTypes,
ServiceLifetime lifeTime) ServiceLifetime lifeTime)
{ {
if (lifeTime.IsIn(ServiceLifetime.Singleton, ServiceLifetime.Scoped)) if (lifeTime.IsIn(ServiceLifetime.Singleton, ServiceLifetime.Scoped))
@ -108,27 +118,41 @@ public abstract class ConventionalRegistrarBase : IConventionalRegistrar
if (redirectedType != null) if (redirectedType != null)
{ {
return ServiceDescriptor.Describe( return serviceKey == null
exposingServiceType, ? ServiceDescriptor.Describe(
provider => provider.GetService(redirectedType)!, exposingServiceType,
lifeTime provider => provider.GetService(redirectedType)!,
); lifeTime
)
: ServiceDescriptor.DescribeKeyed(
exposingServiceType,
serviceKey,
(provider, key) => provider.GetKeyedService(redirectedType, key)!,
lifeTime
);
} }
} }
return ServiceDescriptor.Describe( return serviceKey == null
exposingServiceType, ? ServiceDescriptor.Describe(
implementationType, exposingServiceType,
lifeTime implementationType,
); lifeTime
)
: ServiceDescriptor.DescribeKeyed(
exposingServiceType,
serviceKey,
implementationType,
lifeTime
);
} }
protected virtual Type? GetRedirectedTypeOrNull( protected virtual Type? GetRedirectedTypeOrNull(
Type implementationType, Type implementationType,
Type exposingServiceType, Type exposingServiceType,
List<Type> allExposingServiceTypes) List<ServiceIdentifier> allExposingKeyedServiceTypes)
{ {
if (allExposingServiceTypes.Count < 2) if (allExposingKeyedServiceTypes.Count < 2)
{ {
return null; return null;
} }
@ -138,14 +162,13 @@ public abstract class ConventionalRegistrarBase : IConventionalRegistrar
return null; return null;
} }
if (allExposingServiceTypes.Contains(implementationType)) if (allExposingKeyedServiceTypes.Any(t => t.ServiceType == implementationType))
{ {
return implementationType; return implementationType;
} }
return allExposingServiceTypes.FirstOrDefault( return allExposingKeyedServiceTypes.FirstOrDefault(
t => t != exposingServiceType && exposingServiceType.IsAssignableFrom(t) t => t.ServiceType != exposingServiceType && exposingServiceType.IsAssignableFrom(t.ServiceType)
); ).ServiceType;
} }
} }

17
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/DefaultConventionalRegistrar.cs

@ -1,4 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
@ -22,16 +24,21 @@ public class DefaultConventionalRegistrar : ConventionalRegistrarBase
return; 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( var serviceDescriptor = CreateServiceDescriptor(
type, type,
exposedServiceType, exposedServiceType.ServiceKey,
exposedServiceTypes, exposedServiceType.ServiceType,
allExposingServiceTypes,
lifeTime.Value lifeTime.Value
); );

25
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<TServiceType> : 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 };
}
}

1
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposeServicesAttribute.cs

@ -5,6 +5,7 @@ using System.Reflection;
namespace Volo.Abp.DependencyInjection; namespace Volo.Abp.DependencyInjection;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class ExposeServicesAttribute : Attribute, IExposedServiceTypesProvider public class ExposeServicesAttribute : Attribute, IExposedServiceTypesProvider
{ {
public Type[] ServiceTypes { get; } public Type[] ServiceTypes { get; }

21
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposedServiceExplorer.cs

@ -15,12 +15,31 @@ public static class ExposedServiceExplorer
public static List<Type> GetExposedServices(Type type) public static List<Type> GetExposedServices(Type type)
{ {
return type var exposedServiceTypesProviders = type
.GetCustomAttributes(true) .GetCustomAttributes(true)
.OfType<IExposedServiceTypesProvider>() .OfType<IExposedServiceTypesProvider>()
.ToList();
if (exposedServiceTypesProviders.IsNullOrEmpty() && type.GetCustomAttributes(true).OfType<IExposedKeyedServiceTypesProvider>().Any())
{
// If there is any IExposedKeyedServiceTypesProvider but no IExposedServiceTypesProvider, we will not expose the default services.
return Array.Empty<Type>().ToList();
}
return exposedServiceTypesProviders
.DefaultIfEmpty(DefaultExposeServicesAttribute) .DefaultIfEmpty(DefaultExposeServicesAttribute)
.SelectMany(p => p.GetExposedServiceTypes(type)) .SelectMany(p => p.GetExposedServiceTypes(type))
.Distinct() .Distinct()
.ToList(); .ToList();
} }
public static List<ServiceIdentifier> GetExposedKeyedServices(Type type)
{
return type
.GetCustomAttributes(true)
.OfType<IExposedKeyedServiceTypesProvider>()
.SelectMany(p => p.GetExposedServiceTypes(type))
.Distinct()
.ToList();
}
} }

8
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);
}

2
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IOnServiceExposingContext.cs

@ -7,5 +7,5 @@ public interface IOnServiceExposingContext
{ {
Type ImplementationType { get; } Type ImplementationType { get; }
List<Type> ExposedTypes { get; } List<ServiceIdentifier> ExposedTypes { get; }
} }

8
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/OnServiceExposingContext.cs

@ -8,9 +8,15 @@ public class OnServiceExposingContext : IOnServiceExposingContext
{ {
public Type ImplementationType { get; } public Type ImplementationType { get; }
public List<Type> ExposedTypes { get; } public List<ServiceIdentifier> ExposedTypes { get; }
public OnServiceExposingContext([NotNull] Type implementationType, List<Type> exposedTypes) public OnServiceExposingContext([NotNull] Type implementationType, List<Type> exposedTypes)
{
ImplementationType = Check.NotNull(implementationType, nameof(implementationType));
ExposedTypes = Check.NotNull(exposedTypes, nameof(exposedTypes)).ConvertAll(t => new ServiceIdentifier(t));
}
public OnServiceExposingContext([NotNull] Type implementationType, List<ServiceIdentifier> exposedTypes)
{ {
ImplementationType = Check.NotNull(implementationType, nameof(implementationType)); ImplementationType = Check.NotNull(implementationType, nameof(implementationType));
ExposedTypes = Check.NotNull(exposedTypes, nameof(exposedTypes)); ExposedTypes = Check.NotNull(exposedTypes, nameof(exposedTypes));

7
framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/AbpObjectMappingModule.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.Reflection; using Volo.Abp.Reflection;
@ -10,12 +11,12 @@ public class AbpObjectMappingModule : AbpModule
{ {
context.Services.OnExposing(onServiceExposingContext => context.Services.OnExposing(onServiceExposingContext =>
{ {
//Register types for IObjectMapper<TSource, TDestination> if implements //Register types for IObjectMapper<TSource, TDestination> if implements
onServiceExposingContext.ExposedTypes.AddRange( onServiceExposingContext.ExposedTypes.AddRange(
ReflectionHelper.GetImplementedGenericTypes( ReflectionHelper.GetImplementedGenericTypes(
onServiceExposingContext.ImplementationType, onServiceExposingContext.ImplementationType,
typeof(IObjectMapper<,>) typeof(IObjectMapper<,>)
) ).ConvertAll(t => new ServiceIdentifier(t))
); );
}); });
} }

7
framework/src/Volo.Abp.Serialization/Volo/Abp/Serialization/AbpSerializationModule.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.Reflection; using Volo.Abp.Reflection;
@ -10,12 +11,12 @@ public class AbpSerializationModule : AbpModule
{ {
context.Services.OnExposing(onServiceExposingContext => context.Services.OnExposing(onServiceExposingContext =>
{ {
//Register types for IObjectSerializer<T> if implements //Register types for IObjectSerializer<T> if implements
onServiceExposingContext.ExposedTypes.AddRange( onServiceExposingContext.ExposedTypes.AddRange(
ReflectionHelper.GetImplementedGenericTypes( ReflectionHelper.GetImplementedGenericTypes(
onServiceExposingContext.ImplementationType, onServiceExposingContext.ImplementationType,
typeof(IObjectSerializer<>) typeof(IObjectSerializer<>)
) ).ConvertAll(t => new ServiceIdentifier(t))
); );
}); });
} }

53
framework/test/Volo.Abp.Core.Tests/Microsoft/Extensions/DependencyInjection/DependencyInjection_Tests.cs

@ -79,6 +79,25 @@ public abstract class DependencyInjection_Standard_Tests : AbpIntegratedTest<Dep
GetRequiredService<GenericServiceWithDisablePropertyInjectionOnProperty<string>>().DisablePropertyInjectionService.ShouldBeNull(); GetRequiredService<GenericServiceWithDisablePropertyInjectionOnProperty<string>>().DisablePropertyInjectionService.ShouldBeNull();
} }
[Fact]
public void ExposeKeyedServices_Should_Expose_Correct_Services()
{
GetService<IMyExposingKeyedServices>().ShouldBeNull();
GetService<MyExposingKeyedService1>().ShouldBeNull();
GetService<MyExposingKeyedService2>().ShouldBeNull();
GetRequiredKeyedService<IMyExposingKeyedServices>("k1").ShouldNotBeNull();
GetRequiredKeyedService<MyExposingKeyedService1>("k1").ShouldNotBeNull();
GetRequiredKeyedService<IMyExposingKeyedServices>("k2").ShouldNotBeNull();
GetRequiredKeyedService<MyExposingKeyedService2>("k2").ShouldNotBeNull();
GetService<MyExposingKeyedService3>().ShouldNotBeNull();
GetRequiredKeyedService<IMyExposingKeyedServices>("k3").ShouldNotBeNull();
GetRequiredKeyedService<MyExposingKeyedService3>("k3").ShouldNotBeNull();
}
[Fact] [Fact]
public void Singletons_Exposing_Multiple_Services_Should_Returns_The_Same_Instance() public void Singletons_Exposing_Multiple_Services_Should_Returns_The_Same_Instance()
{ {
@ -86,6 +105,11 @@ public abstract class DependencyInjection_Standard_Tests : AbpIntegratedTest<Dep
var objectByClassRef = GetRequiredService<MySingletonExposingMultipleServices>(); var objectByClassRef = GetRequiredService<MySingletonExposingMultipleServices>();
ReferenceEquals(objectByInterfaceRef, objectByClassRef).ShouldBeTrue(); ReferenceEquals(objectByInterfaceRef, objectByClassRef).ShouldBeTrue();
objectByInterfaceRef = GetRequiredKeyedService<IMySingletonExposingMultipleServices>("k1");
objectByClassRef = GetRequiredKeyedService<MySingletonExposingMultipleServices>("k1");
ReferenceEquals(objectByInterfaceRef, objectByClassRef).ShouldBeTrue();
} }
[Fact] [Fact]
@ -166,11 +190,40 @@ public abstract class DependencyInjection_Standard_Tests : AbpIntegratedTest<Dep
} }
[ExposeServices(typeof(IMySingletonExposingMultipleServices), typeof(MySingletonExposingMultipleServices))] [ExposeServices(typeof(IMySingletonExposingMultipleServices), typeof(MySingletonExposingMultipleServices))]
[ExposeKeyedService<IMySingletonExposingMultipleServices>("k1")]
[ExposeKeyedService<MySingletonExposingMultipleServices>("k1")]
public class MySingletonExposingMultipleServices : IMySingletonExposingMultipleServices, ISingletonDependency public class MySingletonExposingMultipleServices : IMySingletonExposingMultipleServices, ISingletonDependency
{ {
} }
public interface IMyExposingKeyedServices
{
}
[ExposeKeyedService<IMyExposingKeyedServices>("k1")]
[ExposeKeyedService<MyExposingKeyedService1>("k1")]
public class MyExposingKeyedService1 : IMyExposingKeyedServices, ITransientDependency
{
}
[ExposeKeyedService<IMyExposingKeyedServices>("k2")]
[ExposeKeyedService<MyExposingKeyedService2>("k2")]
public class MyExposingKeyedService2 : IMyExposingKeyedServices, ITransientDependency
{
}
[ExposeServices(typeof(MyExposingKeyedService3))]
[ExposeKeyedService<IMyExposingKeyedServices>("k3")]
[ExposeKeyedService<MyExposingKeyedService3>("k3")]
public class MyExposingKeyedService3 : IMyExposingKeyedServices, ITransientDependency
{
}
public class TestModule : AbpModule public class TestModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)

Loading…
Cancel
Save