4

我正在尝试设置一个似乎有复杂要求的 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具有特定属性的(的)实现。因此,所有具有 Attribute 的 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);但它不是在玩球。

更新

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

作为我的问题的一部分,我忘了提到我还需要注册没有 [MyAttribute] 的 IMyInterface<,> 的所有实现。

我分两个阶段做到了这一点。首先用装饰器注册类型,然后注册其余的。

我的解决方案:我知道它需要重构,但作为概念证明。有用。

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

    }
}
4

2 回答 2

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过滤器。

于 2013-08-07T11:07:50.743 回答
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
于 2016-12-13T05:05:15.997 回答