3

Went off on an interesting tangent today after reading this article on command handler decoration. I wanted to see if I could implement the pattern using Unity instead of SimpleInjector, and so far it is proving extremely difficult.

The first thing I had to do was install UnityAutoRegistration to resolve the open generic ICommandHandler<TCommand> interface. Current solution for that aspect is as follows:

Container = new UnityContainer().LoadConfiguration();

Container.ConfigureAutoRegistration()
    .ExcludeSystemAssemblies()
    .Include(type => type.ImplementsOpenGeneric(typeof(ICommandHandler<>)),
        (t, c) => c.RegisterType(typeof(ICommandHandler<>), t)
    )
    .ApplyAutoRegistration()
;

This works for the first part, resolving any single ICommandHandler<TCommand>. What's proven frustrating so far is implementing a decoration handler. As soon as I add a second ICommandHandler<TCommand> as a decorator, Unity throws a StackOverflowException. I don't know enough about Unity internals, but I'm guessing this is because it can't figure out which instance to resolve to -- the command handler, or the command handler decorator -- since both implement the ICommandHandler<TCommand> interface.

Googling around led me first to this article, which explains how to do it in what I would consider a brute force method. I also found these related pages but none is a complete solution to my problem (and I am too ignorant to figure it out for myself).

I then found this article, which seems to address my same concerns. However beefy's solution does not account for dealing with open generics. Currently most of our dependencies are loaded from a unity section in the .config file, so I don't want to write a ton of compiled code for each handler or decorator. It seems like having some kind of UnityContainerExtension and DecoratorBuildStrategy is the right way to go, but I can't figure it out. I have been playing with beefy's code for a little while now, and am completely stuck. My attempts to modify his code to account for generics has led to BadImageFormatExceptions (An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B)).

I like the idea of doing this to implement the decorator chaining, because it's short, and there is only 1 line per concern:

var container = new Container();

// Go look in all assemblies and register all implementations
// of ICommandHandler<T> by their closed interface:
container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>),
    AppDomain.CurrentDomain.GetAssemblies());

// Decorate each returned ICommandHandler<T> object with an
// TransactionCommandHandlerDecorator<T>.
container.RegisterDecorator(typeof(ICommandHandler<>),
    typeof(TransactionCommandHandlerDecorator<>));

// Decorate each returned ICommandHandler<T> object with an
// DeadlockRetryCommandHandlerDecorator<T>.
container.RegisterDecorator(typeof(ICommandHandler<>),
    typeof(DeadlockRetryCommandHandlerDecorator<>));

...but I don't want to change my container from Unity to Simple Injector if I don't have to.

SO my question is how could I go about implementing open generic decorator chaining using unity (plus UnityAutoRegistration)?

4

2 回答 2

4

这将是 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")));
于 2012-12-13T12:09:51.760 回答
3

我希望我正确理解了这个问题,我很想尝试让它工作,我绝不是 Unity 专家,但我正在考虑一个更容易实现并且也更容易实现的解决方案用不同的容器。似乎支持开放泛型以及其他类型的唯一方法是拥有 2 个单独的容器(1 个用于开放泛型)和一个用于您的命令处理程序,这可能不是最好的方法,但它适用于 Unity而且我假设与其他人一起也会更容易。

所以我想出了这个:

我按如下方式创建了容器(你可以使用你的约定方法仍然确定处理程序容器)

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

您会看到容器 2 包含作为命名实例的处理程序。

然后我根据模式的要求创建了一个通用基础装饰器类:

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

其次,我创建了另一个通用处理程序,它将包装您想要为您的解决方案执行的所有装饰,在这里您将为 TryCatch/Caching/Transactions 或您想要应用于每个命令处理程序的任何其他内容添加装饰:

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

您会注意到第一个内部会根据您请求的命令处理程序解析实际的命令处理程序,例如,QueryCommandHandler或任何处理细节的处理程序。然后用你想要的所有装饰器进行包装。UpdateCommandHandlerExecuteCommandHandler

然后我能够解析正确的处理程序,以正确的方式装饰:

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

var cmd = new QueryCommand();

handler.Handle(cmd);

希望这可以帮助

于 2012-03-28T05:26:48.370 回答