Плагины в WPF MvvM с MEF 2 - PullRequest
       79

Плагины в WPF MvvM с MEF 2

1 голос
/ 27 марта 2020

Я попробовал следующий очень хороший учебник https://www.eidias.com/blog/2013/7/26/plugins-in-wpf-mvvm-with-mef#cm -249 , чтобы перейти на MEF2, но по какой-то причине сборки не отображаются в каталоге. Из MEF2 я хотел использовать конфигурацию API (класс RegistrationBuilder) (здесь пример: https://stefanhenneken.wordpress.com/2013/01/21/mef-teil-11-neuerungen-unter-net-4-5/), возможно, у кого-то есть идея, как правильно применить MEF2 к учебнику. Большое спасибо.

здесь обзор решения: solution overview

В файле MainViewModel.cs я пока не знаю, как интегрировать импорт в RegistrationBuilder. Можете ли вы проверить остальную часть кода? Спасибо.

namespace WPF_MEF_App
{
    public class MainWindowModel : NotifyModelBase
    {
        public ICommand ImportPluginCommand { get; protected set; }
        private IView PluginViewVar;

       [Import(typeof(IView), AllowRecomposition = true, AllowDefault = true)]
        public IView PluginView
        {
            get { return PluginViewVar; }
            set{ PluginViewVar = value; NotifyChangedThis();}
        }

        [ImportMany(typeof(IView), AllowRecomposition = true)]
        public IEnumerable<Lazy<IView>> Plugins;

        private AggregateCatalog catalog;
        private CompositionContainer container;

        public MainWindowModel()
        {
            ImportPluginCommand = new DelegateCommand(ImportPluginExecute);
            RegistrationBuilder builder = new RegistrationBuilder();
            builder.ForType<PluginSecondScreen>()
                .Export<IView>(eb =>
                {
                    eb.AddMetadata("Name", "PluginSecond");
                })
                .SetCreationPolicy(CreationPolicy.Any);
            //.ImportProperties(pi => pi.Name == "IView",
            //        (pi, ib) => ib.AllowRecomposition());

            builder.ForType<CalculatorScreen>()
                .Export<IView>(eb =>
                {
                    eb.AddMetadata("Name", "CalculatorScreen");
                })
                .SetCreationPolicy(CreationPolicy.Any);
                //.ImportProperties(pi => pi.Name == "IView",
                //        (pi, ib) => ib.AllowRecomposition());

            catalog = new AggregateCatalog();

            string pluginsPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            catalog.Catalogs.Add(new DirectoryCatalog(pluginsPath, "Plugin*.dll"));
            catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly(), builder));

            //also we add to a search path a subdirectory plugins
            pluginsPath = Path.Combine(pluginsPath, "plugins");
            if (!Directory.Exists(pluginsPath))
                Directory.CreateDirectory(pluginsPath);
            catalog.Catalogs.Add(new DirectoryCatalog(pluginsPath, "Plugin*.dll"));            

            //Create the CompositionContainer with the parts in the catalog.
            container = new CompositionContainer(catalog);
        }

        private void ImportPluginExecute()
        {
            //refresh catalog for any changes in plugins
            //catalog.Refresh();

            //Fill the imports of this object
            //finds imports and fills in all preperties decorated
            //with Import attribute in this instance
            container.ComposeParts(this);
            //another option
            //container.SatisfyImportsOnce(this);
        }
    }
}

Вот два плагина: я уже прокомментировал экспорт здесь, потому что он больше не нужен для RegistrationBuilder. enter image description here enter image description here

1 Ответ

0 голосов
/ 28 марта 2020

Я проверил вашу попытку. Несколько моментов, которые нуждаются в улучшении.

  1. Обычно вы должны сконфигурировать контейнер в центральном расположении, обычно в точке входа приложения при запуске, например, внутри метода App.xaml.cs. Класс никогда не создает свой собственный контейнер для импорта своих зависимостей. Если это необходимо, рассмотрите возможность импорта ExportFactory<TImport> ( никогда обход вокруг контейнера).
  2. Вы должны импортировать зависимости либо через конструктор (рекомендуется), либо свойства и не поля . Поэтому вам необходимо добавить get и set к определениям PluginView и Plugins.
  3. . Вы должны использовать либо аннотационное разрешение зависимостей, либо API. Не смешивай это. Поэтому вы должны удалить атрибут Import из всех свойств MainWindowModel.
  4. Вы не можете иметь несколько реализаций интерфейса, например, IView и один импорт (количество элементов). Вам следует либо импортировать коллекцию конкретных типов, зарегистрировать только один конкретный тип или ввести специализированный интерфейс для каждого конкретного типа (например, PluginSecondScreen и ICalculatorScreen), где каждый интерфейс наследует общий интерфейс (например, IView).
  5. Не забудьте утилизировать CompositionContainer после завершения инициализации.
  6. SetCreationPolicy(CreationPolicy.Any) является избыточным, поскольку CreationPolicy.Any является значением по умолчанию, которое обычно по умолчанию равно CreationPolicy.Shared.
  7. Попробуйте использовать интерфейсы везде
  8. Избегайте литералов string при использовании класса или члена класса или имен типов. Используйте nameof вместо:
    ImportProperties(pi => pi.Name == "Plugins")
    должно быть:
    ImportProperties(pi => pi.Name == nameof(MainWindowModel.Plugins). Это значительно упрощает рефакторинг.

MainWindowModel.cs

class MainWindowModel
{
  // Import a unique matching type or import a collection of all matching types (see below).
  // Alternatively let the property return IView and initialize it form the constructor,
  // by selecting an instance from the `Plugins` property.
  public IPluginSecondScreen PluginView { get; set; }

  // Import many (implicit)
  public IEnumerable<Lazy<IView>> Plugins { get; set; }
}

Интерфейсы IView и специализации для создания уникальных типов:

interface IView
{
}

interface IPluginSecondScreen : IView
{
}

interface ICalculatorScreen : IView
{
}

Реализации интерфейса:

class PluginSecondScreen : UserControl, IPluginSecondScreen
{
}

class CalculatorScreen : UserControl, ICalculatorScreen
{
}

Инициализировать приложение из App.xaml.cs с помощью обработчика событий Application.Startup:

private void Run(object sender, StartupEventArgs e)
{
  RegistrationBuilder builder = new RegistrationBuilder();
  builder.ForTypesDerivedFrom<IView>()
    .ExportInterfaces();

  builder.ForType<MainWindowModel>()
    .Export()
    .ImportProperties(
      propertyInfo => propertyInfo.Name.Equals(nameof(MainWindowModel.Plugins), StringComparison.OrdinalIgnoreCase) 
        || propertyInfo.Name.Equals(nameof(MainWindowModel.PluginView), StringComparison.OrdinalIgnoreCase));

  var catalog = new AggregateCatalog();
  catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly(), builder));
  catalog.Catalogs.Add(new DirectoryCatalog(Environment.CurrentDirectory, "InternalShared.dll", builder));
  catalog.Catalogs.Add(new DirectoryCatalog(Environment.CurrentDirectory, "PluginCalculator.dll", builder));
  catalog.Catalogs.Add(new DirectoryCatalog(Environment.CurrentDirectory, "PluginSecond.dll", builder));

  using (var container = new CompositionContainer(catalog))
  {    
    MainWindowModel mainWindowModel = container.GetExportedValue<MainWindowModel>();

    this.MainWindow = new MainWindow() { DataContext = mainWindowModel };
    this.MainWindow.Show();
  }
}
...