WPF MvvM 和 MEF 2 中的插件

Plugins in WPF MvvM with MEF 2

我尝试了以下非常好的教程 https://www.eidias.com/blog/2013/7/26/plugins-in-wpf-mvvm-with-mef#cm-249 to migrate to MEF2 but for some reason the assemblies are not shown in the catalog. From MEF2 I wanted to use the API Configuration (RegistrationBuilder class) (here an example: https://stefanhenneken.wordpress.com/2013/01/21/mef-teil-11-neuerungen-unter-net-4-5/),也许有人知道如何将 MEF2 正确应用到教程中。非常感谢。

这里是解决方案的概述:

在 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 不再需要它们。

我检查了你的尝试。需要改进的几点。

  1. 一般你应该在中心位置配置容器, 通常在启动时的应用程序入口点,例如里面一个 App.xaml.cs 的方法。 class 从不创建自己的容器来导入其依赖项。如果这是必要的,请考虑导入一个 ExportFactory<TImport>从不 绕过容器)。
  2. 您必须通过构造函数(推荐)或 properties 而不是字段 导入依赖项。因此,您需要将 getset 添加到 PluginViewPlugins 的定义中。
  3. 您应该使用基于注释的依赖项解析或基于 API 的依赖项解析。不要混合它。因此,您必须从 MainWindowModel 中的所有属性中删除 Import 属性。
  4. 一个接口不能有多个实现,例如IView 和一次导入(基数)。您应该要么导入具体类型的集合,只注册一个具体类型,要么为每个具体类型(例如 PluginSecondScreenICalculatorScreen)引入一个专用接口,其中每个接口继承共享接口(例如 IView).
  5. 不要忘记在完成初始化后处理 CompositionContainer
  6. SetCreationPolicy(CreationPolicy.Any) 是多余的,因为 CreationPolicy.Any 是默认值,通常默认为 CreationPolicy.Shared
  7. 尽量在任何地方使用接口
  8. 在使用 class 或 class 成员或类型名称时避免使用 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
{
}

使用 Application.Startup 事件处理程序从 App.xaml.cs 初始化应用程序:

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