Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/Autofac/Core/Activators/Reflection/AutowiringParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Reflection;
using Autofac.Core.Resolving.Pipeline;
using Autofac.Util;

namespace Autofac.Core.Activators.Reflection;

Expand Down Expand Up @@ -34,7 +36,21 @@ public override bool CanSupplyValue(ParameterInfo pi, IComponentContext context,
throw new ArgumentNullException(nameof(context));
}

var service = new TypedService(pi.ParameterType);
Service service;

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))
{
service = new KeyedService(keyedService.ServiceKey, pi.ParameterType);
}
else
{
service = new TypedService(pi.ParameterType);
}

if (context.ComponentRegistry.TryGetServiceRegistration(service, out var implementation))
{
valueProvider = () => context.ResolveComponent(new ResolveRequest(service, implementation, Enumerable.Empty<Parameter>()));
Expand Down
6 changes: 6 additions & 0 deletions src/Autofac/Core/InternalReflectionCaches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ internal class InternalReflectionCaches
/// </summary>
public ReflectionCacheTupleDictionary<Type, bool> IsGenericTypeDefinedBy { get; }

/// <summary>
/// Gets the cache used by <see cref="InternalTypeExtensions.IsGenericTypeContainingType"/>.
/// </summary>
public ReflectionCacheTupleDictionary<Type, bool> IsGenericTypeContainingType { get; }

/// <summary>
/// Gets the cache used by <see cref="ConstructorBinder"/>.
/// </summary>
Expand Down Expand Up @@ -78,6 +83,7 @@ public InternalReflectionCaches(ReflectionCacheSet set)
IsGenericEnumerableInterface = set.GetOrCreateCache<ReflectionCacheDictionary<Type, bool>>(nameof(IsGenericEnumerableInterface));
IsGenericListOrCollectionInterfaceType = set.GetOrCreateCache<ReflectionCacheDictionary<Type, bool>>(nameof(IsGenericListOrCollectionInterfaceType));
IsGenericTypeDefinedBy = set.GetOrCreateCache<ReflectionCacheTupleDictionary<Type, bool>>(nameof(IsGenericTypeDefinedBy));
IsGenericTypeContainingType = set.GetOrCreateCache<ReflectionCacheTupleDictionary<Type, bool>>(nameof(IsGenericTypeContainingType));
ConstructorBinderFactory = set.GetOrCreateCache<ReflectionCacheDictionary<ConstructorInfo, Func<object?[], object>>>(nameof(ConstructorBinderFactory));
AutowiringPropertySetters = set.GetOrCreateCache<ReflectionCacheDictionary<PropertyInfo, Action<object, object?>>>(nameof(AutowiringPropertySetters));
AutowiringInjectableProperties = set.GetOrCreateCache<ReflectionCacheDictionary<Type, IReadOnlyList<PropertyInfo>>>(nameof(AutowiringInjectableProperties));
Expand Down
4 changes: 2 additions & 2 deletions src/Autofac/RegistrationExtensions.Composite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,8 @@ private static void ApplyCompositeConfiguration<TLimit, TActivatorData, TStyle>(

builder.RegisterCallback(crb =>
{
// Validate that we are only behaving as a composite for a single service.
if (registration.RegistrationData.Services.Count() > 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(
Expand Down
19 changes: 19 additions & 0 deletions src/Autofac/Util/InternalTypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,25 @@ static bool Uncached(Type type, Type openGeneric)
key => Uncached(key.Item1, key.Item2));
}

/// <summary>
/// Checks whether this type is a generic containing the given type.
/// </summary>
/// <param name="this">The type to check.</param>
/// <param name="type">The type to validate against.</param>
/// <returns>True if the <paramref name="this"/> is a generic containing <paramref name="type"/>; false otherwise.</returns>
/// <remarks>Recursively moves through generic type arguments looking for <paramref name="type"/>.</remarks>
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));
}

/// <summary>
/// Checks whether this type is an open generic type of a given type.
/// </summary>
Expand Down
110 changes: 110 additions & 0 deletions test/Autofac.Specification.Test/Features/CompositeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,108 @@ public void CanRegisterComposite()
i => Assert.IsType<S2>(i));
}

[Fact]
public void CanRegisterCompositeOfKeyedServices()
{
var builder = new ContainerBuilder();
builder.Register(ctx => new S1()).As<I1>().Keyed<I1>("1");
builder.Register(ctx => new S2()).As<I1>().Keyed<I1>("1");
builder.Register(ctx => new S3()).As<I1>().Keyed<I1>("2");
builder.Register(ctx => new S4()).As<I1>().Keyed<I1>("2");

builder.RegisterComposite<MyComposite, I1>()
.Keyed<I1>("1")
.Keyed<I1>("2");

var container = builder.Build();

var comp = container.Resolve<I1>(); // gets all I1 non keyed

Assert.IsType<MyComposite>(comp);

var actualComp = (MyComposite)comp;

Assert.Collection(
actualComp.Implementations,
i => Assert.IsType<S1>(i),
i => Assert.IsType<S2>(i),
i => Assert.IsType<S3>(i),
i => Assert.IsType<S4>(i));

comp = container.ResolveKeyed<I1>("1"); // gets only I1 keyed to "1"

Assert.IsType<MyComposite>(comp);

actualComp = (MyComposite)comp;

Assert.Collection(
actualComp.Implementations,
i => Assert.IsType<S1>(i),
i => Assert.IsType<S2>(i));

comp = container.ResolveKeyed<I1>("2"); // gets only I1 keyed to "2"

Assert.IsType<MyComposite>(comp);

actualComp = (MyComposite)comp;

Assert.Collection(
actualComp.Implementations,
i => Assert.IsType<S3>(i),
i => Assert.IsType<S4>(i));
}

[Fact]
public void CanRegisterCompositeOfKeyedServicesComplexList()
{
var builder = new ContainerBuilder();
builder.Register(ctx => new S1()).As<I1>().Keyed<I1>("1");
builder.Register(ctx => new S2()).As<I1>().Keyed<I1>("1");
builder.Register(ctx => new S3()).As<I1>().Keyed<I1>("2");
builder.Register(ctx => new S4()).As<I1>().Keyed<I1>("2");

builder.RegisterComposite<MyComplexComposite, I1>()
.Keyed<I1>("1")
.Keyed<I1>("2");

var container = builder.Build();

var comp = container.Resolve<I1>(); // gets composite with all I1 non keyed

Assert.IsType<MyComplexComposite>(comp);

var actualComp = (MyComplexComposite)comp;

Assert.Collection(
actualComp.Implementations,
i => Assert.IsType<S1>(i),
i => Assert.IsType<S2>(i),
i => Assert.IsType<S3>(i),
i => Assert.IsType<S4>(i));

comp = container.ResolveKeyed<I1>("1"); // gets composite with only I1 keyed to "1"

Assert.IsType<MyComplexComposite>(comp);

actualComp = (MyComplexComposite)comp;

Assert.Collection(
actualComp.Implementations,
i => Assert.IsType<S1>(i),
i => Assert.IsType<S2>(i));

comp = container.ResolveKeyed<I1>("2"); // gets composite with only I1 keyed to "2"

Assert.IsType<MyComplexComposite>(comp);

actualComp = (MyComplexComposite)comp;

Assert.Collection(
actualComp.Implementations,
i => Assert.IsType<S3>(i),
i => Assert.IsType<S4>(i));
}

[Fact]
public void CompositeRegistrationOrderIrrelevant()
{
Expand Down Expand Up @@ -611,6 +713,14 @@ public MyComposite(IEnumerable<I1> implementations)
}
}

private class MyComplexComposite : MyComposite
{
public MyComplexComposite(IEnumerable<Lazy<Func<int, Owned<Meta<I1>>>>> implementations)
: base(implementations.Select(i => i.Value(5).Value.Value).ToList())
{
}
}

private class MyCompositeNeedsDisposeTracker : MyComposite<I1>, I1
{
public MyCompositeNeedsDisposeTracker(DisposeTracker tracker, IEnumerable<I1> implementations)
Expand Down