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
Другие вопросы по тегам