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());
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With