之前在使用Prism框架时接触到了可扩展性框架MEF(Managed Extensibility Framework),体验到MEF带来的极大的便利性与可扩展性。
此篇将编写一个可组合的应用程序,帮助大家快速熟悉MEF并将其应用于实际项目中。
有关MEF中的名词含义及功能实现,请大家移步:火车票
介绍下将要编写的Demo程序(下图),使用winform开发。
- 通过组合操作,程序动态加载可用部件进行组合操作。
- 通过解体操作,程序卸载所加载的所有部件。
新建项目后需引用程序集:
System.ComponentModel.Composition
主程序的核心代码如下:
public partial class Form1 : Form, IPartImportsSatisfiedNotification { [ImportMany(AllowRecomposition = true)] private IEnumerable<Lazy<IPlugin, IPluginMetadata>> plugins; private AggregateCatalog catalog; private CompositionContainer container; public Form1() { InitializeComponent(); if (catalog == null) catalog = new AggregateCatalog(); this.container = new CompositionContainer(catalog); this.container.ComposeParts(this); } #region Implementation of IPartImportsSatisfiedNotification public void OnImportsSatisfied() { flowLayoutPanel1.Controls.Clear(); if (plugins != null && plugins.Count() != 0) { plugins.ToList().ForEach((a) => { Button btn = new Button(); btn.Cursor = System.Windows.Forms.Cursors.Hand; btn.Width = 100; btn.Height = 50; btn.Text = a.Metadata.ThePluginName; btn.Click += (d, b) => { a.Value.Run(); }; flowLayoutPanel1.Controls.Add(btn); }); } } #endregion public void CompositionAction() { catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly())); } ...... }
1. IPartImportsSatisfiedNotification接口 : 在组件导入完成后,调用该接口中的方法(OnImportsSatisfied)。
2. MEF中最常用目录有三种:程序集目录(AssemblyCatalog),文件目录(DirectoryCatalog),聚合目录(AggregateCatalog)
程序集目录(AssemblyCatalog): 顾名思义可以向目录中添加程序集已存在类型中寻找可用于导入的部件。
var catalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly());
文件夹目录(DirectoryCatalog):从文件夹中寻找可用于导入的部件
var catalog = new DirectoryCatalog("Extensions");
聚合目录(AggregateCatalog):可以向聚合目录包含上述两种方式
var catalog =new AggregateCatalog( new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly()), new DirectoryCatalog("Extensions"));
3. CompositionContainer : 组合容器,管理部件的组合并提供了一系列用于创建部件实例扩展方法等,详细资料
4. 目录与容器的一般使用方法:
var catalog =new AggregateCatalog(); var container =new CompositionContainer(catalog); var container.ComposeParts(this);
5. 导入:ImportAttribute 与 ImportManyAttribute
用于以下三种用途:字段,属性,方法
[import] private IData _data; [import] public IData Data{set;get;} [import] public Action ClickAction;
ImportManyAttribute : 通过组合容器将所有符合契约的导出进行填充 (真别扭,说白了就是导入多个)
[ImportMany] private IEnumerable<iplugin> plugins;</iplugin>
AllowRecomposition : 是否允许重组
AllowRecomposition = true : 比如在程序运行的过程中,动态向聚合目录中添加可导出的部件,可以引发重组操作
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
需要注意的是:DirectoryCatalog 不会引发重组操作,可通过Refresh方法指定重组的策略。
6. System.Lazy<T> : MEF提供延迟导入。
下面来看一下,插件式如何实现的:
[ExportPluginAttribute(typeof(IPlugin), ThePluginName = "TheFristPlugin")] public class TheFristPlugin : IPlugin { public TheFristPlugin() { this.TheName = "TheFristPlugin"; } #region Implementation of IPlugin public string TheName { get; set; } public void Run() { MessageBox.Show("TheFristPlugin"); } #endregion }
1. 简单说一下:导入与导出之前的关系
一个基于MEF开发的可扩展的程序,在容器中必然有很多的导出(Export),而这些Export又是怎么样找到自己的归宿呢。
Export 与 Import 依靠一种契约,来确定对方是否是自己的兄弟,说白了就是接口,比如上述程序所定义的IPlugin接口
public interface IPlugin { void Run(); }
使用 ExportAttribute 特性导出:
[Export("count")] public int count{ get{return 0;} } [Export(typeof(Action))] public void SendMsg(){return;} [Export] public class Person{}
有一个需求,主程序要求插件必须要指定插件名称:
1. 在IPlugin接口中定义:Name字段
2. 使用元数据
3. 使用自定义导出特性(与第二种方案类似)
如何使用元数据?
1.定义元数据视图,此处视图使用接口类型
public interface IPluginMetadata { string ThePluginName { get; } }
2. 导出部件时,使用ExportMetaData特性
[ExportMetadata("ThePluginName", "TheFivePlugin")] [Export(typeof(mef.test.wform.Interface.IPlugin))] public class TheFivePlugin : mef.test.wform.Interface.IPlugin { public void Run() { MessageBox.Show("TheFivePlugin"); } }
3. 导入元数据
[ImportMany(AllowRecomposition = true)] private IEnumerable<Lazy<IPlugin, IPluginMetadata>> plugins;
4. 访问元数据
Lazy<T,TMetadata>.Value.Metadata
结束
到此为止,MEF 基本内容已讲解结束,如果有遗漏也请博友留言指出。
文章中很多都是白话,非官方语言,怎么理解的就怎么写,如果有不妥之处,还望各位博友指出。
新年快乐