From 17a50f877afce39a8309602c6dd1d095cd4f7b1b Mon Sep 17 00:00:00 2001 From: Kevin Bowes Date: Sun, 4 May 2025 01:05:18 -0400 Subject: [PATCH 1/6] Allowed Keyed Composites --- .../Reflection/AutowiringParameter.cs | 16 +++++- .../RegistrationExtensions.Composite.cs | 2 +- .../Features/CompositeTests.cs | 51 +++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/Autofac/Core/Activators/Reflection/AutowiringParameter.cs b/src/Autofac/Core/Activators/Reflection/AutowiringParameter.cs index ae3beab4d..65a3e62c9 100644 --- a/src/Autofac/Core/Activators/Reflection/AutowiringParameter.cs +++ b/src/Autofac/Core/Activators/Reflection/AutowiringParameter.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using System.Reflection; +using Autofac.Core.Resolving.Pipeline; namespace Autofac.Core.Activators.Reflection; @@ -34,7 +35,20 @@ public override bool CanSupplyValue(ParameterInfo pi, IComponentContext context, throw new ArgumentNullException(nameof(context)); } - var service = new TypedService(pi.ParameterType); + Service service = new TypedService(pi.ParameterType); + + if (context is DefaultResolveRequestContext ctx) + { + if ((ctx.Registration.Options & Registration.RegistrationOptions.Composite) == Registration.RegistrationOptions.Composite + && ctx.Service is KeyedService keyedService + && pi.ParameterType.IsGenericType + && pi.ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>) + && pi.ParameterType.GenericTypeArguments[0] == keyedService.ServiceType) + { + service = new KeyedService(keyedService.ServiceKey, pi.ParameterType); + } + } + if (context.ComponentRegistry.TryGetServiceRegistration(service, out var implementation)) { valueProvider = () => context.ResolveComponent(new ResolveRequest(service, implementation, Enumerable.Empty())); diff --git a/src/Autofac/RegistrationExtensions.Composite.cs b/src/Autofac/RegistrationExtensions.Composite.cs index cfe428fa5..03c1971d5 100644 --- a/src/Autofac/RegistrationExtensions.Composite.cs +++ b/src/Autofac/RegistrationExtensions.Composite.cs @@ -179,7 +179,7 @@ private static void ApplyCompositeConfiguration( builder.RegisterCallback(crb => { // Validate that we are only behaving as a composite for a single service. - if (registration.RegistrationData.Services.Count() > 1) + if (registration.RegistrationData.Services.Count(s => s is TypedService) > 1) { // Cannot have a multi-service composite. throw new InvalidOperationException( diff --git a/test/Autofac.Specification.Test/Features/CompositeTests.cs b/test/Autofac.Specification.Test/Features/CompositeTests.cs index f043e79c5..4b71498ed 100644 --- a/test/Autofac.Specification.Test/Features/CompositeTests.cs +++ b/test/Autofac.Specification.Test/Features/CompositeTests.cs @@ -35,6 +35,57 @@ public void CanRegisterComposite() i => Assert.IsType(i)); } + [Fact] + public void CanRegisterCompositeOfKeyedServices() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As().Keyed("1"); + builder.Register(ctx => new S2()).As().Keyed("1"); + builder.Register(ctx => new S3()).As().Keyed("2"); + builder.Register(ctx => new S4()).As().Keyed("2"); + + builder.RegisterComposite() + .Keyed("1") + .Keyed("2"); + + var container = builder.Build(); + + var comp = container.Resolve(); // gets all I1 + + Assert.IsType(comp); + + var actualComp = (MyComposite)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(i), + i => Assert.IsType(i), + i => Assert.IsType(i), + i => Assert.IsType(i)); + + comp = container.ResolveKeyed("1"); // gets only 11 keyed to "1" + + Assert.IsType(comp); + + actualComp = (MyComposite)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(i), + i => Assert.IsType(i)); + + comp = container.ResolveKeyed("2"); // gets only 11 keyed to "2" + + Assert.IsType(comp); + + actualComp = (MyComposite)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(i), + i => Assert.IsType(i)); + } + [Fact] public void CompositeRegistrationOrderIrrelevant() { From 76bad3186e91956b115b4c756ad5be3dc4deffd0 Mon Sep 17 00:00:00 2001 From: Kevin Bowes Date: Sun, 4 May 2025 01:05:18 -0400 Subject: [PATCH 2/6] Allowed Keyed Composites --- test/Autofac.Specification.Test/Features/CompositeTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Autofac.Specification.Test/Features/CompositeTests.cs b/test/Autofac.Specification.Test/Features/CompositeTests.cs index 4b71498ed..b7f53e423 100644 --- a/test/Autofac.Specification.Test/Features/CompositeTests.cs +++ b/test/Autofac.Specification.Test/Features/CompositeTests.cs @@ -50,7 +50,7 @@ public void CanRegisterCompositeOfKeyedServices() var container = builder.Build(); - var comp = container.Resolve(); // gets all I1 + var comp = container.Resolve(); // gets all I1 non keyed Assert.IsType(comp); @@ -63,7 +63,7 @@ public void CanRegisterCompositeOfKeyedServices() i => Assert.IsType(i), i => Assert.IsType(i)); - comp = container.ResolveKeyed("1"); // gets only 11 keyed to "1" + comp = container.ResolveKeyed("1"); // gets only I1 keyed to "1" Assert.IsType(comp); @@ -74,7 +74,7 @@ public void CanRegisterCompositeOfKeyedServices() i => Assert.IsType(i), i => Assert.IsType(i)); - comp = container.ResolveKeyed("2"); // gets only 11 keyed to "2" + comp = container.ResolveKeyed("2"); // gets only I1 keyed to "2" Assert.IsType(comp); From ccd4d7111a2e4ca7f8a479540235c7759c4d4c42 Mon Sep 17 00:00:00 2001 From: Kevin Bowes Date: Sun, 4 May 2025 14:24:39 -0400 Subject: [PATCH 3/6] Autowiring of more complex relationships in Composites --- .../Reflection/AutowiringParameter.cs | 11 +++- .../Features/CompositeTests.cs | 59 +++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/Autofac/Core/Activators/Reflection/AutowiringParameter.cs b/src/Autofac/Core/Activators/Reflection/AutowiringParameter.cs index 65a3e62c9..d8edccde4 100644 --- a/src/Autofac/Core/Activators/Reflection/AutowiringParameter.cs +++ b/src/Autofac/Core/Activators/Reflection/AutowiringParameter.cs @@ -37,13 +37,20 @@ public override bool CanSupplyValue(ParameterInfo pi, IComponentContext context, Service service = new TypedService(pi.ParameterType); + static bool HasService(Type type, Type serviceType) + { + return type.IsGenericType && type.GenericTypeArguments.Any(genericType => genericType == serviceType || HasService(genericType, serviceType)); + } + if (context is DefaultResolveRequestContext ctx) { if ((ctx.Registration.Options & Registration.RegistrationOptions.Composite) == Registration.RegistrationOptions.Composite && ctx.Service is KeyedService keyedService && pi.ParameterType.IsGenericType - && pi.ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>) - && pi.ParameterType.GenericTypeArguments[0] == keyedService.ServiceType) + && (pi.ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>) + || pi.ParameterType.GetGenericTypeDefinition() == typeof(IList<>) + || pi.ParameterType.GetGenericTypeDefinition() == typeof(ICollection<>)) + && HasService(pi.ParameterType, keyedService.ServiceType)) { service = new KeyedService(keyedService.ServiceKey, pi.ParameterType); } diff --git a/test/Autofac.Specification.Test/Features/CompositeTests.cs b/test/Autofac.Specification.Test/Features/CompositeTests.cs index b7f53e423..26ebe80d3 100644 --- a/test/Autofac.Specification.Test/Features/CompositeTests.cs +++ b/test/Autofac.Specification.Test/Features/CompositeTests.cs @@ -86,6 +86,57 @@ public void CanRegisterCompositeOfKeyedServices() i => Assert.IsType(i)); } + [Fact] + public void CanRegisterCompositeOfKeyedServicesMeta() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As().Keyed("1"); + builder.Register(ctx => new S2()).As().Keyed("1"); + builder.Register(ctx => new S3()).As().Keyed("2"); + builder.Register(ctx => new S4()).As().Keyed("2"); + + builder.RegisterComposite() + .Keyed("1") + .Keyed("2"); + + var container = builder.Build(); + + var comp = container.Resolve(); // gets all I1 non keyed + + Assert.IsType(comp); + + var actualComp = (MyComplexComposite)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(i), + i => Assert.IsType(i), + i => Assert.IsType(i), + i => Assert.IsType(i)); + + comp = container.ResolveKeyed("1"); // gets only I1 keyed to "1" + + Assert.IsType(comp); + + actualComp = (MyComplexComposite)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(i), + i => Assert.IsType(i)); + + comp = container.ResolveKeyed("2"); // gets only I1 keyed to "2" + + Assert.IsType(comp); + + actualComp = (MyComplexComposite)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(i), + i => Assert.IsType(i)); + } + [Fact] public void CompositeRegistrationOrderIrrelevant() { @@ -662,6 +713,14 @@ public MyComposite(IEnumerable implementations) } } + private class MyComplexComposite : MyComposite + { + public MyComplexComposite(IEnumerable>>>> implementations) + : base(implementations.Select(i => i.Value(5).Value.Value).ToList()) + { + } + } + private class MyCompositeNeedsDisposeTracker : MyComposite, I1 { public MyCompositeNeedsDisposeTracker(DisposeTracker tracker, IEnumerable implementations) From 3c58dec7908ad74aade0eefa548ed457ac248aed Mon Sep 17 00:00:00 2001 From: Kevin Bowes Date: Sun, 4 May 2025 15:01:59 -0400 Subject: [PATCH 4/6] Check that the composite only has one typed service and the rest are only keyed services --- src/Autofac/RegistrationExtensions.Composite.cs | 4 ++-- test/Autofac.Specification.Test/Features/CompositeTests.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Autofac/RegistrationExtensions.Composite.cs b/src/Autofac/RegistrationExtensions.Composite.cs index 03c1971d5..e3965bd40 100644 --- a/src/Autofac/RegistrationExtensions.Composite.cs +++ b/src/Autofac/RegistrationExtensions.Composite.cs @@ -178,8 +178,8 @@ private static void ApplyCompositeConfiguration( builder.RegisterCallback(crb => { - // Validate that we are only behaving as a composite for a single service. - if (registration.RegistrationData.Services.Count(s => s is TypedService) > 1) + // Validate that we are only behaving as a composite for a single typed service, allows more keyed services. + if (registration.RegistrationData.Services.Count(s => s is TypedService) > 1 && registration.RegistrationData.Services.Count(s => s is KeyedService) < registration.RegistrationData.Services.Count() - 1) { // Cannot have a multi-service composite. throw new InvalidOperationException( diff --git a/test/Autofac.Specification.Test/Features/CompositeTests.cs b/test/Autofac.Specification.Test/Features/CompositeTests.cs index 26ebe80d3..9f0c06bc1 100644 --- a/test/Autofac.Specification.Test/Features/CompositeTests.cs +++ b/test/Autofac.Specification.Test/Features/CompositeTests.cs @@ -87,7 +87,7 @@ public void CanRegisterCompositeOfKeyedServices() } [Fact] - public void CanRegisterCompositeOfKeyedServicesMeta() + public void CanRegisterCompositeOfKeyedServicesComplexList() { var builder = new ContainerBuilder(); builder.Register(ctx => new S1()).As().Keyed("1"); From 243d617f38e84f36af05098ec8a301e2e9d85d17 Mon Sep 17 00:00:00 2001 From: Kevin Bowes Date: Sun, 4 May 2025 15:54:00 -0400 Subject: [PATCH 5/6] Using base ResolveRequestContext instead of default --- src/Autofac/Core/Activators/Reflection/AutowiringParameter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Autofac/Core/Activators/Reflection/AutowiringParameter.cs b/src/Autofac/Core/Activators/Reflection/AutowiringParameter.cs index d8edccde4..b4f9266d0 100644 --- a/src/Autofac/Core/Activators/Reflection/AutowiringParameter.cs +++ b/src/Autofac/Core/Activators/Reflection/AutowiringParameter.cs @@ -42,7 +42,7 @@ static bool HasService(Type type, Type serviceType) return type.IsGenericType && type.GenericTypeArguments.Any(genericType => genericType == serviceType || HasService(genericType, serviceType)); } - if (context is DefaultResolveRequestContext ctx) + if (context is ResolveRequestContext ctx) { if ((ctx.Registration.Options & Registration.RegistrationOptions.Composite) == Registration.RegistrationOptions.Composite && ctx.Service is KeyedService keyedService From 9c861a40390ca0c1b85f7b636635bc3367096226 Mon Sep 17 00:00:00 2001 From: Kevin Bowes Date: Mon, 5 May 2025 13:53:45 -0400 Subject: [PATCH 6/6] Using type extension method to determine if collection type, moved generic of service type code to InternalTypeExtensions, created cache for result. --- .../Reflection/AutowiringParameter.cs | 25 ++++++++----------- src/Autofac/Core/InternalReflectionCaches.cs | 6 +++++ src/Autofac/Util/InternalTypeExtensions.cs | 19 ++++++++++++++ .../Features/CompositeTests.cs | 6 ++--- 4 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/Autofac/Core/Activators/Reflection/AutowiringParameter.cs b/src/Autofac/Core/Activators/Reflection/AutowiringParameter.cs index b4f9266d0..e4bcaba94 100644 --- a/src/Autofac/Core/Activators/Reflection/AutowiringParameter.cs +++ b/src/Autofac/Core/Activators/Reflection/AutowiringParameter.cs @@ -3,6 +3,7 @@ using System.Reflection; using Autofac.Core.Resolving.Pipeline; +using Autofac.Util; namespace Autofac.Core.Activators.Reflection; @@ -35,25 +36,19 @@ public override bool CanSupplyValue(ParameterInfo pi, IComponentContext context, throw new ArgumentNullException(nameof(context)); } - Service service = new TypedService(pi.ParameterType); + Service service; - static bool HasService(Type type, Type serviceType) + if (context is ResolveRequestContext ctx + && (ctx.Registration.Options & Registration.RegistrationOptions.Composite) == Registration.RegistrationOptions.Composite + && ctx.Service is KeyedService keyedService + && pi.ParameterType.IsGenericEnumerableInterfaceType() + && pi.ParameterType.IsGenericTypeContainingType(keyedService.ServiceType)) { - return type.IsGenericType && type.GenericTypeArguments.Any(genericType => genericType == serviceType || HasService(genericType, serviceType)); + service = new KeyedService(keyedService.ServiceKey, pi.ParameterType); } - - if (context is ResolveRequestContext ctx) + else { - if ((ctx.Registration.Options & Registration.RegistrationOptions.Composite) == Registration.RegistrationOptions.Composite - && ctx.Service is KeyedService keyedService - && pi.ParameterType.IsGenericType - && (pi.ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>) - || pi.ParameterType.GetGenericTypeDefinition() == typeof(IList<>) - || pi.ParameterType.GetGenericTypeDefinition() == typeof(ICollection<>)) - && HasService(pi.ParameterType, keyedService.ServiceType)) - { - service = new KeyedService(keyedService.ServiceKey, pi.ParameterType); - } + service = new TypedService(pi.ParameterType); } if (context.ComponentRegistry.TryGetServiceRegistration(service, out var implementation)) diff --git a/src/Autofac/Core/InternalReflectionCaches.cs b/src/Autofac/Core/InternalReflectionCaches.cs index f68c41767..be5d6becb 100644 --- a/src/Autofac/Core/InternalReflectionCaches.cs +++ b/src/Autofac/Core/InternalReflectionCaches.cs @@ -34,6 +34,11 @@ internal class InternalReflectionCaches /// public ReflectionCacheTupleDictionary IsGenericTypeDefinedBy { get; } + /// + /// Gets the cache used by . + /// + public ReflectionCacheTupleDictionary IsGenericTypeContainingType { get; } + /// /// Gets the cache used by . /// @@ -78,6 +83,7 @@ public InternalReflectionCaches(ReflectionCacheSet set) IsGenericEnumerableInterface = set.GetOrCreateCache>(nameof(IsGenericEnumerableInterface)); IsGenericListOrCollectionInterfaceType = set.GetOrCreateCache>(nameof(IsGenericListOrCollectionInterfaceType)); IsGenericTypeDefinedBy = set.GetOrCreateCache>(nameof(IsGenericTypeDefinedBy)); + IsGenericTypeContainingType = set.GetOrCreateCache>(nameof(IsGenericTypeContainingType)); ConstructorBinderFactory = set.GetOrCreateCache>>(nameof(ConstructorBinderFactory)); AutowiringPropertySetters = set.GetOrCreateCache>>(nameof(AutowiringPropertySetters)); AutowiringInjectableProperties = set.GetOrCreateCache>>(nameof(AutowiringInjectableProperties)); diff --git a/src/Autofac/Util/InternalTypeExtensions.cs b/src/Autofac/Util/InternalTypeExtensions.cs index fc96054f6..67449add3 100644 --- a/src/Autofac/Util/InternalTypeExtensions.cs +++ b/src/Autofac/Util/InternalTypeExtensions.cs @@ -186,6 +186,25 @@ static bool Uncached(Type type, Type openGeneric) key => Uncached(key.Item1, key.Item2)); } + /// + /// Checks whether this type is a generic containing the given type. + /// + /// The type to check. + /// The type to validate against. + /// True if the is a generic containing ; false otherwise. + /// Recursively moves through generic type arguments looking for . + public static bool IsGenericTypeContainingType(this Type @this, Type type) + { + static bool Uncached(Type @this, Type type) + { + return @this.IsGenericType && @this.GenericTypeArguments.Any(genericType => genericType == type || genericType.IsGenericTypeContainingType(type)); + } + + return ReflectionCacheSet.Shared.Internal.IsGenericTypeContainingType.GetOrAdd( + (@this, type), + key => Uncached(key.Item1, key.Item2)); + } + /// /// Checks whether this type is an open generic type of a given type. /// diff --git a/test/Autofac.Specification.Test/Features/CompositeTests.cs b/test/Autofac.Specification.Test/Features/CompositeTests.cs index 9f0c06bc1..f4dc32988 100644 --- a/test/Autofac.Specification.Test/Features/CompositeTests.cs +++ b/test/Autofac.Specification.Test/Features/CompositeTests.cs @@ -101,7 +101,7 @@ public void CanRegisterCompositeOfKeyedServicesComplexList() var container = builder.Build(); - var comp = container.Resolve(); // gets all I1 non keyed + var comp = container.Resolve(); // gets composite with all I1 non keyed Assert.IsType(comp); @@ -114,7 +114,7 @@ public void CanRegisterCompositeOfKeyedServicesComplexList() i => Assert.IsType(i), i => Assert.IsType(i)); - comp = container.ResolveKeyed("1"); // gets only I1 keyed to "1" + comp = container.ResolveKeyed("1"); // gets composite with only I1 keyed to "1" Assert.IsType(comp); @@ -125,7 +125,7 @@ public void CanRegisterCompositeOfKeyedServicesComplexList() i => Assert.IsType(i), i => Assert.IsType(i)); - comp = container.ResolveKeyed("2"); // gets only I1 keyed to "2" + comp = container.ResolveKeyed("2"); // gets composite with only I1 keyed to "2" Assert.IsType(comp);