繁体   English   中英

仅为一个通用命令处理程序注册Autofac装饰器

[英]Register Autofac decorator for only one generic command handler

我们有许多通用命令处理程序,由Autofac以开放式通用方式注册。 我们有几个装饰器来装饰所有手柄。 现在我需要为一个命令处理程序注册一个装饰器,而不是影响所有其他命令处理程序。 这是我的尝试,但我似乎没有正确的注册。

这是与我们的代码类似的简单测试代码:

我们有数百个命令如下:

class NormalCommand : ICommand { }

// This command handler should not be decorated
class NormalCommandHandler : ICommandHandler<NormalCommand>
{
    public void Handle(NormalCommand command) { }
}

我想换只TestCommandHandler在装饰TestCommandHandlerDecorator

class TestCommand : ICommand { }

// And I would like to put decorator around this handler
class TestCommandHandler : ICommandHandler<TestCommand>
{
    public void Handle(TestCommand command) { }
}

// This decorator should be wrapped only around TestCommandHandler
class TestCommandHandlerDecorator : ICommandHandler<TestCommand>
{
    private readonly ICommandHandler<TestCommand> decorated;

    public TestCommandHandlerDecorator(ICommandHandler<TestCommand> decorated)
    {
        this.decorated = decorated;
    }

    public void Handle(TestCommand command)
    {
        // do something
        decorated.Handle(command);
        // do something again
    }
}

这就是我注册我的组件的方式:

static class AutofacRegistration
{
    public static IContainer RegisterHandlers()
    {
        var builder = new ContainerBuilder();

        //Register All Command Handlers but not decorators
        builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(AutofacRegistration)))
            .Where(t => !t.Name.EndsWith("Decorator"))
            .AsClosedTypesOf(typeof(ICommandHandler<>))
            .InstancePerLifetimeScope();

        // and here is the battle! 
        builder.RegisterType<TestCommandHandler>()
               .Named<ICommandHandler<TestCommand>>("TestHandler")
               .InstancePerLifetimeScope();

        // this does not seem to wrap the decorator
        builder.RegisterDecorator<ICommandHandler<TestCommand>>(
            (c, inner) => new TestCommandHandlerDecorator(inner),
            fromKey: "TestHandler")
               .Named<ICommandHandler<TestCommand>>("TestHandler1")
               .InstancePerLifetimeScope();

        return builder.Build();
    }
}

这就是我尝试确认我得到命令处理程序/装饰器的正确实例的方法:

class AutofacRegistrationTests
{
    [Test]
    public void ResolveNormalCommand()
    {
        var container = AutofacRegistration.RegisterHandlers();

        var result = container.Resolve<ICommandHandler<NormalCommand>>();

        // this resolves correctly
        Assert.IsInstanceOf<NormalCommandHandler>(result); // pass
    }

    [Test]
    public void TestCommand_Resolves_AsDecorated()
    {
        var container = AutofacRegistration.RegisterHandlers();

        var result = container.Resolve<ICommandHandler<TestCommand>>();

        // and this resolves to TestCommandHandler, not decorated!
        Assert.IsInstanceOf<TestCommandHandlerDecorator>(result); // FAILS!
    }
}

正如评论所说,装饰器没有被应用,装饰器注册被忽略。

任何想法如何注册这个装饰? 我究竟做错了什么?

在我的头撞击键盘足够多次之后,我已经找到了解决问题的方法:

static class AutofacRegistration
{
    public static IContainer RegisterHandlers()
    {
        var builder = new ContainerBuilder();

        builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(AutofacRegistration)))
            .AsClosedTypesOf(typeof(ICommandHandler<>))
            .InstancePerLifetimeScope();

        builder.RegisterType<TestCommandHandler>()
               .Named<ICommandHandler<TestCommand>>("TestHandler")
               .InstancePerLifetimeScope();

        // this works!
        builder.Register(c => new TestCommandHandlerDecorator(c.ResolveNamed<ICommandHandler<TestCommand>>("TestHandler")))
               .As<ICommandHandler<TestCommand>>()
               .InstancePerLifetimeScope();

        return builder.Build();
    }
}

在这里,我没有使用Autofac的装饰器功能并手动包装装饰器。 因此,如果装饰器中的依赖项数量增加,我将需要更新容器以解决所有必需的依赖项。

如果您知道更好的解决方案,请告诉我们!

要避免在@ trailmax的答案中手动注册,您可以定义以下扩展方法:

public static class ContainerBuilderExtensions
{
    public static void RegisterDecorator<TService, TDecorater, TInterface>(this ContainerBuilder builder,
        Action<IRegistrationBuilder<TService, ConcreteReflectionActivatorData, SingleRegistrationStyle>> serviceAction,
        Action<IRegistrationBuilder<TDecorater, ConcreteReflectionActivatorData, SingleRegistrationStyle>> decoratorAction)
    {
        IRegistrationBuilder<TService, ConcreteReflectionActivatorData, SingleRegistrationStyle> serviceBuilder = builder
            .RegisterType<TService>()
            .Named<TInterface>(typeof (TService).Name);

        serviceAction(serviceBuilder);

        IRegistrationBuilder<TDecorater, ConcreteReflectionActivatorData, SingleRegistrationStyle> decoratorBuilder =
            builder.RegisterType<TDecorater>()
                .WithParameter(
                    (p, c) => p.ParameterType == typeof (TInterface),
                    (p, c) => c.ResolveNamed<TInterface>(typeof (TService).Name))
                .As<TInterface>();

        decoratorAction(decoratorBuilder);
    }
}

然后像这样使用它:

        builder.RegisterDecorator<TestCommandHandler, TestCommandHandlerDecorator, ICommandHandler<TestCommand>>(
            s => s.InstancePerLifetimeScope(),
            d => d.InstancePerLifetimeScope());

最后,该功能已添加到4.9.0版本的Autofac中。

builder.RegisterAssemblyTypes(typeof(AutofacRegistration).Assembly)
        .AsClosedTypesOf(typeof(ICommandHandler<>));
builder.RegisterDecorator<TestCommandHandlerDecorator, ICommandHandler<TestCommand>>();

我不能在Castle Windsor或StructureMap上提供任何示例,根据我的经验,使用除Autofac和Simple Injector之外的其他任何东西都很难应用开放式通用装饰器。 当涉及有条件地应用开放式通用装饰器(您的特定场景)时,AFAIK Simple Injector是唯一具有下降支持的DI容器。

使用Simple Injector,您可以按如下方式注册所有命令处理程序:

container.RegisterManyForOpenGeneric(
    typeof(ICommandHandler<>),
    typeof(ICommandHandler<>).Assembly);

装饰者可以注册如下:

container.RegisterDecorator(
    typeof(ICommandHandler<>),
    typeof(CommandHandlerDecorator1<>));

container.RegisterDecorator(
    typeof(ICommandHandler<>),
    typeof(TestCommandHandlerDecorator));

container.RegisterDecorator(
    typeof(ICommandHandler<>),
    typeof(CommandHandlerDecorator2<>));

装饰器按照它们的注册顺序添加,这意味着在上面的例子中, CommandHandlerDecorator2<T>包装了TestCommandHandlerDecorator ,它包装了CommandHandlerDecorator1<T> ,它包装了任何具体的命令处理程序。 由于TestCommandHandlerDecorator是针对一个特定的ICommandHandler<T> ,因此它只包含在这些类型中。 所以在你的情况下,你在完成之前的注册后就完成了。

但你的情况实际上是一个简单的案例。 Simple Injector支持更有趣的场景,例如基于谓词或泛型类型约束有条件地应用装饰器:

container.RegisterDecorator(
    typeof(ICommandHandler<>),
    typeof(SomeDecorator<>), c =>
        c.ServiceType.GetGenericArguments()[0] == typeof(TestCommand));

通过向RegisterDecorator提供谓词,您可以控制装饰器是否应用于某个注册。

另一种选择是将泛型类型约束应用于装饰器。 Simple Injector能够处理泛型类型约束:

// This decorator should be wrapped only around TestCommandHandler
class TestCommandHandlerDecorator<T> : ICommandHandler<T>
    where T : TestCommand // GENERIC TYPE CONSTRAINT
{
    // ...
}

当您拥有处理从TestCommand派生的命令的任何命令处理程序时,这很有用,但通常您会看到命令实现一个或多个接口,并且装饰器应用于使用其中一个接口处理命令的命令处理程序。

但不管怎样,装饰器可以简单地注册如下:

container.RegisterDecorator(
    typeof(ICommandHandler<>),
    typeof(TestCommandHandlerDecorator<>));

虽然我认为最终你可以在每个容器中使用它,但是大多数容器都会使这个变得非常复杂。 这就是Simple Injector擅长的地方。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM