RyannnnnnR
RyannnnnnR

Reputation: 39

Autofac not resolving interfaces in other project

I'm trying to write a generic command bus (Part of class library) that uses different commands and handlers in each of my services.

The following code produces the following exception:

System.Exception: Command does not have any handler RegisterUserCommand

I was under the impression passing the the ExecutingAssemblies of my UserService would allow the Container to resolve the handler in my UserService but apparently not.

Am I doing something wrong?

CommandBus:

public interface ICommandBus
{
    void Send<T>(T Command) where T : ICommand;
}
public class CommandBus : ICommandBus
{
    private IContainer Container { get; set; }

    public CommandBus(Assembly assembly)
    {
        Container = new CommandBusContainerConfig().Configure(assembly);
    }
    public void Send<TCommand>(TCommand command) where TCommand : ICommand
    {
        var handlers = Container.Resolve<IEnumerable<ICommandHandler<TCommand>>>().ToList();
        if (handlers.Count == 1)
        {
            handlers[0].Handle(command);
        }
        else if (handlers.Count == 0)
        {
            throw new System.Exception($"Command does not have any handler {command.GetType().Name}");
        }
        else
        {
            throw new System.Exception($"Too many registred handlers - {handlers.Count} for command {command.GetType().Name}");
        }
    }
}

ContainerBuilder:

public class CommandBusContainerConfig : IContainerConfig
{

    public IContainer Configure(Assembly executingAssembly)
    {
        var builder = new ContainerBuilder();

        builder.RegisterAssemblyTypes(executingAssembly)
            .Where(x => x.IsAssignableTo<ICommandHandler>())
            .AsImplementedInterfaces();

        builder.Register<Func<Type, ICommandHandler>>(c =>
        {
            var ctx = c.Resolve<IComponentContext>();

            return t =>
            {
                var handlerType = typeof(ICommandHandler<>).MakeGenericType(t);
                return (ICommandHandler)ctx.Resolve(handlerType);
            };
        });
        return builder.Build();
    }
}

In my UserService(ASP.Net Core 3), which is a different project that references the above CommandBus:

public class RegisterUserCommand : ICommand
{
    public readonly string Name;
    public readonly Address Address;
    public string MobileNumber;
    public string EmailAddress;

    public RegisterUserCommand(Guid messageId, string name, string mobileNumber, string emailAddress, Address address)
    {
        Name = name;
        Address = address;
        MobileNumber = mobileNumber;
        EmailAddress = emailAddress;
    }

CommandHandler:

 public class RegisterUserComnmandHandler : ICommandHandler<RegisterUserCommand>
{

    public void Handle(RegisterUserCommand command)
    {
        Console.WriteLine($"Create user {command.Name} {command.MobileNumber} - handler");
    }
}

Startup:

public void ConfigureServices(IServiceCollection services)
    {

        services.AddSingleton<ICommandBus>(new CommandBus(Assembly.GetExecutingAssembly()));
    }

Controller:

    private readonly ICommandBus _commandBus;
    public UsersController(ICommandBus commandBus) {
        _commandBus = commandBus;
    }
    // POST api/values
    [HttpPost]
    public async Task<IActionResult> Post([FromBody]RegisterUserCommand command)
    {
            if (ModelState.IsValid)
            {
                CommandBus commandBus = new CommandBus(Assembly.GetExecutingAssembly());
                commandBus.Send(command);
                _commandBus.Send(Command); //Same result as above
                // return result
                return Ok(command);
            }
            return BadRequest();
        }

Thanks,

Upvotes: 1

Views: 864

Answers (2)

Cyril Durand
Cyril Durand

Reputation: 16187

The main error is here :

builder.RegisterAssemblyTypes(executingAssembly)
       .Where(x => x.IsAssignableTo<ICommandHandler>())
       .AsImplementedInterfaces();

RegisterUserComnmandHandler is not a ICommandHandler but a ICommandHandler<RegisterUserCommand>. Instead of IsAssignableTo<> method you can use the IsClosedTypeOf which is an Autofac extension which do exactly what you can.

builder.RegisterAssemblyTypes(executingAssembly)
       .Where(x => x.IsClosedTypeOf(typeof(ICommandHandler<>)))
       .AsImplementedInterfaces();

By the way, in your code sample you are using another Container. Most of the time it is always simple to have a single container for the whole application. To get things organised you can use autofac module. You are also resolving straight from the container and not using scope this means that your instance graph won't be disposed at the end of the operation but will stay for the whole lifetime of the container.

In your controller, I saw that you are building a new CommandBus for each request, which will create a new container. Building a new container is a heavy operation and you should avoid doing it often but only once of the startup of the application.

Also I don't get the point of this registration :

builder.Register<Func<Type, ICommandHandler>>(c =>
{
    var ctx = c.Resolve<IComponentContext>();

    return t =>
    {
        var handlerType = typeof(ICommandHandler<>).MakeGenericType(t);
        return (ICommandHandler)ctx.Resolve(handlerType);
    };
}); 

It doesn't looks you need it and it seems useless to me

Upvotes: 2

RyannnnnnR
RyannnnnnR

Reputation: 39

This took me a while to figure out. But my CommandHandler interface was incorrectly defined. It should look like:

public interface ICommandHandler { }
public interface ICommandHandler<T> : ICommandHandler where T : ICommand
{
    void Handle(T command);
}

}

When trying to resolve the CommandHandler in the Autofac configuration class, the .Where(x => x.IsAssignableTo<ICommandHandler>()) was failing because the class was assignable to ICommandHandle<T> not ICommandHandler

Upvotes: 0

Related Questions