I'm coming from Ninject but I decided to give Autofac a try since it seems more actively developed. So far I can say that registering decorators is not as easy as in Ninject using .WhenInjectedExactlyInto
syntax. Anyway please bear with me as I am an Autofac newbie.
Here's the problem:
I have type A
implementing interface IA
that is decorated by A_Decorator
. A_Decorator
implements interfaces IA
and IB
, and in turn should be decorated by AB_Decorator
that also implements both IA
and IB
. AB_Decorator
takes two dependencies of types IA
and IB
(so it is a decorator for both) but they both should resolve to the same instance of A_Decorator
. It looks something like this: AB_Decorator(A_Decorator(A) as IA, A_Decorator(A) as IB)
. When requesting service of type IA
or type IB
from Autofac container, they should reference a single AB_Decorator
instance.
It is a bit tricky to describe by word, but here's the simplest code sample I could come up with that shows this situation (I've added instance IDs and trace messages to constructors to see what's going on):
using System;
using Autofac;
namespace AutofacExample
{
internal interface IA { }
internal interface IB { }
class A : IA
{
static int _instanceCounter;
readonly int Id = ++_instanceCounter;
public A()
{
Console.WriteLine(this);
}
public override string ToString()
{
return $"{GetType().Name}[{nameof(Id)}={Id}]";
}
}
class A_Decorator : IA, IB
{
static int _instanceCounter = 10;
readonly int Id = ++_instanceCounter;
/* decorated1 should reference instance of A */
public A_Decorator(IA decoratedA)
{
Console.WriteLine($"{this}({nameof(decoratedA)}={decoratedA})");
}
public override string ToString()
{
return $"{GetType().Name}[{nameof(Id)}={Id}]";
}
}
class AB_Decorator : IA, IB
{
static int _instanceCounter = 100;
readonly int Id = ++_instanceCounter;
/* Both decorated1 and decorated2 should reference the same instance of A_Decorator */
public AB_Decorator(IA decoratedA, IB decoratedB)
{
Console.WriteLine($"{this}({nameof(decoratedA)}={decoratedA}, {nameof(decoratedB)}={decoratedB})");
}
public override string ToString()
{
return $"{GetType().Name}[{nameof(Id)}={Id}]";
}
}
class Program
{
static void Main(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();
builder
.RegisterType<A>()
.Named<IA>(nameof(A))
.SingleInstance();
builder
.RegisterType<A_Decorator>()
.Named<IA>(nameof(A_Decorator))
.Named<IB>(nameof(A_Decorator))
.SingleInstance();
builder
.RegisterType<AB_Decorator>()
.Named<IA>(nameof(AB_Decorator))
.Named<IB>(nameof(AB_Decorator))
.SingleInstance();
/* A is decorated by A_Decorator as IA */
builder
.RegisterDecorator<IA>(
(c, decorated) =>
c.ResolveNamed<IA>(nameof(A_Decorator), TypedParameter.From(decorated)),
nameof(A))
//.Keyed<IA>("innerA")
//.Keyed<IB>("innerB")
.SingleInstance();
/* Trying to register AB_Decorator as IA creates circular dependency */
//builder
// .RegisterDecorator<IA>(
// (c, decorated) =>
// c.ResolveNamed<IA>(nameof(AB_Decorator), TypedParameter.From(decorated)),
// "innerA")
// .SingleInstance();
/* A_Decorator is decorated by AB_Decorator as IB */
builder
.RegisterDecorator<IB>(
(c, decorated) =>
c.ResolveNamed<IB>(nameof(AB_Decorator), TypedParameter.From(decorated)),
nameof(A_Decorator) /* "innerB" */)
.SingleInstance();
IContainer container = builder.Build();
IA a = container.Resolve<IA>();
IB b = container.Resolve<IB>();
Console.WriteLine($"{nameof(a)} == {nameof(b)} ? {ReferenceEquals(a, b)}");
Console.WriteLine($"{nameof(a)} is {a.GetType().Name}");
Console.WriteLine($"{nameof(b)} is {b.GetType().Name}");
}
}
}
Unfortunately requesting instance of IA
gives me A_Decorator
, while for IB
I get AB_Decorator
. Trying to uncomment the extra decorator registration block causes circular dependency exception (DependencyResolutionException: Circular component dependency detected: System.Object -> AutofacExample.AB_Decorator -> System.Object -> AutofacExample.AB_Decorator
) and I can't make it work trying various combinations of the named registrations.
Does anyone know a solution to this? Thanks in advance.
The problem lies with the decorator registrations for AB_Decorator
. Specifically the lambda function resolving the AB_Decorator
:
( c, decorated ) => c.ResolveNamed<IA>( nameof( AB_Decorator ), TypedParameter.From( decorated ) );
The constructor for AB_Decorator
takes in 2 arguments that should both be the same instance of A_Decorator
which is being supplied to the lambda as decorated
. However, decorated
is being passed as a parameter only once via TypedParameter.From( decorated )
. Therefore Autofac is going to try to resolve the second parameter via the container.
Now the registrations for IB
show that we should get a singleton instance with A_Decorator
wrapped in AB_Decorator
. So to resolve IB
, the container must construct AB_Decorator
. There's the problem, we're currently trying to resolve AB_Decorator
as IA
, but we need an IB
to fulfill the constructor arguments for the AB_Decorator
being constructed for IA
. And IB
is registered in the container as AB_Decorator
. So you get:
AB_Decorator(A_Decorator(A) as IA, AB_Decorator(A_Decorator(A) as IA, AB_Decorator(etc...))
We need to pass decorated
into both parameters when resolving AB_Decorator
. Like this:
builder
.RegisterDecorator<IA>(
( c, decorated ) =>
c.ResolveNamed<IA>( nameof( AB_Decorator ),
new TypedParameter( typeof( IA ), decorated ),
new TypedParameter( typeof( IB ), decorated )
)
,"innerA"
)
.SingleInstance();
builder
.RegisterDecorator<IB>(
( c, decorated ) =>
c.ResolveNamed<IB>( nameof( AB_Decorator ),
new TypedParameter( typeof( IA ), decorated ),
new TypedParameter( typeof( IB ), decorated )
)
, nameof( A_Decorator ) /* "innerB" */
)
.SingleInstance();
Now we are sending decorated
which is A_Decorator
to both the IA
and the IB
parameters. Directly constructing the TypedParameter
instances allows me to specify the type I want the instance to fulfill in the parameters list for, in this case, AB_Decorator
.