diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProvider.cs index b7df112377..870e1f8faf 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProvider.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProvider.cs @@ -4,27 +4,13 @@ using System.Collections.Generic; namespace Volo.Abp.DependencyInjection; [ExposeServices(typeof(ICachedServiceProvider))] -public class CachedServiceProvider : ICachedServiceProvider, IScopedDependency +public class CachedServiceProvider : + CachedServiceProviderBase, + ICachedServiceProvider, + IScopedDependency { - protected IServiceProvider ServiceProvider { get; } - - protected IDictionary CachedServices { get; } - public CachedServiceProvider(IServiceProvider serviceProvider) + : base(serviceProvider) { - ServiceProvider = serviceProvider; - - CachedServices = new Dictionary - { - {typeof(IServiceProvider), serviceProvider} - }; - } - - public object GetService(Type serviceType) - { - return CachedServices.GetOrAdd( - serviceType, - () => ServiceProvider.GetService(serviceType) - ); } -} +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs new file mode 100644 index 0000000000..14c42b7b7f --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Concurrent; + +namespace Volo.Abp.DependencyInjection; + +public abstract class CachedServiceProviderBase +{ + private readonly IServiceProvider _serviceProvider; + private readonly ConcurrentDictionary> _cachedServices; + + protected CachedServiceProviderBase(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + _cachedServices = new ConcurrentDictionary>(); + _cachedServices.TryAdd(typeof(IServiceProvider), new Lazy(() => this)); + } + + public object GetService(Type serviceType) + { + return _cachedServices.GetOrAdd( + serviceType, + _ => new Lazy(() => _serviceProvider.GetService(serviceType)) + ).Value; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProvider.cs index b5961249df..073daa135a 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProvider.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProvider.cs @@ -11,4 +11,4 @@ namespace Volo.Abp.DependencyInjection; public interface ICachedServiceProvider : IServiceProvider { -} +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ITransientCachedServiceProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ITransientCachedServiceProvider.cs new file mode 100644 index 0000000000..55c43e7c94 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ITransientCachedServiceProvider.cs @@ -0,0 +1,14 @@ +using System; + +namespace Volo.Abp.DependencyInjection; + +/// +/// Provides services by caching the resolved services. +/// It caches all type of services including transients. +/// This service's lifetime is transient. +/// for the one with scoped lifetime. +/// +public interface ITransientCachedServiceProvider : IServiceProvider +{ + +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/TransientCachedServiceProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/TransientCachedServiceProvider.cs new file mode 100644 index 0000000000..e84fccd36f --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/TransientCachedServiceProvider.cs @@ -0,0 +1,15 @@ +using System; + +namespace Volo.Abp.DependencyInjection; + +[ExposeServices(typeof(ITransientCachedServiceProvider))] +public class TransientCachedServiceProvider : + CachedServiceProviderBase, + ITransientCachedServiceProvider, + ITransientDependency +{ + public TransientCachedServiceProvider(IServiceProvider serviceProvider) + : base(serviceProvider) + { + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..f2bfa2534a --- /dev/null +++ b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/DependencyInjection/CachedServiceProvider_Tests.cs @@ -0,0 +1,102 @@ +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Modularity; +using Volo.Abp.Testing.Utils; +using Xunit; + +namespace Volo.Abp.DependencyInjection; + +public class CachedServiceProvider_Tests +{ + [Fact] + public void CachedServiceProvider_Should_Cache_Services() + { + void TestResolvingServices(IServiceScope scope) + { + var cachedServiceProvider1 = scope.ServiceProvider.GetRequiredService(); + var cachedServiceProvider2 = scope.ServiceProvider.GetRequiredService(); + cachedServiceProvider1.ShouldBeSameAs(cachedServiceProvider2); + + var transientTestService1 = cachedServiceProvider1.GetRequiredService(); + var transientTestService2 = cachedServiceProvider2.GetRequiredService(); + transientTestService1.ShouldBeSameAs(transientTestService2); + } + + using (var application = AbpApplicationFactory.Create()) + { + application.Initialize(); + + using (var scope1 = application.ServiceProvider.CreateScope()) + { + TestResolvingServices(scope1); + 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() + { + void TestResolvingServices(IServiceScope scope) + { + var cachedServiceProvider1 = scope.ServiceProvider.GetRequiredService(); + + var transientTestService1_1 = cachedServiceProvider1.GetRequiredService(); + var transientTestService1_2 = cachedServiceProvider1.GetRequiredService(); + transientTestService1_1.ShouldBeSameAs(transientTestService1_2); + + var cachedServiceProvider2 = scope.ServiceProvider.GetRequiredService(); + cachedServiceProvider1.ShouldNotBeSameAs(cachedServiceProvider2); + + var transientTestService2_1 = cachedServiceProvider2.GetRequiredService(); + var transientTestService2_2 = cachedServiceProvider2.GetRequiredService(); + transientTestService2_1.ShouldBeSameAs(transientTestService2_2); + + transientTestService1_1.ShouldNotBeSameAs(transientTestService2_1); + } + + using (var application = AbpApplicationFactory.Create()) + { + application.Initialize(); + + using (var scope1 = application.ServiceProvider.CreateScope()) + { + TestResolvingServices(scope1); + var testCounter = scope1.ServiceProvider.GetRequiredService(); + testCounter.GetValue(nameof(TransientTestService)).ShouldBe(2); + } + } + } + + [DependsOn(typeof(AbpTestBaseModule))] + private class TestModule : AbpModule + { + public TestModule() + { + SkipAutoServiceRegistration = true; + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddType(); + } + } + + private class TransientTestService : ITransientDependency + { + public TransientTestService(ITestCounter counter) + { + counter.Increment(nameof(TransientTestService)); + } + } +} \ No newline at end of file