From 1d28bab085ff089c9977ec2450b7caa6abb6228d Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 1 Apr 2026 09:53:46 +0800 Subject: [PATCH] Upgrade Autofac.Extensions.DependencyInjection to 11.0.0 - Upgrade Autofac from 8.4.0 to 9.1.0 - Upgrade Autofac.Extensions.DependencyInjection from 10.0.0 to 11.0.0 - Upgrade Microsoft.Bcl.AsyncInterfaces from 10.0.2 to 10.0.4 - Remove AnyKeyRegistrationSource (now native in Autofac 9.1.0) - Add MSDI KeyedService.AnyKey to Autofac KeyedService.AnyKey translation - Use Parameters.KeyedServiceKey() for keyed factory key retrieval - Add ExternallyOwned() to instance registrations - Add unit tests for keyed services and AnyKey support --- Directory.Packages.props | 6 +- .../AutofacRegistration.cs | 16 +- .../Abp/Autofac/AutofacRegistration_Tests.cs | 157 ++++++++++++++++++ 3 files changed, 169 insertions(+), 10 deletions(-) create mode 100644 framework/test/Volo.Abp.Autofac.Tests/Volo/Abp/Autofac/AutofacRegistration_Tests.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 008660ae8c..1a0e47b2c0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,8 +7,8 @@ - - + + @@ -76,7 +76,7 @@ - + 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 7d1c3dee39..01386f3835 100644 --- a/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacRegistration.cs +++ b/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacRegistration.cs @@ -119,7 +119,6 @@ public static class AutofacRegistration .SingleInstance(); // Shims for keyed service compatibility. - builder.RegisterSource(); builder.ComponentRegistryBuilder.Registered += AddFromKeyedServiceParameterMiddleware; Register(builder, services, lifetimeScopeTagForSingletons); @@ -212,11 +211,15 @@ public static class AutofacRegistration this IRegistrationBuilder registrationBuilder, ServiceDescriptor descriptor) { + // If it's keyed, the service key won't be null. A null key results in it _not_ being a keyed service. if (descriptor.IsKeyedService) { var key = descriptor.ServiceKey!; + if (key.Equals(Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + { + key = Autofac.Core.KeyedService.AnyKey; + } - // 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 @@ -335,8 +338,7 @@ public static class AutofacRegistration var serviceProvider = context.Resolve(); var keyedService = (Autofac.Core.KeyedService)requestContext.Service; - - var key = keyedService.ServiceKey; + var key = requestContext.Parameters.KeyedServiceKey(); return descriptor.KeyedImplementationFactory(serviceProvider, key); }) @@ -349,8 +351,7 @@ public static class AutofacRegistration continue; } - - if (!descriptor.IsKeyedService && descriptor.ImplementationFactory != null) + else if (!descriptor.IsKeyedService && descriptor.ImplementationFactory != null) { var registration = RegistrationBuilder.ForDelegate(descriptor.ServiceType, (context, parameters) => { @@ -371,7 +372,8 @@ public static class AutofacRegistration builder .RegisterInstance(descriptor.NormalizedImplementationInstance()!) .ConfigureServiceType(descriptor) - .ConfigureLifecycle(descriptor.Lifetime, null); + .ConfigureLifecycle(descriptor.Lifetime, null) + .ExternallyOwned(); } } } diff --git a/framework/test/Volo.Abp.Autofac.Tests/Volo/Abp/Autofac/AutofacRegistration_Tests.cs b/framework/test/Volo.Abp.Autofac.Tests/Volo/Abp/Autofac/AutofacRegistration_Tests.cs new file mode 100644 index 0000000000..cc6b1f1668 --- /dev/null +++ b/framework/test/Volo.Abp.Autofac.Tests/Volo/Abp/Autofac/AutofacRegistration_Tests.cs @@ -0,0 +1,157 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Modularity; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.Autofac; + +public class AutofacRegistration_Tests : AbpIntegratedTest +{ + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + + [Fact] + public void Should_Resolve_AnyKey_Keyed_Service_With_Any_Key() + { + // AnyKey registration should be resolvable with any key value. + var serviceWithKeyA = GetRequiredKeyedService("keyA"); + var serviceWithKeyB = GetRequiredKeyedService("keyB"); + var serviceWithKeyC = GetRequiredKeyedService(42); + + serviceWithKeyA.ShouldNotBeNull(); + serviceWithKeyB.ShouldNotBeNull(); + serviceWithKeyC.ShouldNotBeNull(); + + serviceWithKeyA.ShouldBeOfType(); + serviceWithKeyB.ShouldBeOfType(); + serviceWithKeyC.ShouldBeOfType(); + } + + [Fact] + public void Should_Pass_Correct_Key_To_Keyed_Factory() + { + var serviceA = GetRequiredKeyedService("alpha"); + var serviceB = GetRequiredKeyedService("beta"); + + serviceA.Key.ShouldBe("alpha"); + serviceB.Key.ShouldBe("beta"); + } + + [Fact] + public void Should_Not_Dispose_Instance_Registration_When_Scope_Disposed() + { + // Resolve the pre-registered singleton instance. + var instance = GetRequiredKeyedService("instance"); + instance.ShouldNotBeNull(); + instance.IsDisposed.ShouldBeFalse(); + + // The same instance should be returned from a child scope. + using (var scope = ServiceProvider.CreateScope()) + { + var scopedInstance = scope.ServiceProvider.GetRequiredKeyedService("instance"); + scopedInstance.ShouldBeSameAs(instance); + } + + // After the scope is disposed, the singleton instance should still be alive. + instance.IsDisposed.ShouldBeFalse(); + + // It should also be the same static instance registered in the module. + instance.ShouldBeSameAs(TestModule.DisposableInstanceForTest); + } + + [Fact] + public void Should_Resolve_Standard_Keyed_Services() + { + var big = GetRequiredKeyedService("big"); + var small = GetRequiredKeyedService("small"); + + big.ShouldBeOfType(); + small.ShouldBeOfType(); + + big.Get("test").ShouldBe("big:test"); + small.Get("test").ShouldBe("small:test"); + } + + [DependsOn(typeof(AbpAutofacModule))] + public class TestModule : AbpModule + { + public static DisposableInstance DisposableInstanceForTest { get; } = new(); + + public override void ConfigureServices(ServiceConfigurationContext context) + { + // AnyKey registration: this service can be resolved with any key. + context.Services.AddKeyedTransient( + Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey); + + // Keyed factory registration: the factory receives the actual key used for resolution. + context.Services.Add(ServiceDescriptor.KeyedTransient( + Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey, + (sp, key) => new KeyedFactoryServiceImpl(key))); + + // Instance registration with keyed service (ExternallyOwned should prevent Autofac from disposing it). + context.Services.AddKeyedSingleton("instance", DisposableInstanceForTest); + + // Standard keyed type registrations. + context.Services.AddKeyedTransient("big"); + context.Services.AddKeyedTransient("small"); + } + } + + public interface IAnyKeyService + { + } + + public class AnyKeyServiceImpl : IAnyKeyService + { + } + + public interface IKeyedFactoryService + { + object Key { get; } + } + + public class KeyedFactoryServiceImpl : IKeyedFactoryService + { + public object Key { get; } + + public KeyedFactoryServiceImpl(object key) + { + Key = key; + } + } + + public interface IDisposableInstance + { + bool IsDisposed { get; } + } + + public class DisposableInstance : IDisposableInstance, IDisposable + { + public bool IsDisposed { get; private set; } + + public void Dispose() + { + IsDisposed = true; + } + } + + public interface ITypedCache + { + string Get(string key); + } + + public class BigTypedCache : ITypedCache + { + public string Get(string key) => $"big:{key}"; + } + + public class SmallTypedCache : ITypedCache + { + public string Get(string key) => $"small:{key}"; + } +}