I'm attempting to set up an Autofac module that seems to have a complicated requirement.
here goes:
I have a generic interface:
public interface IMyInterface<TFoo, TBar>
I have a bunch of classes that implement this interface
e.g.
class MyImpl1 : IMyInterface<string, bool> { }
class MyImpl2 : IMyInterface<bool, string> { }
class MyImpl3 : IMyInterface<bool, string> { }
Finally, I have a decorator:
class MyDecorator<TFoo, TBar> : IMyInterface<TFoo, TBar>
I only want to "Decorate" the implementations (of MyInterface
) that have a particular attribute. So all implementations of MyInterface that have an Attribute of [MyAttribute]
are decorated with MyDecorator.
I'm close but no cigar yet:
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(type => type.GetCustomAttributes(true)
.Any(attr => attr.GetType() == typeof(MyAttribute)))
.AsClosedTypesOf(typeof (IMyInterface<,>))
.Keyed("CachableQueries", typeof(IMyInterface<,>));
builder.RegisterGenericDecorator(typeof(MyDecorator<,>),
typeof(IMyInterface<,>), "CachableQueries");
var container = builder.Build();
Console.WriteLine(container.Resolve<IMyInterface<string,bool>>());
Console.WriteLine(container.Resolve<IMyInterface<bool,bool>>());
I understand that the final piece of the puzzle is the Key, it actually need to pass the type into the Keyed("CachableQueries", THE_TYPE);
but its not playing ball.
Update
nemesv sent me in the right direction.
As part of my question, I forgot to mention that I also needed to register all the implementations of IMyInterface<,> that didn't have the [MyAttribute].
I did this in two stages. First Register the types with the Decorator and then register the rest.
My Solution: I know it needs refactoring, but as a proof of concept. it works.
class Program
{
static void Main(string[] args)
{
var builder = new ContainerBuilder();
//Get all the types we're interested in (that inherit IMyInterface)
List<Type> typesToQuery = Assembly.GetExecutingAssembly().GetTypes()
.Where(type => type.GetInterfaces()
.Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof (IMyInterface<,>))).ToList();
//Even tho the decorator inherits IMyInterface (we don't want to process it)
typesToQuery.Remove(typeof (MyDecorator<,>));
//build a dictionary of all the types, so we don't process them again.
Dictionary<Type, bool> typesToProcess = typesToQuery.ToDictionary(queryType => queryType, queryType => false);
//Register all types that have [MyAttribute]
foreach (var type in typesToQuery
.Where(type => type.GetCustomAttributes(true)
.Any(attr => attr.GetType() == (typeof(MyAttribute)))))
{
builder.RegisterType(type).Keyed("CachableQueries",
type.GetInterfaces()
.First(i =>
i.IsGenericType &&
i.GetGenericTypeDefinition() == typeof(IMyInterface<,>)));
typesToProcess[type] = true; //update, so this type isn't processed again
}
//Decorate the correct ones
builder.RegisterGenericDecorator(typeof(MyDecorator<,>), typeof(IMyInterface<,>), fromKey: "CachableQueries");
//Register the rest of the types we're interested
foreach (Type type in typesToProcess.Where(kvp => kvp.Value == false).Select(pair => pair.Key))
{
builder.RegisterType(type).As(
type.GetInterfaces()
.First(i =>
i.IsGenericType &&
i.GetGenericTypeDefinition() == typeof(IMyInterface<,>)));
}
var container = builder.Build();
Console.WriteLine(container.Resolve<IMyInterface<string, bool>>());
Console.WriteLine(container.Resolve<IMyInterface<bool, bool>>());
//Result:
//AutoFacPlay.MyDecorator`2[System.String,System.Boolean] - this one was decorated (as it has MyAttribute)
//AutoFacPlay.MyImplementation2 - this one wasn't decorated
Console.ReadLine();
}
}
Alright, I didn't realise the question was from 3 years ago as it was updated just a week ago.
We can take advantage of the chained methods during registration to segregate types that are decorated with the attribute from those that are not.
The output from the console is
The problem is that when you are using the
Keyed
registration then you need to specify the closed service type e.g.IMyInterface<string, bool>
so you cannot use an open generic there liketypeof(IMyInterface<,>)
However because when using the
RegisterAssemblyTypes
there is no API where you could get the currently registered closed type in order to register it asKeyed
. So you need to implement the "assembly scanning" by hand:You need to replace your
RegisterAssemblyTypes
call with something like this (you probably need some more error handling in production):This is equivalent to the following manual registration which is required for the
RegisterGenericDecorator
to work (assuming thatMyImpl1
andMyImpl3
was marked with theMyAttribute
:Note you cannot use
RegisterGeneric
here because you have this specialMyAttribute
filter.