AutoFac - Registering a decorator for some of an open Generic

后端 未结 2 1968
刺人心
刺人心 2021-01-13 14:19

I\'m attempting to set up an Autofac module that seems to have a complicated requirement.

here goes:

I have a generic interface:



        
相关标签:
2条回答
  • 2021-01-13 15:00

    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
    
    0 讨论(0)
  • 2021-01-13 15:08

    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.

    0 讨论(0)
提交回复
热议问题