AutoFac - 为一些开放的Generic注册装饰器

时间:2013-08-06 19:13:01

标签: c# dependency-injection ioc-container autofac

我正在尝试设置一个似乎有复杂要求的Autofac模块。

这里是:

我有一个通用界面:

public interface IMyInterface<TFoo, TBar>

我有一堆实现此接口的类

e.g。

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>>());

我知道拼图的最后一块是Key,它实际上需要将类型传递给Keyed("CachableQueries", THE_TYPE);,但它不能打球。

更新

nemesv让我朝着正确的方向前进。

作为我的问题的一部分,我忘了提到我还需要注册IMyInterface的所有实现&lt;,&gt;那个没有[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 个答案:

答案 0 :(得分: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工作所需的以下手动注册(假设MyImpl1MyImpl3标有MyAttribute

builder.RegisterType<MyImpl1>()
       .Keyed<IMyInterface<string, bool>>("CachableQueries");
builder.RegisterType<MyImpl3>()
       .Keyed<IMyInterface<bool, bool>>("CachableQueries");

请注意,您无法在此使用RegisterGeneric,因为您有这个特殊的MyAttribute过滤器。

答案 1 :(得分:2)

好吧,我没有意识到这个问题来自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