I\'m attempting to set up an Autofac module that seems to have a complicated requirement.
here goes:
I have a generic interface:
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.
using System;
using System.Collections.Generic;
using System.Linq;
using Autofac;
namespace ConsoleApplication1
{
public interface IOpenGeneric<T, U>
{
U Get(T value);
}
[AttributeUsage(AttributeTargets.Class)]
public class DecorateAttribute : Attribute
{
}
[Decorate]
public class BooleanToStringOne : IOpenGeneric<bool, string>
{
public string Get(bool value)
{
return $"{value.ToString()} from BooleanToStringOne";
}
}
[Decorate]
public class BooleanToStringTwo : IOpenGeneric<bool, string>
{
public string Get(bool value)
{
return $"{value.ToString()} from BooleanToStringTwo";
}
}
public class BooleanToStringThree : IOpenGeneric<bool, string>
{
public string Get(bool value)
{
return $"{value.ToString()} from BooleanToStringThree";
}
}
public class OpenGenericDecorator<T, U> : IOpenGeneric<T, U>
{
private readonly IOpenGeneric<T, U> _inner;
public OpenGenericDecorator(IOpenGeneric<T, U> inner)
{
_inner = inner;
}
public U Get(T value)
{
Console.WriteLine($"{_inner.GetType().Name} is being decorated!");
return _inner.Get(value);
}
}
public static class ReflectionExtensions
{
public static bool HasAttribute<TAttribute>(this Type type)
where TAttribute : Attribute
{
return type
.GetCustomAttributes(typeof(TAttribute), false)
.Cast<Attribute>()
.Any();
}
}
class Program
{
static void Main(string[] args)
{
var assembly = typeof(Program).Assembly;
var builder = new ContainerBuilder();
// associate types that have the [Decorate] attribute with a specific key
builder
.RegisterAssemblyTypes(assembly)
.Where(x => x.HasAttribute<DecorateAttribute>())
.AsClosedTypesOf(typeof(IOpenGeneric<,>), "decoratable-service");
// get the keyed types and register the decorator
builder.RegisterGenericDecorator(
typeof(OpenGenericDecorator<,>),
typeof(IOpenGeneric<,>),
"decoratable-service");
// no key for the ones with no [Decorate] attribute so they'll
// get resolved "as is"
builder
.RegisterAssemblyTypes(assembly)
.Where(x => !x.HasAttribute<DecorateAttribute>())
.AsClosedTypesOf(typeof(IOpenGeneric<,>));
var container = builder.Build();
var booleanToStrings = container.Resolve<IEnumerable<IOpenGeneric<bool,string>>>();
foreach (var item in booleanToStrings)
{
Console.WriteLine(item.Get(true));
Console.WriteLine();
}
Console.ReadLine();
}
}
}
The output from the console is
BooleanToStringTwo is being decorated!
True from BooleanToStringTwo
BooleanToStringOne is being decorated!
True from BooleanToStringOne
True from BooleanToStringThree
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 like typeof(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 as Keyed
. 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):
foreach (var type in Assembly.GetExecutingAssembly().GetTypes()
.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<,>)));
}
This is equivalent to the following manual registration which is required for the RegisterGenericDecorator
to work (assuming that MyImpl1
and MyImpl3
was marked with the MyAttribute
:
builder.RegisterType<MyImpl1>()
.Keyed<IMyInterface<string, bool>>("CachableQueries");
builder.RegisterType<MyImpl3>()
.Keyed<IMyInterface<bool, bool>>("CachableQueries");
Note you cannot use RegisterGeneric
here because you have this special MyAttribute
filter.