Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
23 changes: 22 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,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;

Expand Down Expand Up @@ -34,7 +35,27 @@ 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);

static bool HasService(Type type, Type serviceType)
{
return type.IsGenericType && type.GenericTypeArguments.Any(genericType => genericType == serviceType || HasService(genericType, serviceType));
}

if (context is ResolveRequestContext ctx)
{
if ((ctx.Registration.Options & Registration.RegistrationOptions.Composite) == Registration.RegistrationOptions.Composite
&& ctx.Service is KeyedService keyedService
&& pi.ParameterType.IsGenericType
&& (pi.ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check out CollectionRegistrationSource to see how we detect generic collections. There's an extension method IsGenericListOrCollectionInterfaceType that can check but also caches the result so we don't have to look it up every time.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be worth looking at that for detection of things like array parameters or collection types that are derived from an open generic (like public class MyCollection : IEnumerable<MyType>).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the info. I moved to using the extension method that included the IEnumerable<> lookup. I looked through the rest of them to see if any of them already did my recursive lookup, there was not. I moved my lookup into the extension class as well, which feels better. I didn't know there was caching as I looked at the code for the first time on Saturday. I wrapped my lookup into it's own cache as well. Will look into the derived types.

|| pi.ParameterType.GetGenericTypeDefinition() == typeof(IList<>)
|| pi.ParameterType.GetGenericTypeDefinition() == typeof(ICollection<>))
&& HasService(pi.ParameterType, 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<Parameter>()));
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
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 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 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 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