簡體   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