5

Autofac によってオープン ジェネリック方式で登録される汎用コマンド ハンドラーが多数あります。すべてのハンドルを装飾するデコレーターがいくつかあります。ここで、1 つのコマンド ハンドラーに対してのみデコレーターを登録する必要があり、他のすべてのコマンド ハンドラーには影響しません。これが私の試みですが、登録が正しく行われていないようです。

以下は、私たちのコードに似た簡単なテスト コードです。

次のように機能する何百ものコマンドがあります。

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

頭をキーボードに何度もぶつけた後、私の問題に対する何らかの解決策が得られました。

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 に答える
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 に答える
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を意味します。は 1 つの特定のものであるためCommandHandlerDecorator1<T>、そのような型のみをラップします。したがって、あなたの場合、前の登録を行った後に完了です。TestCommandHandlerDecoratorICommandHandler<T>

しかし、あなたのケースは実際には単純なケースです。Simple Injector は、述語またはジェネリック型制約に基づいて条件付きでデコレーターを適用するなど、はるかに興味深いシナリオをサポートします。

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

に述語を指定するRegisterDecoratorことで、特定の登録にデコレーターを適用するかどうかを制御できます。

もう 1 つのオプションは、ジェネリック型の制約をデコレーターに適用することです。Simple Injector は、ジェネリック型の制約を処理できます。

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

これは、 から派生したコマンドを処理するコマンド ハンドラがある場合に便利ですがTestCommand、多くの場合、コマンドは 1 つまたは複数のインターフェイスを実装し、それらのインターフェイスの 1 つを使用してコマンドを処理するコマンド ハンドラにデコレータが適用されます。

いずれにしても、デコレータは次のように簡単に登録できます。

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

最終的にはすべてのコンテナーでこれを機能させることができると思いますが、ほとんどのコンテナーでは、これを実現するのが非常に複雑になります。これは、Simple Injector が優れているところです。

于 2013-08-05T08:16:51.893 に答える