AutoFac - регистрация декоратора для некоторых открытых Generic
Я пытаюсь настроить модуль Autofac, который кажется сложным требованием.
вот оно:
У меня есть общий интерфейс:
public interface IMyInterface<TFoo, TBar>
У меня есть куча классов, которые реализуют этот интерфейс
например
class MyImpl1 : IMyInterface<string, bool> { }
class MyImpl2 : IMyInterface<bool, string> { }
class MyImpl3 : IMyInterface<bool, string> { }
Наконец, у меня есть декоратор:
class MyDecorator<TFoo, TBar> : IMyInterface<TFoo, TBar>
Я только хочу "Украсить" реализации (из MyInterface
), которые имеют определенный атрибут. Таким образом, все реализации MyInterface, которые имеют атрибут [MyAttribute]
украшены MyDecorator.
Я рядом, но сигары еще нет
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>>());
Я понимаю, что последний кусок головоломки - это ключ, на самом деле нужно передать тип в Keyed("CachableQueries", THE_TYPE);
но это не игра в мяч.
Обновить
Немеш отправил меня в правильном направлении.
Как часть моего вопроса, я забыл упомянуть, что мне также нужно было зарегистрировать все реализации IMyInterface<,>, которые не имели [MyAttribute].
Я сделал это в два этапа. Сначала зарегистрируйте типы с помощью Decorator, а затем зарегистрируйте остальные.
Мое решение: я знаю, что это нуждается в рефакторинге, но в качестве доказательства концепции. оно работает.
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();
}
}
2 ответа
Проблема в том, что когда вы используете Keyed
при регистрации необходимо указать тип закрытого сервиса, например IMyInterface<string, bool>
так что вы не можете использовать открытый универсальный там, как typeof(IMyInterface<,>)
Однако, потому что при использовании RegisterAssemblyTypes
нет API, где вы могли бы получить в настоящее время зарегистрированный закрытый тип, чтобы зарегистрировать его как Keyed
, Так что вам нужно реализовать "сканирование сборки" вручную:
Вы должны заменить свой RegisterAssemblyTypes
вызовите что-то вроде этого (вам, вероятно, понадобится больше обработки ошибок в производственной среде):
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<,>)));
}
Это эквивалентно следующей ручной регистрации, которая требуется для RegisterGenericDecorator
работать (при условии, что MyImpl1
а также MyImpl3
был отмечен MyAttribute
:
builder.RegisterType<MyImpl1>()
.Keyed<IMyInterface<string, bool>>("CachableQueries");
builder.RegisterType<MyImpl3>()
.Keyed<IMyInterface<bool, bool>>("CachableQueries");
Обратите внимание, что вы не можете использовать RegisterGeneric
здесь, потому что у вас есть этот особенный MyAttribute
фильтр.
Хорошо, я не знал, что вопрос был с 3 года назад, поскольку он был обновлен только неделю назад.
Мы можем воспользоваться цепочечными методами во время регистрации, чтобы отделить типы, украшенные атрибутом, от тех, которые не являются таковыми.
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();
}
}
}
Вывод из консоли
BooleanToStringTwo is being decorated!
True from BooleanToStringTwo
BooleanToStringOne is being decorated!
True from BooleanToStringOne
True from BooleanToStringThree