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

问题描述 投票:5回答:4

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

正如评论说,装饰是没有得到应用,装饰注册将被忽略。

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

c# decorator autofac
4个回答
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的装饰功能和手动缠绕装饰。所以,如果在装饰依赖数量增长,我需要更新的容器来解决所有需要的依赖。

如果你知道一个更好的解决方案,请让我知道!


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

2
投票

最后,在4.9.0版本Autofac加入该功能。

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

1
投票

我不能给在温莎城堡或StructureMap任何例子,在我的经验是很难申请使用任何比Autofac和简单的注射器其他开放式泛型装饰。当涉及到有条件地采用开放式通用装饰(您的具体方案)据我所知简单喷油器是这个血统支持的唯一DI容器。

与简单的喷油器,注册您的所有命令处理程序如下:

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>它只是身边有这样类型的包裹。所以你的情况,你正在做以前注册后完成的。

但是,你的情况实际上是一个简单的例子。简单的喷油器支持更多有趣的场景,如基于谓词或一个泛型类型约束条件将装饰:

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

通过提供一个谓词到RegisterDecorator如果一个装饰施加到一定的注册可以控制。

另一种选择是泛型类型约束适用于装饰。简单的喷油器能够处理泛型类型的限制:

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

虽然我认为那到底你可以在每个集装箱这个工作,大多数容器将使这个相当复杂的实现。这是简单的喷油器的过人之处。

© www.soinside.com 2019 - 2024. All rights reserved.