Фильтр Autofac разрешен IEnumerable реализации - PullRequest
0 голосов
/ 09 мая 2018

Возможно ли отфильтровать зависимости при разрешении IEnumerable в автофакте?

У меня есть несколько реализаций интерфейса (IHandler в примере ниже), которые определены и зарегистрированы с использованием модуля Autofac в изолированных проектах. Я хотел бы иметь возможность фильтровать реализации при разрешении в родительском типе (Processor в приведенном ниже примере).

Реализации IHandler могут быть внедрены в Processor и отфильтрованы в ctor, но это потребует разрешения всех реализаций независимо от того, требуются ли они, что является расточительным.

public interface IHandler { }
public class Handler1 : IHandler { }
public class Handler2 : IHandler { }
public class Handler3 : IHandler { }

public class Processor {
    public IEnumerable<IHandler> Handlers;

    public Processor(IEnumerable<IHandler> handlers) {
        Handlers = handlers;
    }
}

static void Main(string[] args)
{
    var builder = new ContainerBuilder();

    builder.RegisterType<Handler1>().As<IHandler>();
    builder.RegisterType<Handler2>().As<IHandler>();
    builder.RegisterType<Handler3>().As<IHandler>();

    builder.RegisterType<Processor>().AsSelf();

    var container = builder.Build();

    var processor = container.Resolve<Processor>();
}

Поскольку за один раз можно разрешить только 1 ключ, моя попытка с ключами не сработала:

[Flags]
public enum HandlerType
{
    One = 1,
    Two = 2,
    Three = 4
}

builder.RegisterType<Handler1>().Keyed<IHandler>(HandlerType.One);
builder.RegisterType<Handler2>().Keyed<IHandler>(HandlerType.Two);
builder.RegisterType<Handler3>().Keyed<IHandler>(HandlerType.Three);

var enabledHandlers = HandlerType.One | HandlerType.Three;
builder.RegisterType<Processor>().AsSelf()
    .WithParameter(ResolvedParameter.ForKeyed<IEnumerable<IHandler>>(enabledHandlers));

Ответы [ 3 ]

0 голосов
/ 09 мая 2018

Я бы порекомендовал использовать неявные типы отношений Meta<T> и Lazy<T> , чтобы процессор мог читать конфигурацию и контролировать все это во время выполнения. Это также позволит вам по-разному фильтровать в различных условиях или полностью отключить фильтрацию без необходимости менять какие-либо клавиши.

Зарегистрируйте обработчики с метаданными, а не как службы с ключами ...

builder.RegisterType<Handler1>()
       .As<IHandler>()
       .WithMetadata("type", HandlerType.One);
builder.RegisterType<Handler2>()
       .As<IHandler>()
       .WithMetadata("type", HandlerType.Two);
builder.RegisterType<Handler3>()
       .As<IHandler>()
       .WithMetadata("type", HandlerType.Three);

Обновите ваш процессор, чтобы он принимал IEnumerable<Meta<Lazy<IHandler>>> и фильтруйте во время построения или в более поздний момент, когда понадобятся обработчики, ваш вызов.

public class Processor
{
  private readonly IHandler[] _handlers;
  public Processor(IEnumerable<Meta<Lazy<IHandler>>> handlers)
  {
    this._handlers =
      handlers
        .Where(h => h.Metadata["type"] == HandlerType.One || h.Metadata["type"] == HandlerType.Three)
        .Select(h => h.Value.Value)
        .ToArray();
  }
}

Каждый элемент, поступающий в конструктор, будет Meta<Lazy<IHandler>>:

  • Meta<T> имеет Metadata словарь для запроса, а Value будет Lazy<IHandler>.
  • Lazy<T> не разрешит / не создаст обработчик, пока вы не вызовете свойство Value, поэтому оно не будет дорогим или приведет к нежелательным разрешениям.

Так что item.Value.Value будет разрешено IHandler (как вы видите в LINQ выше).

Материал в фильтре Where может быть основан на конфигурации или чем-то еще. Но настоящая победа здесь в том, что если вам все еще нужно разрешить все обработчики где-то еще ...

public OtherHandlerConsumer(IEnumerable<IHandler> handlers)

... это все еще будет работать. Вы не можете сделать это, если они все включены. Кроме того, вы можете добавить столько метаданных, сколько захотите, к регистрациям или даже определить более надежные строго типизированные метаданные , чтобы вы могли принимать более взвешенные решения о том, какие обработчики вы хотите.

0 голосов
/ 09 мая 2018

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

var handlerIds = ConfigurationManager.AppSettings["Handlers"].Split(',');

if (handlerIds.Contains("1")) builder.RegisterType<Handler1>().As<IHandler>();
if (handlerIds.Contains("2")) builder.RegisterType<Handler2>().As<IHandler>();
if (handlerIds.Contains("3")) builder.RegisterType<Handler3>().As<IHandler>();
if (handlerIds.Contains("4")) builder.RegisterType<Handler4>().As<IHandler>();
// etc

builder.RegisterType<Processor>().AsSelf();
0 голосов
/ 09 мая 2018

Если вы можете обеспечить надлежащий фильтр на этапе настройки контейнера (как вы делаете в своем примере кода), вы можете изменить регистрацию типа Processor на следующее:

builder.RegisterType<Processor>().AsSelf()
    .WithParameter((p, c) => p.Name == "handlers",
        (p, c) => new[] 
            {
                c.ResolveKeyed<IHandler>(HandlerType.One),
                c.ResolveKeyed<IHandler>(HandlerType.Three)
            });

Значения, которые вам нужныИспользование может быть взято из любого места, из которого вы хотите, также из конфигурационного файла.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...