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