Browse Source

Merge pull request #18792 from abpframework/IKeyedServiceProvider

Inherit `IKeyedServiceProvider` to support keyed services.
pull/18818/head
Halil İbrahim Kalkan 3 years ago
committed by GitHub
parent
commit
9e80d3a607
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 28
      framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacRegistration.cs
  2. 4
      framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionCommonExtensions.cs
  3. 31
      framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceDescriptorExtensions.cs
  4. 19
      framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceProviderKeyedServiceExtensions.cs
  5. 31
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs
  6. 7
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProviderBase.cs
  7. 8
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IRootServiceProviderAccessor.cs
  8. 15
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/RootServiceProvider.cs
  9. 54
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ServiceIdentifier.cs
  10. 38
      framework/test/Volo.Abp.Core.Tests/Volo/Abp/DependencyInjection/CachedServiceProvider_Tests.cs

28
framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacRegistration.cs

@ -298,32 +298,4 @@ public static class AutofacRegistration
.ConfigureLifecycle(descriptor.Lifetime, null);
}
}
/// <summary>
/// Normalizes the implementation instance data between keyed and not keyed services.
/// </summary>
/// <param name="descriptor">
/// The <see cref="T:Microsoft.Extensions.DependencyInjection.ServiceDescriptor" /> to normalize.
/// </param>
/// <returns>
/// The appropriate implementation instance from the service descriptor.
/// </returns>
public static object? NormalizedImplementationInstance(this ServiceDescriptor descriptor)
{
return !descriptor.IsKeyedService ? descriptor.ImplementationInstance : descriptor.KeyedImplementationInstance;
}
/// <summary>
/// Normalizes the implementation type data between keyed and not keyed services.
/// </summary>
/// <param name="descriptor">
/// The <see cref="T:Microsoft.Extensions.DependencyInjection.ServiceDescriptor" /> to normalize.
/// </param>
/// <returns>
/// The appropriate implementation type from the service descriptor.
/// </returns>
public static Type? NormalizedImplementationType(this ServiceDescriptor descriptor)
{
return !descriptor.IsKeyedService ? descriptor.ImplementationType : descriptor.KeyedImplementationType;
}
}

4
framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionCommonExtensions.cs

@ -28,7 +28,7 @@ public static class ServiceCollectionCommonExtensions
{
return (T?)services
.FirstOrDefault(d => d.ServiceType == typeof(T))
?.ImplementationInstance;
?.NormalizedImplementationInstance();
}
public static T GetSingletonInstance<T>(this IServiceCollection services)
@ -48,7 +48,7 @@ public static class ServiceCollectionCommonExtensions
foreach (var service in services)
{
var factoryInterface = service.ImplementationInstance?.GetType()
var factoryInterface = service.NormalizedImplementationInstance()?.GetType()
.GetTypeInfo()
.GetInterfaces()
.FirstOrDefault(i => i.GetTypeInfo().IsGenericType &&

31
framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceDescriptorExtensions.cs

@ -0,0 +1,31 @@
using System;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Extensions for working with <see cref="ServiceDescriptor"/>.
/// </summary>
public static class ServiceDescriptorExtensions
{
/// <summary>
/// Normalizes the implementation instance data between keyed and not keyed services.
/// </summary>
/// <param name="descriptor">
/// The <see cref="ServiceDescriptor"/> to normalize.
/// </param>
/// <returns>
/// The appropriate implementation instance from the service descriptor.
/// </returns>
public static object? NormalizedImplementationInstance(this ServiceDescriptor descriptor) => descriptor.IsKeyedService ? descriptor.KeyedImplementationInstance : descriptor.ImplementationInstance;
/// <summary>
/// Normalizes the implementation type data between keyed and not keyed services.
/// </summary>
/// <param name="descriptor">
/// The <see cref="ServiceDescriptor"/> to normalize.
/// </param>
/// <returns>
/// The appropriate implementation type from the service descriptor.
/// </returns>
public static Type? NormalizedImplementationType(this ServiceDescriptor descriptor) => descriptor.IsKeyedService ? descriptor.KeyedImplementationType : descriptor.ImplementationType;
}

19
framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceProviderKeyedServiceExtensions.cs

@ -0,0 +1,19 @@
using System;
using Volo.Abp;
namespace Microsoft.Extensions.DependencyInjection;
public static class ServiceProviderKeyedServiceExtensions
{
public static object? GetKeyedService(this IServiceProvider provider, Type serviceType, object? serviceKey)
{
Check.NotNull(provider, nameof(provider));
if (provider is IKeyedServiceProvider keyedServiceProvider)
{
return keyedServiceProvider.GetKeyedService(serviceType, serviceKey);
}
throw new InvalidOperationException("This service provider doesn't support keyed services.");
}
}

31
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs

@ -1,28 +1,29 @@
using System;
using System.Collections.Concurrent;
using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.DependencyInjection;
public abstract class CachedServiceProviderBase : ICachedServiceProviderBase
{
protected IServiceProvider ServiceProvider { get; }
protected ConcurrentDictionary<Type, Lazy<object?>> CachedServices { get; }
protected ConcurrentDictionary<ServiceIdentifier, Lazy<object?>> CachedServices { get; }
protected CachedServiceProviderBase(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
CachedServices = new ConcurrentDictionary<Type, Lazy<object?>>();
CachedServices.TryAdd(typeof(IServiceProvider), new Lazy<object?>(() => ServiceProvider));
CachedServices = new ConcurrentDictionary<ServiceIdentifier, Lazy<object?>>();
CachedServices.TryAdd(new ServiceIdentifier(typeof(IServiceProvider)), new Lazy<object?>(() => ServiceProvider));
}
public virtual object? GetService(Type serviceType)
{
return CachedServices.GetOrAdd(
serviceType,
new ServiceIdentifier(serviceType),
_ => new Lazy<object?>(() => ServiceProvider.GetService(serviceType))
).Value;
}
public T GetService<T>(T defaultValue)
{
return (T)GetService(typeof(T), defaultValue!);
@ -37,12 +38,28 @@ public abstract class CachedServiceProviderBase : ICachedServiceProviderBase
{
return (T)GetService(typeof(T), factory);
}
public object GetService(Type serviceType, Func<IServiceProvider, object> factory)
{
return CachedServices.GetOrAdd(
serviceType,
new ServiceIdentifier(serviceType),
_ => new Lazy<object?>(() => factory(ServiceProvider))
).Value!;
}
public object? GetKeyedService(Type serviceType, object? serviceKey)
{
return CachedServices.GetOrAdd(
new ServiceIdentifier(serviceKey, serviceType),
_ => new Lazy<object?>(() => ServiceProvider.GetKeyedService(serviceType, serviceKey))
).Value;
}
public object GetRequiredKeyedService(Type serviceType, object? serviceKey)
{
return CachedServices.GetOrAdd(
new ServiceIdentifier(serviceKey, serviceType),
_ => new Lazy<object?>(() => ServiceProvider.GetRequiredKeyedService(serviceType, serviceKey))
).Value!;
}
}

7
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProviderBase.cs

@ -1,14 +1,15 @@
using System;
using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.DependencyInjection;
public interface ICachedServiceProviderBase : IServiceProvider
public interface ICachedServiceProviderBase : IKeyedServiceProvider
{
T GetService<T>(T defaultValue);
object GetService(Type serviceType, object defaultValue);
T GetService<T>(Func<IServiceProvider, object> factory);
object GetService(Type serviceType, Func<IServiceProvider, object> factory);
}
}

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

@ -1,4 +1,4 @@
using System;
using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.DependencyInjection;
@ -8,7 +8,7 @@ namespace Volo.Abp.DependencyInjection;
/// to release/dispose objects resolved from the root service provider.
/// So, always create a new scope if you need to resolve any service.
/// </summary>
public interface IRootServiceProvider : IServiceProvider
public interface IRootServiceProvider : IKeyedServiceProvider
{
}
}

15
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/RootServiceProvider.cs

@ -1,4 +1,5 @@
using System;
using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.DependencyInjection;
@ -6,7 +7,7 @@ namespace Volo.Abp.DependencyInjection;
public class RootServiceProvider : IRootServiceProvider, ISingletonDependency
{
protected IServiceProvider ServiceProvider { get; }
public RootServiceProvider(IObjectAccessor<IServiceProvider> objectAccessor)
{
ServiceProvider = objectAccessor.Value!;
@ -16,4 +17,14 @@ public class RootServiceProvider : IRootServiceProvider, ISingletonDependency
{
return ServiceProvider.GetService(serviceType);
}
}
public object? GetKeyedService(Type serviceType, object? serviceKey)
{
return ServiceProvider.GetKeyedService(serviceType, serviceKey);
}
public virtual object GetRequiredKeyedService(Type serviceType, object? serviceKey)
{
return ServiceProvider.GetRequiredKeyedService(serviceType, serviceKey);
}
}

54
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ServiceIdentifier.cs

@ -0,0 +1,54 @@
using System;
namespace Volo.Abp.DependencyInjection;
/// <summary>
/// https://github.com/dotnet/runtime/blob/release/8.0/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceIdentifier.cs#L9
/// </summary>
public readonly struct ServiceIdentifier : IEquatable<ServiceIdentifier>
{
public object? ServiceKey { get; }
public Type ServiceType { get; }
public ServiceIdentifier(Type serviceType)
{
ServiceType = serviceType;
}
public ServiceIdentifier(object? serviceKey, Type serviceType)
{
ServiceKey = serviceKey;
ServiceType = serviceType;
}
public bool Equals(ServiceIdentifier other)
{
if (ServiceKey == null && other.ServiceKey == null)
{
return ServiceType == other.ServiceType;
}
else if (ServiceKey != null && other.ServiceKey != null)
{
return ServiceType == other.ServiceType && ServiceKey.Equals(other.ServiceKey);
}
return false;
}
public override bool Equals(object? obj)
{
return obj is ServiceIdentifier && Equals((ServiceIdentifier)obj);
}
public override int GetHashCode()
{
if (ServiceKey == null)
{
return ServiceType.GetHashCode();
}
unchecked
{
return (ServiceType.GetHashCode() * 397) ^ ServiceKey.GetHashCode();
}
}
}

38
framework/test/Volo.Abp.Core.Tests/Volo/Abp/DependencyInjection/CachedServiceProvider_Tests.cs

@ -21,7 +21,11 @@ public class CachedServiceProvider_Tests
var transientTestService1 = cachedServiceProvider1.GetRequiredService<TransientTestService>();
var transientTestService2 = cachedServiceProvider2.GetRequiredService<TransientTestService>();
transientTestService1.ShouldBeSameAs(transientTestService2);
var transientKeyedTestService1 = cachedServiceProvider1.GetRequiredKeyedService<TransientKeyedTestService>("key1");
var transientKeyedTestService2 = cachedServiceProvider2.GetRequiredKeyedService<TransientKeyedTestService>("key1");
transientKeyedTestService1.ShouldBeSameAs(transientKeyedTestService2);
var cachedServiceProvider3 = cachedServiceProvider1.GetRequiredService<IServiceProvider>().GetRequiredService<ICachedServiceProvider>();
cachedServiceProvider3.ShouldBeSameAs(cachedServiceProvider1);
}
@ -36,18 +40,18 @@ public class CachedServiceProvider_Tests
var testCounter = scope1.ServiceProvider.GetRequiredService<ITestCounter>();
testCounter.GetValue(nameof(TransientTestService)).ShouldBe(1);
}
using (var scope2 = application.ServiceProvider.CreateScope())
{
TestResolvingServices(scope2);
var testCounter = scope2.ServiceProvider.GetRequiredService<ITestCounter>();
//Resolved in a different scope, so should not cache the service!
testCounter.GetValue(nameof(TransientTestService)).ShouldBe(2);
}
}
}
[Fact]
public void TransientCachedServiceProvider_Should_Cache_Services()
{
@ -58,15 +62,26 @@ public class CachedServiceProvider_Tests
var transientTestService1_1 = transientCachedServiceProvider1.GetRequiredService<TransientTestService>();
var transientTestService1_2 = transientCachedServiceProvider1.GetRequiredService<TransientTestService>();
transientTestService1_1.ShouldBeSameAs(transientTestService1_2);
var transientKeyedTestService1 = transientCachedServiceProvider1.GetRequiredKeyedService<TransientKeyedTestService>("key1");
var transientKeyedTestService2 = transientCachedServiceProvider1.GetRequiredKeyedService<TransientKeyedTestService>("key1");
transientKeyedTestService1.ShouldBeSameAs(transientKeyedTestService2);
var transientKeyedTestService3 = transientCachedServiceProvider1.GetRequiredKeyedService<TransientKeyedTestService>("key2");
transientKeyedTestService2.ShouldNotBeSameAs(transientKeyedTestService3);
var transientCachedServiceProvider2 = scope.ServiceProvider.GetRequiredService<ITransientCachedServiceProvider>();
transientCachedServiceProvider1.ShouldNotBeSameAs(transientCachedServiceProvider2);
var transientTestService2_1 = transientCachedServiceProvider2.GetRequiredService<TransientTestService>();
var transientTestService2_2 = transientCachedServiceProvider2.GetRequiredService<TransientTestService>();
transientTestService2_1.ShouldBeSameAs(transientTestService2_2);
var transientKeyedTestService1_1 = transientCachedServiceProvider2.GetRequiredKeyedService<TransientKeyedTestService>("key1");
var transientKeyedTestService2_1 = transientCachedServiceProvider2.GetRequiredKeyedService<TransientKeyedTestService>("key1");
transientKeyedTestService1_1.ShouldBeSameAs(transientKeyedTestService2_1);
transientTestService1_1.ShouldNotBeSameAs(transientTestService2_1);
transientKeyedTestService1.ShouldNotBeSameAs(transientKeyedTestService1_1);
var transientCachedServiceProvider1_1 = transientCachedServiceProvider1.GetRequiredService<IServiceProvider>().GetRequiredService<ITransientCachedServiceProvider>();
var transientCachedServiceProvider1_2 = transientCachedServiceProvider1.GetRequiredService<IServiceProvider>().GetRequiredService<ITransientCachedServiceProvider>();
@ -87,7 +102,7 @@ public class CachedServiceProvider_Tests
}
}
}
[DependsOn(typeof(AbpTestBaseModule))]
private class TestModule : AbpModule
{
@ -99,6 +114,8 @@ public class CachedServiceProvider_Tests
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddType<TransientTestService>();
context.Services.AddKeyedTransient<TransientKeyedTestService>("key1");
context.Services.AddKeyedTransient<TransientKeyedTestService>("key2");
}
}
@ -109,4 +126,9 @@ public class CachedServiceProvider_Tests
counter.Increment(nameof(TransientTestService));
}
}
}
private class TransientKeyedTestService
{
}
}

Loading…
Cancel
Save