Получите все варианты регистрации AsClosedTypesOf из Autofac Builder.

Предположим, что эти классы/интерфейсы:

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

Если я хочу использовать такой тип в лямбда-выражении во время регистрации, у меня есть проблема, что мне нужно определить конкретный тип, как в этом примере в процессе активации для другого компонента. Но я не хочу перечислять их все вручную... хочу что-то подобное автоматически.

Как перехватить все реализации ICommandHandler и автоматически зарегистрировать их с помощью метода Register*?

Изменить:

Другой вариант — расширить класс SomeCommandHandler, чтобы он регистрировал себя при разрешении внутри его конструктора:

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

Таким образом, я должен предоставить AutoActivate() для результата регистрации AsClosedTypesOf. (возможное решение, но теперь у «обработчиков» две обязанности... регистрация и обработка)


person Beachwalker    schedule 15.06.2016    source источник


Ответы (1)


Это интересная и сложная проблема. Дженерики определенно усложняют эту задачу, поскольку переход на неуниверсальные методы был бы простым IEnumerable<T> разрешением.

Но... Думаю, я могу помочь.

Вы воспользуетесь...

  • Событие OnRegistered в RegisterAssemblyTypes, чтобы вы могли посмотреть, что на самом деле было зарегистрировано.
  • OnActivating событие для шины, чтобы вы могли выполнить регистрацию обработчиков .
  • Замыкания для переноса списка зарегистрированных типов обработчиков в событие OnActivating.
  • Некоторое причудливое отражение для создания закрытой универсальной версии метода RegisterHandler на шине.

Вот полный рабочий пример, показывающий, как это сделать. Обратите внимание, что мне пришлось немного изменить интерфейс ICommandBus для RegisterHandler, так как он не компилировался для меня в исходном виде, указанном в списке, но вы должны иметь возможность адаптировать по мере необходимости. Я запустил это в 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());
  }
}
person Travis Illig    schedule 17.06.2016
comment
Хорошее решение. Спасибо. Я бы дал второй голос за причудливое отражение :-D - person Beachwalker; 17.06.2016