Search code examples
c#genericsautofacregistrationopen-generics

Get all AsClosedTypesOf registration variants from the Autofac Builder


Lets assume these classes/interfaces:

public interface ICommand
{
}

public class SomeCommand : ICommand
{
}

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

public class SomeCommandHandler : ICommandHandler<SomeCommand>
{
   void Handle(SomeCommand arg){ /* do something */ }
}

public interface ICommandBus
{
   void RegisterHandler<T>(T t) where T : ICommandHandler<T>;
   void RegisterHandlerByParam<T2>(ICommandHandler<T2> t2) where T2 : ICommand;
   void RegisterHandlerMethod<T3>(Action<T3> action) where T3 : ICommand
}

public class TheCommandBus : ICommandBus
{
     // implements ICommandBus ...
}

I want to register all implementations of ICommandHandler<> automatically. All variants (Register*) are valid solutions even though I would prefer Action parameter while it is more flexible and has no dependency to Handler interface (just action delegate).

Autofac has the capability to register types based upon assembly scanning and register the found implementations of a generic interface, for example:

builder.RegisterAssemblyTypes(Assembly.LoadFrom("MyAssembly.dll"))
       .AsClosedTypesOf(typeof(ICommandHandler<>));

So I have all implementations registered. Now I need to Register them all automatically to TheCommandBus. How to do that?

I can do this manually by adding those lines (e.g. during OnActivated):

builder.RegisterType<TheCommandBus>().As<ICommandBus>().OnActivated(args =>
        {
            // now I need to list all implementations here!!! please, no...
            args.Instance.RegisterHandler<ICommandHandler<SomeCommand>>(args.Context.Resolve<ICommandHandler<SomeCommand>>());

            // does not look better to me than before ...
            args.Instance.RegisterHandlerByParam<SomeCommand>(args.Context.Resolve<ICommandHandler<SomeCommand>>())

            // uses delegate for, but still need to list all variants
            args.Instance.RegisterHandlerMethod<SomeCommand>(args.Context.Resolve<ICommandHandler<SomeCommand>>().Handle)
         });

If I want to use such a type in a lambda expression during registration I have the problem, that I need to identify the concrete type, like for this example on activation process for another component. But I don't want to list all of them manually... want something like this automatically.

How do I catch all ICommandHandler implementations AND have them automatically registered with the Register* method?

Edit:

Another variant is to extend the SomeCommandHandler class to register itself when resolved inside its constructor:

    public SomeCommandHandler(ICommandBus commandBus)
    {
        // and register here, for example
        commandBus.RegisterHandlerbyParam(this);
    }

This way I have to provide AutoActivate() to the AsClosedTypesOf registration result. (a possible solution, but now the "handlers" have two responsibilities... registration and handling)


Solution

  • This is an interesting and tricky problem. The generics definitely add to that complexity since going non-generic would be a simple IEnumerable<T> resolution.

    But... I think I can help.

    You'll take advantage of...

    • The OnRegistered event in RegisterAssemblyTypes so you can look at what was actually registered.
    • The OnActivating event for the bus so you can do the registration of the handlers.
    • Closures to carry the list of registered handler types into the OnActivating event.
    • Some fancy-schmancy reflection to create the closed generic version(s) of the RegisterHandler method on the bus.

    Here's a full, working example showing how to do it. Note I had to change the ICommandBus interface for RegisterHandler a bit since it wouldn't compile for me in the originally listed form, but you should be able to adapt as needed. I ran this in ScriptCs to verify.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using Autofac;
    
    public interface ICommand { }
    public class CommandOne : ICommand { }
    public class CommandTwo : ICommand { }
    
    public interface ICommandHandler<T> where T : ICommand
    {
      void Handle(T arg);
    }
    
    public class CommandOneHandler : ICommandHandler<CommandOne>
    {
      public void Handle(CommandOne arg) { }
    }
    
    public class CommandTwoHandler : ICommandHandler<CommandTwo>
    {
      public void Handle(CommandTwo arg) { }
    }
    
    public interface ICommandBus
    {
      IEnumerable<object> Handlers { get; }
      void RegisterHandler<TCommand, THandler>(THandler handler)
        where THandler : ICommandHandler<TCommand>
        where TCommand : ICommand;
    }
    
    public class CommandBus : ICommandBus
    {
      private readonly List<object> _handlers = new List<object>();
    
      public IEnumerable<object> Handlers
      {
        get
        {
          return this._handlers;
        }
      }
    
      public void RegisterHandler<TCommand, THandler>(THandler handler)
        where THandler : ICommandHandler<TCommand>
        where TCommand : ICommand
      {
        this._handlers.Add(handler);
      }
    }
    
    var builder = new ContainerBuilder();
    
    // Track the list of registered command types.
    var registeredHandlerTypes = new List<Type>();
    builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
      .AsClosedTypesOf(typeof(ICommandHandler<>))
      .OnRegistered(e => registeredHandlerTypes.Add(e.ComponentRegistration.Activator.LimitType));
    
    // Initialize the bus by registering handlers on activating.
    builder.RegisterType<CommandBus>()
      .As<ICommandBus>()
      .OnActivating(e => {
        foreach(var handlerType in registeredHandlerTypes)
        {
          // Due to the generic method, some crazy reflection happens.
          // First, get ICommandHandler<T> interface.
          var handlerInterfaceType = handlerType.GetInterface("ICommandHandler`1");
          // Grab the <T> from the ICommandHandler<T>.
          var commandType = handlerInterfaceType.GetGenericArguments()[0];
          // Build the closed generic version of RegisterHandler<TCommand, THandler>.
          var registerMethod = typeof(ICommandBus).GetMethod("RegisterHandler").MakeGenericMethod(commandType, handlerType);
          // Call the closed generic RegisterHandler<TCommand, THandler> to register the handler.
          registerMethod.Invoke(e.Instance, new object[] { e.Context.Resolve(handlerInterfaceType) });
        }
      })
      .SingleInstance();
    
    var container = builder.Build();
    using(var scope = container.BeginLifetimeScope())
    {
      var bus = scope.Resolve<ICommandBus>();
      foreach(var t in bus.Handlers)
      {
        // List the handler types registered.
        Console.WriteLine(t.GetType());
      }
    }