从 Autofac Builder 获取所有 AsClosedTypesOf 注册变体

Get all AsClosedTypesOf registration variants from the Autofac Builder

让我们假设这些 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 ...
}

我想自动注册 ICommandHandler<> 的所有实现。 所有变体 (Register*) 都是有效的解决方案,尽管我更喜欢 Action 参数,因为它更灵活并且不依赖于 Handler 接口(只是动作委托)。

Autofac 能够根据程序集扫描注册类型,并注册找到的通用接口实现,例如:

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

所以我已经注册了所有的实现。 现在我需要将它们全部自动注册到 TheCommandBus 怎么做?

我可以通过添加这些行(例如在 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)
         });

如果我想在注册期间在 lambda 表达式中使用这样的类型,我会遇到问题,我需要确定具体类型,例如另一个组件的激活过程示例。但我不想手动列出所有这些......想要自动这样的东西。

如何捕获所有 ICommandHandler 实现并让它们自动注册到 Register* 方法?

编辑:

另一种变体是扩展 SomeCommandHandler class 以在其构造函数中解析时注册自身:

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

这样我必须向 AsClosedTypesOf 注册结果提供 AutoActivate()。 (一个可能的解决方案,但现在 "handlers" 有两个职责...注册和处理)

这是一个有趣且棘手的问题。泛型肯定会增加这种复杂性,因为非泛型将是一个简单的 IEnumerable<T> 解决方案。

但是... 我想我可以帮上忙。

您将利用...

  • RegisterAssemblyTypes 中的 OnRegistered 事件,因此您可以查看实际注册的内容。
  • 总线的 OnActivating event,因此您可以注册处理程序。
  • 将已注册处理程序类型列表传送到 OnActivating 事件中的闭包。
  • 在总线上创建 RegisterHandler 方法的封闭通用版本的一些花哨的反射。

这是一个完整的工作示例,展示了如何做到这一点。请注意,我必须稍微更改 RegisterHandlerICommandBus 接口,因为它不会' 以最初列出的形式为我编译,但您应该能够根据需要进行调整。我运行这个在ScriptCs验证一下。

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());
  }
}