AutoFac - Registering a decorator for some of an o

2020-03-24 04:55发布

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

here goes:

I have a generic interface:

public interface IMyInterface<TFoo, TBar>

I have a bunch of classes that implement this interface

e.g.

class MyImpl1 : IMyInterface<string, bool> { }
class MyImpl2 : IMyInterface<bool, string> { }
class MyImpl3 : IMyInterface<bool, string> { }

Finally, I have a decorator:

class MyDecorator<TFoo, TBar> : IMyInterface<TFoo, TBar>

I only want to "Decorate" the implementations (of MyInterface) that have a particular attribute. So all implementations of MyInterface that have an Attribute of [MyAttribute] are decorated with MyDecorator.

I'm close but no cigar yet:

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

I understand that the final piece of the puzzle is the Key, it actually need to pass the type into the Keyed("CachableQueries", THE_TYPE); but its not playing ball.

Update

nemesv sent me in the right direction.

As part of my question, I forgot to mention that I also needed to register all the implementations of IMyInterface<,> that didn't have the [MyAttribute].

I did this in two stages. First Register the types with the Decorator and then register the rest.

My Solution: I know it needs refactoring, but as a proof of concept. it works.

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条回答
▲ chillily
2楼-- · 2020-03-24 05:19

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
查看更多
等我变得足够好
3楼-- · 2020-03-24 05:22

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.

查看更多
登录 后发表回答