Browse Source

Inherit `IKeyedServiceProvider` to support keyed services.

pull/18792/head
maliming 3 years ago
parent
commit
51714b2147
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  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. 29
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceDescriptor.cs
  5. 51
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs
  6. 11
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProviderBase.cs
  7. 8
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IRootServiceProviderAccessor.cs
  8. 25
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/RootServiceProvider.cs
  9. 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;
}

29
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceDescriptor.cs

@ -0,0 +1,29 @@
using System;
namespace Volo.Abp.DependencyInjection;
public class CachedServiceDescriptor
{
private object? Key { get; }
private Type ServiceType { get; }
public CachedServiceDescriptor(object? key, Type serviceType)
{
Key = key;
ServiceType = serviceType;
}
public override bool Equals(object? obj)
{
return obj is CachedServiceDescriptor descriptor &&
Key == descriptor.Key &&
ServiceType == descriptor.ServiceType;
}
public override int GetHashCode()
{
var keyHashCode = Key?.GetHashCode() ?? 0;
return keyHashCode ^ ServiceType.GetHashCode();
}
}

51
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<CachedServiceDescriptor, 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<CachedServiceDescriptor, Lazy<object?>>();
CachedServices.TryAdd(new CachedServiceDescriptor(null, typeof(IServiceProvider)), new Lazy<object?>(() => ServiceProvider));
}
public virtual object? GetService(Type serviceType)
{
return CachedServices.GetOrAdd(
serviceType,
new CachedServiceDescriptor(null, serviceType),
_ => new Lazy<object?>(() => ServiceProvider.GetService(serviceType))
).Value;
}
public T GetService<T>(T defaultValue)
{
return (T)GetService(typeof(T), defaultValue!);
@ -37,12 +38,48 @@ 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 CachedServiceDescriptor(null, serviceType),
_ => new Lazy<object?>(() => factory(ServiceProvider))
).Value!;
}
public virtual T GetKeyedService<T>(object? serviceKey)
{
return (T)GetKeyedService(typeof(T), serviceKey)!;
}
public virtual object? GetKeyedService(Type serviceType, object? serviceKey)
{
if (ServiceProvider is IKeyedServiceProvider requiredServiceSupportingProvider)
{
return CachedServices.GetOrAdd(
new CachedServiceDescriptor(serviceKey, serviceType),
_ => new Lazy<object?>(() => requiredServiceSupportingProvider.GetKeyedService(serviceType, serviceKey))
).Value;
}
throw new InvalidOperationException("This service provider doesn't support keyed services.");
}
public virtual T GetRequiredKeyedService<T>(object? serviceKey)
{
return (T)GetRequiredKeyedService(typeof(T), serviceKey);
}
public virtual object GetRequiredKeyedService(Type serviceType, object? serviceKey)
{
if (ServiceProvider is IKeyedServiceProvider requiredServiceSupportingProvider)
{
return CachedServices.GetOrAdd(
new CachedServiceDescriptor(serviceKey, serviceType),
_ => new Lazy<object?>(() => requiredServiceSupportingProvider.GetRequiredKeyedService(serviceType, serviceKey))
).Value!;
}
throw new InvalidOperationException("This service provider doesn't support keyed services.");
}
}

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

@ -1,14 +1,19 @@
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);
}
T GetKeyedService<T>(object? serviceKey);
T GetRequiredKeyedService<T>(object? serviceKey);
}

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
{
}
}

25
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,24 @@ public class RootServiceProvider : IRootServiceProvider, ISingletonDependency
{
return ServiceProvider.GetService(serviceType);
}
}
public virtual object? GetKeyedService(Type serviceType, object? serviceKey)
{
if (ServiceProvider is IKeyedServiceProvider requiredServiceSupportingProvider)
{
return requiredServiceSupportingProvider.GetKeyedService(serviceType, serviceKey);
}
throw new InvalidOperationException("This service provider doesn't support keyed services.");
}
public virtual object GetRequiredKeyedService(Type serviceType, object? serviceKey)
{
if (ServiceProvider is IKeyedServiceProvider requiredServiceSupportingProvider)
{
return requiredServiceSupportingProvider.GetRequiredKeyedService(serviceType, serviceKey);
}
throw new InvalidOperationException("This service provider doesn't support keyed services.");
}
}

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