-
-
Notifications
You must be signed in to change notification settings - Fork 843
Description
Describe the Bug
When I register a generic decorator for my command handlers with RegisterGenericDecorator()
and then try to resolve all command handlers by container.Resolve(IEnumerable<IHandler<Command>>)
, the returned decorators all decorate the same instance.
Steps to Reproduce
full code here
[TestFixture]
public class ReproTest
{
[Test]
public void ResolveAllDecoratedCommandHandlers_IsSuccessful()
{
//Arrange
var builder = new ContainerBuilder();
builder.RegisterType(typeof(CommandHandler1))
.As(typeof(ICommandHandler<Command>))
.As(typeof(IHandler<Command>));
builder.RegisterType(typeof(CommandHandler2))
.As(typeof(ICommandHandler<Command>))
.As(typeof(IHandler<Command>));
builder.RegisterGenericDecorator(
typeof(CommandHandlerDecorator<>),
typeof(IHandler<>),
context => context.ImplementationType.GetInterfaces().Any(t => t.IsGenericType
&& t.GetGenericTypeDefinition() == typeof(ICommandHandler<>))
);
var container = builder.Build();
//Act
var commandHandlers = ((IEnumerable<IHandler<Command>>)container.Resolve(typeof(IEnumerable<IHandler<Command>>))).ToList();
//Assert
Assert.AreEqual(
((CommandHandlerDecorator<Command>)commandHandlers[0]).Decorated.GetType(),
typeof(CommandHandler1)); //fails, decorated is typeof(CommandHandler2)
Assert.AreEqual(
((CommandHandlerDecorator<Command>)commandHandlers[1]).Decorated.GetType(),
typeof(CommandHandler2));
}
}
interface IRequest { }
interface IHandler<in TRequest> where TRequest : IRequest { public void Handle(TRequest request); }
interface ICommand: IRequest { }
interface ICommandHandler<in TCommand>: IHandler<TCommand> where TCommand: ICommand { }
class Command : ICommand { }
class CommandHandler1 : ICommandHandler<Command> { public void Handle(Command command) => Console.WriteLine("CommandHandler1"); }
class CommandHandler2 : ICommandHandler<Command> { public void Handle(Command command) => Console.WriteLine("CommandHandler2"); }
class CommandHandlerDecorator<TCommand> : ICommandHandler<TCommand> where TCommand : ICommand
{
public ICommandHandler<TCommand> Decorated { get; }
public CommandHandlerDecorator(ICommandHandler<TCommand> decorated) => Decorated = decorated;
public void Handle(TCommand request)
{
Console.WriteLine($"Command Decorator for {Decorated.GetType().FullName}");
Decorated.Handle(request);
}
}
Expected Behavior
the above unit test should pass:
Without using decorators, container.Resolve<IEnumerable<IHandler<Command>>()
returns expected { CommandHandler1, CommandHandler2 }
.
With decorators, I'm expecting { CommandHandlerDecorator(CommandHandler1), CommandHandlerDecorator(CommandHandler2) }
but getting { CommandHandlerDecorator(CommandHandler2), CommandHandlerDecorator(CommandHandler2) }
.
Dependency Versions
Autofac: 6.4.0
Additional Info
This was encountered with the MediatR library where MediatR works on its base interface IRequest
.
I have control over the registration part (the "Arrange" section in the unit test) but not how MediatR resolves the command handlers.
In other words, the following solutions would not solve my issue:
- Resolve by calling
Resolve(typeof(IEnumerable<ICommandHandler<Command>>)
rather thanResolve(typeof(IEnumerable<IHandler<Command>>)
[no control over that] - Get rid of
ICommand
andIQuery
[but I needz them! for example, theUnitOfWorkCommandHandlerDecorator
should only apply to CommandHandlers] - ditch MediatR
- use
IPipelineBehavior
of MediatR instead [the problem is present forMediatR.INofitication
s as well and those don't support pipelines]
(thank you for your time, you are appreciated!)