简体   繁体   中英

Register Autofac decorator for only one generic command handler

We have a lot of generic command handlers that are registered by Autofac in open-generic fashion. We have couple decorators that decorate all handles. Now I need to register a decorator for only one command handler and not affect all other command handlers. Here is my attempt on that, but I don't seem to get the registration right.

Here is simple test code that is similar to our code:

We have hundreds of commands working like this:

class NormalCommand : ICommand { }

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

And I would like to wrap ONLY TestCommandHandler in decorator 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
    }
}

That is how I register my components:

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

And this is how I try to confirm that I get correct instances of command handlers/decorators:

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!
    }
}

As the comment says, decorator is not getting applied, decorator registration is ignored.

Any ides how to register this decorator?? What am I doing wrong?

After smacking my head against keyboard enough times, I've got some kind of solution to my problem:

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

Here I'm not using decorator functionality of Autofac and wrapping the decorator manually. So if number of dependencies in decorator grows, I'll need to update the the container to resolve all required dependencies.

If you know of a better solution, please let me know!

To avoid the manual registration in @trailmax's answer you can define the following extension method:

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

And then use this like so:

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

Finally, that feature was added in 4.9.0 version of Autofac.

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

I can't give any examples on Castle Windsor or StructureMap, and in my experience it is very hard to apply open generic decorators using anything else than Autofac and Simple Injector. When it comes to conditionally applying open generic decorators (your specific scenario) AFAIK Simple Injector is the only DI container with descent support for this.

With Simple Injector, you register all your command handlers as follows:

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

Decorators can be registered as follows:

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

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

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

Decorators are added in the order they are registered, which means that in the above case CommandHandlerDecorator2<T> wraps TestCommandHandlerDecorator which wraps CommandHandlerDecorator1<T> which wraps any concrete command handler. Since the TestCommandHandlerDecorator is for one specific ICommandHandler<T> it is only wrapped around such types. So in your case, you're done after doing the previous registration.

But your case is actually a simple case. Simple Injector supports much more interesting scenarios, such as conditionally applying decorators based on a predicate or on a generic type constraint:

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

By supplying a predicate to the RegisterDecorator you can control if a decorator is applied to a certain registration.

Another option is to apply generic type constraints to the decorator. Simple Injector is able to handle generic type constraints:

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

This is useful when you have any command handler that handles commands that derive from TestCommand , but often you'll see that commands implement one or multiple interfaces and decorators are applied to command handlers that handle a command with one of those interfaces.

But either way, the decorator can simply be registered as follows:

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

Although I think that in the end you can get this working in every container, most containers will make this really complicated to achieve. This is where Simple Injector excels.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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