diff --git a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreAsyncIntegratedTestBase.cs b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreAsyncIntegratedTestBase.cs index e6c49d475c..d47b439728 100644 --- a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreAsyncIntegratedTestBase.cs +++ b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreAsyncIntegratedTestBase.cs @@ -35,6 +35,16 @@ public class AbpAspNetCoreAsyncIntegratedTestBase return ServiceProvider.GetRequiredService(); } + protected virtual T? GetKeyedServices(object? serviceKey) + { + return ServiceProvider.GetKeyedService(serviceKey); + } + + protected virtual T GetRequiredKeyedService(object? serviceKey) where T : notnull + { + return ServiceProvider.GetRequiredKeyedService(serviceKey); + } + public virtual async Task InitializeAsync() { var builder = WebApplication.CreateBuilder(); diff --git a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpWebApplicationFactoryIntegratedTest.cs b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpWebApplicationFactoryIntegratedTest.cs index 9a6422ca97..2e16c79809 100644 --- a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpWebApplicationFactoryIntegratedTest.cs +++ b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpWebApplicationFactoryIntegratedTest.cs @@ -43,6 +43,16 @@ public abstract class AbpWebApplicationFactoryIntegratedTest : WebAppl return Services.GetRequiredService(); } + protected virtual T? GetKeyedServices(object? serviceKey) + { + return ServiceProvider.GetKeyedService(serviceKey); + } + + protected virtual T GetRequiredKeyedService(object? serviceKey) where T : notnull + { + return ServiceProvider.GetRequiredKeyedService(serviceKey); + } + protected virtual void ConfigureServices(IServiceCollection services) { 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 9e86a0e3ce..17fd94c467 100644 --- a/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacRegistration.cs +++ b/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacRegistration.cs @@ -27,6 +27,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Reflection; using Autofac.Builder; +using Autofac.Core.Resolving.Pipeline; using Microsoft.Extensions.DependencyInjection; using Volo.Abp; using Volo.Abp.Modularity; @@ -97,6 +98,8 @@ public static class AutofacRegistration builder.RegisterType() .As() .As() + .As() + .As() .ExternallyOwned(); var autofacServiceScopeFactory = typeof(AutofacServiceProvider).Assembly.GetType("Autofac.Extensions.DependencyInjection.AutofacServiceScopeFactory"); @@ -111,9 +114,43 @@ public static class AutofacRegistration .As() .SingleInstance(); + // Shims for keyed service compatibility. + builder.RegisterServiceMiddlewareSource(new KeyedServiceMiddlewareSource()); + builder.RegisterSource(); + Register(builder, services, lifetimeScopeTagForSingletons); } + /// + /// Configures the exposed service type on a service registration. + /// + /// The activator data type. + /// The object registration style. + /// The registration being built. + /// The service descriptor with service type and key information. + /// + /// The , configured with the proper service type, + /// and available for additional configuration. + /// + private static IRegistrationBuilder ConfigureServiceType( + this IRegistrationBuilder registrationBuilder, + ServiceDescriptor descriptor) + { + if (descriptor.IsKeyedService) + { + var key = descriptor.ServiceKey!; + + // If it's keyed, the service key won't be null. A null key results in it _not_ being a keyed service. + registrationBuilder.Keyed(key, descriptor.ServiceType); + } + else + { + registrationBuilder.As(descriptor.ServiceType); + } + + return registrationBuilder; + } + /// /// Configures the lifecycle on a service registration. /// @@ -187,47 +224,106 @@ public static class AutofacRegistration foreach (var descriptor in services) { - if (descriptor.ImplementationType != null) + var implementationType = descriptor.NormalizedImplementationType(); + if (implementationType != null) { // Test if the an open generic type is being registered var serviceTypeInfo = descriptor.ServiceType.GetTypeInfo(); if (serviceTypeInfo.IsGenericTypeDefinition) { builder - .RegisterGeneric(descriptor.ImplementationType) - .As(descriptor.ServiceType) + .RegisterGeneric(implementationType) + .ConfigureServiceType(descriptor) .ConfigureLifecycle(descriptor.Lifetime, lifetimeScopeTagForSingletons) .ConfigureAbpConventions(descriptor, moduleContainer, registrationActionList, activatedActionList); } else { builder - .RegisterType(descriptor.ImplementationType) - .As(descriptor.ServiceType) + .RegisterType(implementationType) + .ConfigureServiceType(descriptor) .ConfigureLifecycle(descriptor.Lifetime, lifetimeScopeTagForSingletons) .ConfigureAbpConventions(descriptor, moduleContainer, registrationActionList, activatedActionList); } + + continue; } - else if (descriptor.ImplementationFactory != null) + + if (descriptor.IsKeyedService && descriptor.KeyedImplementationFactory != null) + { + var registration = RegistrationBuilder.ForDelegate(descriptor.ServiceType, (context, parameters) => + { + // At this point the context is always a ResolveRequestContext, which will expose the actual service type. + var requestContext = (ResolveRequestContext)context; + + var serviceProvider = context.Resolve(); + + var keyedService = (Autofac.Core.KeyedService)requestContext.Service; + + var key = keyedService.ServiceKey; + + return descriptor.KeyedImplementationFactory(serviceProvider, key); + }) + .ConfigureServiceType(descriptor) + .ConfigureLifecycle(descriptor.Lifetime, lifetimeScopeTagForSingletons) + .CreateRegistration(); + //TODO: ConfigureAbpConventions ? + + builder.RegisterComponent(registration); + + continue; + } + + if (!descriptor.IsKeyedService && descriptor.ImplementationFactory != null) { var registration = RegistrationBuilder.ForDelegate(descriptor.ServiceType, (context, parameters) => { var serviceProvider = context.Resolve(); return descriptor.ImplementationFactory(serviceProvider); }) + .ConfigureServiceType(descriptor) .ConfigureLifecycle(descriptor.Lifetime, lifetimeScopeTagForSingletons) .CreateRegistration(); //TODO: ConfigureAbpConventions ? builder.RegisterComponent(registration); + + continue; } - else - { - builder - .RegisterInstance(descriptor.ImplementationInstance!) - .As(descriptor.ServiceType) - .ConfigureLifecycle(descriptor.Lifetime, null); - } + + // It's not a type or factory, so it must be an instance. + builder + .RegisterInstance(descriptor.NormalizedImplementationInstance()!) + .ConfigureServiceType(descriptor) + .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.TestBase/Volo/Abp/AbpTestBaseWithServiceProvider.cs b/framework/src/Volo.Abp.TestBase/Volo/Abp/AbpTestBaseWithServiceProvider.cs index 04879b4fce..470abcb9d5 100644 --- a/framework/src/Volo.Abp.TestBase/Volo/Abp/AbpTestBaseWithServiceProvider.cs +++ b/framework/src/Volo.Abp.TestBase/Volo/Abp/AbpTestBaseWithServiceProvider.cs @@ -11,9 +11,19 @@ public abstract class AbpTestBaseWithServiceProvider { return ServiceProvider.GetService(); } - + protected virtual T GetRequiredService() where T : notnull { return ServiceProvider.GetRequiredService(); } + + protected virtual T? GetKeyedServices(object? serviceKey) + { + return ServiceProvider.GetKeyedService(serviceKey); + } + + protected virtual T GetRequiredKeyedService(object? serviceKey) where T : notnull + { + return ServiceProvider.GetRequiredKeyedService(serviceKey); + } } diff --git a/framework/test/Volo.Abp.Core.Tests/Microsoft/Extensions/DependencyInjection/DependencyInjection_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Microsoft/Extensions/DependencyInjection/DependencyInjection_Tests.cs index 52f07620fb..be6b6d68f5 100644 --- a/framework/test/Volo.Abp.Core.Tests/Microsoft/Extensions/DependencyInjection/DependencyInjection_Tests.cs +++ b/framework/test/Volo.Abp.Core.Tests/Microsoft/Extensions/DependencyInjection/DependencyInjection_Tests.cs @@ -88,6 +88,25 @@ public abstract class DependencyInjection_Standard_Tests : AbpIntegratedTest("big"); + var bigInstanceCache = GetRequiredKeyedService("bigInstance"); + var smallCache = GetRequiredKeyedService("small"); + var smallFactoryCache = GetRequiredKeyedService("smallFactory"); + + bigCache.GetType().ShouldBe(typeof(BigCache)); + bigInstanceCache.GetType().ShouldBe(typeof(BigCache)); + smallCache.GetType().ShouldBe(typeof(SmallCache)); + smallFactoryCache.GetType().ShouldBe(typeof(SmallCache)); + + bigCache.Get("key").ShouldBe("Resolving key from big cache."); + bigInstanceCache.Get("key").ShouldBe("Resolving key from big cache."); + smallCache.Get("key").ShouldBe("Resolving key from small cache."); + smallFactoryCache.Get("key").ShouldBe("Resolving key from small cache."); + } + public class MySingletonService : ISingletonDependency { public List TransientInstances { get; } @@ -164,6 +183,10 @@ public abstract class DependencyInjection_Standard_Tests : AbpIntegratedTest)); context.Services.AddTransient(typeof(GenericServiceWithDisablePropertyInjectionOnClass<>)); context.Services.AddTransient(typeof(GenericServiceWithDisablePropertyInjectionOnProperty<>)); + context.Services.AddKeyedSingleton("big"); + context.Services.AddKeyedSingleton("small"); + context.Services.AddKeyedSingleton("bigInstance", new BigCache()); + context.Services.AddKeyedSingleton("smallFactory", (sp, key) => new SmallCache()); } } @@ -215,4 +238,18 @@ public abstract class DependencyInjection_Standard_Tests : AbpIntegratedTest $"Resolving {key} from big cache."; + } + + public class SmallCache : ICache + { + public object Get(string key) => $"Resolving {key} from small cache."; + } }