How to do open generic decorator chaining with unity + UnityAutoRegistration

我们两清 提交于 2019-11-28 10:34:52

This would be the equivalent in Unity:

// Go look in all assemblies and register all implementa-
// tions of ICommandHandler<T> by their closed interface:
var container = new UnityContainer();

var handlerRegistrations =
    from assembly in AppDomain.CurrentDomain.GetAssemblies()
    from implementation in assembly.GetExportedTypes()
    where !implementation.IsAbstract
    where !implementation.ContainsGenericParameters
    let services =
        from iface in implementation.GetInterfaces()
        where iface.IsGenericType
        where iface.GetGenericTypeDefinition() == 
            typeof(ICommandHandler<>)
        select iface
    from service in services
    select new { service, implementation };

foreach (var registration in handlerRegistrations)
{
    container.RegisterType(registration.service, 
        registration.implementation, "Inner1");
}

// Decorate each returned ICommandHandler<T> object with an
// TransactionCommandHandlerDecorator<T>.
container.RegisterType(typeof(ICommandHandler<>), 
    typeof(TransactionCommandHandlerDecorator<>),
    "Inner2",
    InjectionConstructor(new ResolvedParameter(
        typeof(ICommandHandler<>), "Inner1")));

// Decorate each returned ICommandHandler<T> object with an
// DeadlockRetryCommandHandlerDecorator<T>.
container.RegisterType(typeof(ICommandHandler<>), 
    typeof(DeadlockRetryCommandHandlerDecorator<>), 
    InjectionConstructor(new ResolvedParameter(
        typeof(ICommandHandler<>), "Inner2")));
Andre

I hope I understand the problem correctly and I was curious to try and get this to work and I'm by no means an expert on Unity but I was thinking of a solution that is a little easier to implement and would also be easier to do with a different container. It would seem like the only way to support the open generic as well as the other types is to have 2 separate containers (1 for the open generic) and one for your command handlers, this might not be the best way but it worked with Unity and I'm assuming will also be easier with others.

So I came up with this:

I created the containers as follows (you can use your convention approach still im sure for the handler container)

var container = new UnityContainer();

var container2 = new UnityContainer();

container2.RegisterType(typeof(ICommandHandler<QueryCommand>),
    typeof(QueryCommandHandler));
container.RegisterInstance("Handlers", container2);
container.RegisterInstance(container);
container.RegisterType(typeof(ICommandHandler<>),
    typeof(DecoratedHandler<>));

You see container 2 containing the Handlers as a named instance.

Then I just created a Generic base decorator class as per the requirement of the pattern:

public class DecoratorCommandHandler<TCommand>
    : ICommandHandler<TCommand>
{
    private ICommandHandler<TCommand> inner;
    public DecoratorCommandHandler(
        ICommandHandler<TCommand> inner)
    {
        this.inner = inner;
    }

    public virtual void Handle(TCommand command)
    {
         this.inner.Handle(command);
    }
}

Second I created another Generic Handler that would wrap all the Decorations you want to do for your solution, here you will add decorating for TryCatch/Caching/Transactions or whatever else you want to apply to each command handler:

public class DecoratedHandler<TCommand>
    : DecoratorCommandHandler<TCommand>
{
    public DecoratedHandler(UnityContainer container)
        : base(BuildInner(container))
    {
    }

    private static ICommandHandler<TCommand> BuildInner(
        UnityContainer container)
    {
         var handlerContainer =
             container.Resolve<UnityContainer>("Handlers");
         var commandHandler =
             handlerContainer.Resolve<ICommandHandler<TCommand>>();

         return new TryCatchCommandHandler<TCommand>(commandHandler);
    }
}

You will notice that the first inner resolves the actual command handler as per the one you requested like QueryCommandHandler, UpdateCommandHandler, ExecuteCommandHandler or whatever one dealing with specifics. And then gets wrapped with all the decorators you want common to all of them.

Then I was able to resolve the correct handler, decorated in the correct way:

ICommandHandler<QueryCommand> handler =
    container.Resolve<ICommandHandler<QueryCommand>>();

var cmd = new QueryCommand();

handler.Handle(cmd);

Hope this helps

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!