5

我们有很多由 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!
    }
}

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

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

4

4 回答 4

3

为避免在@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());
于 2014-09-10T18:43:58.300 回答
3

在用键盘敲了足够多的头之后,我找到了解决问题的方法:

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 的装饰器功能并手动包装装饰器。因此,如果装饰器中的依赖项数量增加,我需要更新容器以解决所有必需的依赖项。

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

于 2013-08-02T10:33:43.793 回答
2

我无法给出任何关于 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 擅长的地方。

于 2013-08-05T08:16:51.893 回答
2

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

builder.RegisterAssemblyTypes(typeof(AutofacRegistration).Assembly)
        .AsClosedTypesOf(typeof(ICommandHandler<>));
builder.RegisterDecorator<TestCommandHandlerDecorator, ICommandHandler<TestCommand>>();
于 2019-02-11T12:24:46.630 回答