diff --git a/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacRegistration.cs b/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacRegistration.cs index 17fd94c467..7804cbbd98 100644 --- a/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacRegistration.cs +++ b/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacRegistration.cs @@ -298,32 +298,4 @@ public static class AutofacRegistration .ConfigureLifecycle(descriptor.Lifetime, null); } } - - /// - /// Normalizes the implementation instance data between keyed and not keyed services. - /// - /// - /// The to normalize. - /// - /// - /// The appropriate implementation instance from the service descriptor. - /// - public static object? NormalizedImplementationInstance(this ServiceDescriptor descriptor) - { - return !descriptor.IsKeyedService ? descriptor.ImplementationInstance : descriptor.KeyedImplementationInstance; - } - - /// - /// Normalizes the implementation type data between keyed and not keyed services. - /// - /// - /// The to normalize. - /// - /// - /// The appropriate implementation type from the service descriptor. - /// - public static Type? NormalizedImplementationType(this ServiceDescriptor descriptor) - { - return !descriptor.IsKeyedService ? descriptor.ImplementationType : descriptor.KeyedImplementationType; - } } diff --git a/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionCommonExtensions.cs b/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionCommonExtensions.cs index 7301b0b7a9..16f5ba9a86 100644 --- a/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionCommonExtensions.cs +++ b/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(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 && diff --git a/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceDescriptorExtensions.cs b/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceDescriptorExtensions.cs new file mode 100644 index 0000000000..1a160edb54 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceDescriptorExtensions.cs @@ -0,0 +1,31 @@ +using System; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Extensions for working with . +/// +public static class ServiceDescriptorExtensions +{ + /// + /// Normalizes the implementation instance data between keyed and not keyed services. + /// + /// + /// The to normalize. + /// + /// + /// The appropriate implementation instance from the service descriptor. + /// + public static object? NormalizedImplementationInstance(this ServiceDescriptor descriptor) => descriptor.IsKeyedService ? descriptor.KeyedImplementationInstance : descriptor.ImplementationInstance; + + /// + /// Normalizes the implementation type data between keyed and not keyed services. + /// + /// + /// The to normalize. + /// + /// + /// The appropriate implementation type from the service descriptor. + /// + public static Type? NormalizedImplementationType(this ServiceDescriptor descriptor) => descriptor.IsKeyedService ? descriptor.KeyedImplementationType : descriptor.ImplementationType; +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceDescriptor.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceDescriptor.cs new file mode 100644 index 0000000000..1b812fc4a2 --- /dev/null +++ b/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(); + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs index 5e8d9d251a..774efedb9a 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs +++ b/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> CachedServices { get; } + protected ConcurrentDictionary> CachedServices { get; } protected CachedServiceProviderBase(IServiceProvider serviceProvider) { ServiceProvider = serviceProvider; - CachedServices = new ConcurrentDictionary>(); - CachedServices.TryAdd(typeof(IServiceProvider), new Lazy(() => ServiceProvider)); + CachedServices = new ConcurrentDictionary>(); + CachedServices.TryAdd(new CachedServiceDescriptor(null, typeof(IServiceProvider)), new Lazy(() => ServiceProvider)); } public virtual object? GetService(Type serviceType) { return CachedServices.GetOrAdd( - serviceType, + new CachedServiceDescriptor(null, serviceType), _ => new Lazy(() => ServiceProvider.GetService(serviceType)) ).Value; } - + public T GetService(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 factory) { return CachedServices.GetOrAdd( - serviceType, + new CachedServiceDescriptor(null, serviceType), _ => new Lazy(() => factory(ServiceProvider)) ).Value!; } + + public virtual T GetKeyedService(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(() => requiredServiceSupportingProvider.GetKeyedService(serviceType, serviceKey)) + ).Value; + } + + throw new InvalidOperationException("This service provider doesn't support keyed services."); + } + + public virtual T GetRequiredKeyedService(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(() => requiredServiceSupportingProvider.GetRequiredKeyedService(serviceType, serviceKey)) + ).Value!; + } + + throw new InvalidOperationException("This service provider doesn't support keyed services."); + } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProviderBase.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProviderBase.cs index 2e15a45a46..6fc281f430 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProviderBase.cs +++ b/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 defaultValue); - + object GetService(Type serviceType, object defaultValue); T GetService(Func factory); object GetService(Type serviceType, Func factory); -} \ No newline at end of file + + T GetKeyedService(object? serviceKey); + + T GetRequiredKeyedService(object? serviceKey); +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IRootServiceProviderAccessor.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IRootServiceProviderAccessor.cs index 8a3c1d09a5..60f6b6461f 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IRootServiceProviderAccessor.cs +++ b/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. /// -public interface IRootServiceProvider : IServiceProvider +public interface IRootServiceProvider : IKeyedServiceProvider { - -} \ No newline at end of file + +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/RootServiceProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/RootServiceProvider.cs index a406bc5597..53d5878ea4 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/RootServiceProvider.cs +++ b/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 objectAccessor) { ServiceProvider = objectAccessor.Value!; @@ -16,4 +17,24 @@ public class RootServiceProvider : IRootServiceProvider, ISingletonDependency { return ServiceProvider.GetService(serviceType); } -} \ No newline at end of file + + 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."); + } +} diff --git a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/DependencyInjection/CachedServiceProvider_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/DependencyInjection/CachedServiceProvider_Tests.cs index b040bbc71f..bc67386c20 100644 --- a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/DependencyInjection/CachedServiceProvider_Tests.cs +++ b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/DependencyInjection/CachedServiceProvider_Tests.cs @@ -21,7 +21,11 @@ public class CachedServiceProvider_Tests var transientTestService1 = cachedServiceProvider1.GetRequiredService(); var transientTestService2 = cachedServiceProvider2.GetRequiredService(); transientTestService1.ShouldBeSameAs(transientTestService2); - + + var transientKeyedTestService1 = cachedServiceProvider1.GetRequiredKeyedService("key1"); + var transientKeyedTestService2 = cachedServiceProvider2.GetRequiredKeyedService("key1"); + transientKeyedTestService1.ShouldBeSameAs(transientKeyedTestService2); + var cachedServiceProvider3 = cachedServiceProvider1.GetRequiredService().GetRequiredService(); cachedServiceProvider3.ShouldBeSameAs(cachedServiceProvider1); } @@ -36,18 +40,18 @@ public class CachedServiceProvider_Tests var testCounter = scope1.ServiceProvider.GetRequiredService(); testCounter.GetValue(nameof(TransientTestService)).ShouldBe(1); } - + using (var scope2 = application.ServiceProvider.CreateScope()) { TestResolvingServices(scope2); var testCounter = scope2.ServiceProvider.GetRequiredService(); - + //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(); var transientTestService1_2 = transientCachedServiceProvider1.GetRequiredService(); transientTestService1_1.ShouldBeSameAs(transientTestService1_2); - + + var transientKeyedTestService1 = transientCachedServiceProvider1.GetRequiredKeyedService("key1"); + var transientKeyedTestService2 = transientCachedServiceProvider1.GetRequiredKeyedService("key1"); + transientKeyedTestService1.ShouldBeSameAs(transientKeyedTestService2); + var transientKeyedTestService3 = transientCachedServiceProvider1.GetRequiredKeyedService("key2"); + transientKeyedTestService2.ShouldNotBeSameAs(transientKeyedTestService3); + var transientCachedServiceProvider2 = scope.ServiceProvider.GetRequiredService(); transientCachedServiceProvider1.ShouldNotBeSameAs(transientCachedServiceProvider2); var transientTestService2_1 = transientCachedServiceProvider2.GetRequiredService(); var transientTestService2_2 = transientCachedServiceProvider2.GetRequiredService(); transientTestService2_1.ShouldBeSameAs(transientTestService2_2); - + + var transientKeyedTestService1_1 = transientCachedServiceProvider2.GetRequiredKeyedService("key1"); + var transientKeyedTestService2_1 = transientCachedServiceProvider2.GetRequiredKeyedService("key1"); + transientKeyedTestService1_1.ShouldBeSameAs(transientKeyedTestService2_1); + transientTestService1_1.ShouldNotBeSameAs(transientTestService2_1); + transientKeyedTestService1.ShouldNotBeSameAs(transientKeyedTestService1_1); var transientCachedServiceProvider1_1 = transientCachedServiceProvider1.GetRequiredService().GetRequiredService(); var transientCachedServiceProvider1_2 = transientCachedServiceProvider1.GetRequiredService().GetRequiredService(); @@ -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(); + context.Services.AddKeyedTransient("key1"); + context.Services.AddKeyedTransient("key2"); } } @@ -109,4 +126,9 @@ public class CachedServiceProvider_Tests counter.Increment(nameof(TransientTestService)); } } -} \ No newline at end of file + + private class TransientKeyedTestService + { + + } +}