Skip to content

[Autofac 6.4.0]: RegisterGenericDecorator() resolves incorrect instance for the decorated type  #1330

@ViktorDolezel

Description

@ViktorDolezel

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 than Resolve(typeof(IEnumerable<IHandler<Command>>) [no control over that]
  • Get rid of ICommand and IQuery [but I needz them! for example, the UnitOfWorkCommandHandlerDecorator should only apply to CommandHandlers]
  • ditch MediatR
  • use IPipelineBehavior of MediatR instead [the problem is present for MediatR.INofitications as well and those don't support pipelines]

(thank you for your time, you are appreciated!)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions