Разрешение зависимости области от декларирующей сборки - PullRequest
0 голосов
/ 15 марта 2019

Утро всем.

короткая версия; все конкретные реализации из всех сборок, которые реализуют мой обычно определенный интерфейс IMenuItem, внедряются во все конструкторы, которым требуется IEnumerable<IMenuItem>.

Я создаю небольшое ядро ​​приложения Windows TrayIcon, которое позволяет плагины. Ядро обнаруживает все плагины в каталоге bin и создает мой контейнер Autofac.

У меня есть Core.Interfaces проект, который объявляет IMenuItem интерфейс

Каждый плагин определяется в своей сборке, и в этом плагине может быть много функций; каждая функция объявляет свои пункты меню.

Во время выполнения каждый плагин обнаруживает все функции и запросы для своих пунктов меню. У меня проблема в том, что плагин A получает пункты меню от плагина B, потому что все пункты меню реализуют интерфейс IMenuItem.

Чего я хочу добиться, так это иметь общий интерфейс IMenuItem, но когда конструктор в плагине A запрашивает IEnumerable<IMenuItem>, ему следует передавать только те бетоны, которые мы обнаружили в его собственной сборке.

Просто чтобы сказать, что если я объявляю интерфейс IMenuItem в каждой сборке, то все это прекрасно работает, предположительно, потому что регистрации затем помещаются в пространство имен интерфейса.

Я борюсь за правильную терминологию, чтобы гуглить это, но я думаю, что понимаю, что это проблема регистрации; возможно, тот, который я могу решить только во время разрешения?

1 Ответ

1 голос
/ 15 марта 2019

Из коробки нет ничего, что делает это.Вам придется написать собственный код.

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

Отказ от ответственности: я незапустить все это через компилятор.Они будут частичными примерами, и я могу что-то напечатать.Некоторые могут быть псевдокодом.YMMV.

Вариант 1: зарегистрировать каждый плагин в отдельной жизненной области

Так работает поддержка мультитенантов .По сути, корневой контейнер содержит только общие компоненты, и каждый плагин (и соответствующие пункты меню) будут зарегистрированы в дочерней области.

var builder = new ContainerBuilder();

// register common stuff that all plugins use
builder.Register<SomethingCommon>().As<ICommonService>();
var container = builder.Build();

// iterate over the assemblies and create scopes per plugin
var pluginScopes = new List<ILifetimeScope>();
foreach(var assembly in GetThePluginAssemblies())
{
  var scope = container.BeginLifetimeScope(b =>
  {
    b.RegisterAssemblyTypes(assembly)
     .Where(t => t.GetInterfaces().Any(i => i == typeof(IPlugin))
     .AsImplementedInterfaces();
    b.RegisterAssemblyTypes(assembly)
     .Where(t => t.GetInterfaces().Any(i => i == typeof(IMenuItem))
     .AsImplementedInterfaces();
  });
  pluginScopes.Add(scope);
}

На данный момент у вас есть список отдельных областей, которые выможно использовать для разрешения каждого плагина, например, если вам нужны все плагины:

var plugins = pluginScopes.SelectMany(s => s.Resolve<IEnumerable<IPlugin>>());

думаю , вот как работает SelectMany, я всегда запутываюсь. Дело в том, что вы получитесплошной список всех плагинов во всех областях.)

Чтобы упростить вашу жизнь, технически вы могли бы использовать пакет Autofac.Multitenant и «притвориться», что каждый плагин является отдельным арендатором.Он уже имеет все отслеживание и настройку области действия для каждого арендатора.

var builder = new ContainerBuilder();

// register common stuff that all plugins use
builder.Register<SomethingCommon>().As<ICommonService>();
var container = builder.Build();

// You probably won't want to resolve things "as a tenant" from the container,
// so you'd only use the Multitenant.ApplicationContainer (for global/common stuff)
// or individual tenant scopes directly. The tenant ID strategy, ostensibly, won't
// be used, so just make a dummy one that always returns false or something.
var multiPluginContainer = new MultitenantContainer(container, SomeTenantIdentificationStrategy);

// iterate over the assemblies and create tenants per plugin
// where the tenant ID is something like the assembly name
foreach(var assembly in GetThePluginAssemblies())
{
  multiPluginContainer.ConfigureTenant(assembly.FullName, b =>
  {
    b.RegisterAssemblyTypes(assembly)
     .Where(t => t.GetInterfaces().Any(i => i == typeof(IPlugin))
     .AsImplementedInterfaces();
    b.RegisterAssemblyTypes(assembly)
     .Where(t => t.GetInterfaces().Any(i => i == typeof(IMenuItem))
     .AsImplementedInterfaces();
  });
}

Затем вы можете получить список плагинов ("арендаторов") и решить.

var plugins = multiPluginContainer
  .GetTenants()
  .SelectMany(k =>
    multiPluginContainer.GetTenantScope(k).Resolve<IEnumerable<IPlugin>>());

Вариант 2. Использование метаданных для маркировки элементов

Autofac поддерживает параметры при регистрации , а ResolvedParameter довольно мощный.Некоторая умная работа с этим может иметь большое значение.

Во-первых, вы можете зарегистрировать все пункты меню и пометить их метаданными.

var builder = new ContainerBuilder();
// register a bunch of stuff and...
foreach(var assembly in GetThePluginAssemblies())
{
  builder.RegisterAssemblyTypes(assembly)
     .Where(t => t.GetInterfaces().Any(i => i == typeof(IPlugin))
     .AsImplementedInterfaces();
  builder.RegisterAssemblyTypes(assembly)
     .Where(t => t.GetInterfaces().Any(i => i == typeof(IMenuItem))
     .WithMetadata("assembly", assembly.FullName)
     .AsImplementedInterfaces();
}

ОК, теперь у вас есть все IMenuItem записи, помеченные именем сборки.Создайте модуль, который автоматически присоединяет разрешенный параметр к каждому IPlugin, так что любой IEnumerable<IMenuItem> будет выполняться вашим параметром.Это в значительной степени основано на примере модуля log4net из документов .

public class MenuItemModule : Autofac.Module
{
  private static void OnComponentPreparing(object sender, PreparingEventArgs e)
  {
    e.Parameters = e.Parameters.Union(
      new[]
      {
        new ResolvedParameter(
            // Only provide values for IEnumerable<IMenuItem> requested
            // by IPlugin implementations
            (pi, ctx) =>
               pi.ParameterType == typeof(IEnumerable<IMenuItem>) &&
               pi.Member.DeclaringType.GetInterfaces().Any(i => i == typeof(IPlugin)),
            // Resolve the appropriately tagged menu items
            // IEnumerable<T> - get all the menu items
            // Meta<T> - you want to look at the metadata
            // Lazy<T> - don't actually construct them until you want them
            // meta.Value = Lazy<T>
            // meta.Value.Value resolves the IMenuItem
            (pi, ctx) => {
              var asmName = pi.Member.DeclaringType.Assembly.FullName;
              return ctx.Resolve<IEnumerable<Meta<Lazy<IMenuItem>>>>()
                 .Where(meta => meta.Metadata["assembly"] == asmName)
                 .Select(meta => meta.Value.Value);
            }
        ),
      });
  }

  protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
  {
    registration.Preparing += OnComponentPreparing;
  }
}

Затем вы зарегистрируете этот модуль, чтобы все разрешения получили этот параметр.

builder.RegisterModule<MenuItemModule>();

Существуют и другие варианты.

Вы можете представить другие варианты на нем.Отдельный контейнер на плагин (что неплохая идея - хорошая изоляция плагинов).Отдельный AppDomain для каждого плагина (еще лучшая изоляция, но работа для сбора данных).Базовая реализация IPlugin, в которой вместо логики ResolvedParameter используется логика фильтрации.Атрибуты фильтра метаданных в реализациях плагина для выполнения фильтрации.

Надеемся, это поможет вам разблокировать.

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