Browse Source

Introduce `ExposeKeyedServicesAttribute`.

pull/18819/head
maliming 2 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
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.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<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();
if (exposeActions.Any())
@ -92,10 +96,16 @@ public abstract class ConventionalRegistrarBase : IConventionalRegistrar
return ExposedServiceExplorer.GetExposedServices(type);
}
protected virtual List<ServiceIdentifier> GetExposedKeyedServiceTypes(Type type)
{
return ExposedServiceExplorer.GetExposedKeyedServices(type);
}
protected virtual ServiceDescriptor CreateServiceDescriptor(
Type implementationType,
object? serviceKey,
Type exposingServiceType,
List<Type> allExposingServiceTypes,
List<ServiceIdentifier> 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<Type> allExposingServiceTypes)
List<ServiceIdentifier> 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;
}
}

17
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
);

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;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class ExposeServicesAttribute : Attribute, IExposedServiceTypesProvider
{
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)
{
return type
var exposedServiceTypesProviders = type
.GetCustomAttributes(true)
.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)
.SelectMany(p => p.GetExposedServiceTypes(type))
.Distinct()
.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; }
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 List<Type> ExposedTypes { get; }
public List<ServiceIdentifier> ExposedTypes { get; }
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));
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 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<TSource, TDestination> if implements
onServiceExposingContext.ExposedTypes.AddRange(
//Register types for IObjectMapper<TSource, TDestination> if implements
onServiceExposingContext.ExposedTypes.AddRange(
ReflectionHelper.GetImplementedGenericTypes(
onServiceExposingContext.ImplementationType,
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 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<T> if implements
onServiceExposingContext.ExposedTypes.AddRange(
//Register types for IObjectSerializer<T> if implements
onServiceExposingContext.ExposedTypes.AddRange(
ReflectionHelper.GetImplementedGenericTypes(
onServiceExposingContext.ImplementationType,
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();
}
[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]
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>();
ReferenceEquals(objectByInterfaceRef, objectByClassRef).ShouldBeTrue();
objectByInterfaceRef = GetRequiredKeyedService<IMySingletonExposingMultipleServices>("k1");
objectByClassRef = GetRequiredKeyedService<MySingletonExposingMultipleServices>("k1");
ReferenceEquals(objectByInterfaceRef, objectByClassRef).ShouldBeTrue();
}
[Fact]
@ -166,11 +190,40 @@ public abstract class DependencyInjection_Standard_Tests : AbpIntegratedTest<Dep
}
[ExposeServices(typeof(IMySingletonExposingMultipleServices), typeof(MySingletonExposingMultipleServices))]
[ExposeKeyedService<IMySingletonExposingMultipleServices>("k1")]
[ExposeKeyedService<MySingletonExposingMultipleServices>("k1")]
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 override void ConfigureServices(ServiceConfigurationContext context)

Loading…
Cancel
Save