Skip to content

Commit

Permalink
Merge pull request #18819 from abpframework/ExposeKeyedServicesAttribute
Browse files Browse the repository at this point in the history
Introduce `ExposeKeyedServiceAttribute`.
  • Loading branch information
EngincanV committed Jan 22, 2024
2 parents e18c384 + 0e0c461 commit 206f1ba
Show file tree
Hide file tree
Showing 12 changed files with 232 additions and 33 deletions.
44 changes: 44 additions & 0 deletions docs/en/Dependency-Injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,50 @@ 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
{
}
````

In the example above, the `TaxCalculator` class exposes the `ITaxCalculator` interface with the key `taxCalculator` and the `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");
````

Also, you can use the [`FromKeyedServicesAttribute`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.fromkeyedservicesattribute?view=dotnet-plat-ext-8.0) to resolve a certain keyed service in the constructor:

```csharp
public class MyClass
{
//...
public MyClass([FromKeyedServices("taxCalculator")] ITaxCalculator taxCalculator)
{
TaxCalculator = taxCalculator;
}
}
```

> Notice that the `ExposeKeyedServiceAttribute` only exposes the keyed services. So, you can not inject the `ITaxCalculator` or `ICalculator` interfaces in your application without using the `FromKeyedServicesAttribute` as shown in the example above. If you want to expose both keyed and non-keyed services, you can use the `ExposeServicesAttribute` and `ExposeKeyedServiceAttribute` attributes 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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -40,6 +39,11 @@ protected virtual bool IsConventionalRegistrationDisabled(Type type)
}

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())
Expand Down Expand Up @@ -92,10 +96,16 @@ protected virtual List<Type> GetExposedServiceTypes(Type type)
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))
Expand All @@ -108,27 +118,41 @@ protected virtual List<Type> GetExposedServiceTypes(Type type)

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;
}
Expand All @@ -138,14 +162,13 @@ protected virtual List<Type> GetExposedServiceTypes(Type type)
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;
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

Expand All @@ -22,16 +24,21 @@ public override void AddType(IServiceCollection services, Type type)
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
);

Expand Down
Original file line number Diff line number Diff line change
@@ -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 };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

namespace Volo.Abp.DependencyInjection;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class ExposeServicesAttribute : Attribute, IExposedServiceTypesProvider
{
public Type[] ServiceTypes { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System;

namespace Volo.Abp.DependencyInjection;

public interface IExposedKeyedServiceTypesProvider
{
ServiceIdentifier[] GetExposedServiceTypes(Type targetType);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ public interface IOnServiceExposingContext
{
Type ImplementationType { get; }

List<Type> ExposedTypes { get; }
List<ServiceIdentifier> ExposedTypes { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Modularity;
using Volo.Abp.Reflection;

Expand All @@ -10,12 +11,12 @@ public override void PreConfigureServices(ServiceConfigurationContext context)
{
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))
);
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Modularity;
using Volo.Abp.Reflection;

Expand All @@ -10,12 +11,12 @@ public override void PreConfigureServices(ServiceConfigurationContext context)
{
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))
);
});
}
Expand Down

0 comments on commit 206f1ba

Please sign in to comment.