最近在写一篇关于如何扩展 Visual Studio 编辑器的文章时,用到了 MEF,因此打算写一篇文章提一下这个技术点。本篇文章并不打算详细介绍 MEF,只是一个最简单的入门,相信您在阅读本篇文章后,可以迅速开发出一个可扩展的应用程序。
MEF(Managed Extensibility Framework),是微软推出的一款用于搭建可扩展应用程序的框架,起初是独立于 .Net 发布的,后来集成到了 .Net 4.0 中。使用该框架可以非常轻松地扩展一个已发布的应用程序的功能,连 Visual Studio IDE 中的代码编辑器窗口也采用了MEF的思想,因此大大方便了开发人员对编辑器的扩展。
MEF 可用在任何使用 .NET Framework 的地方。可以在客户端应用程序中使用 MEF(无论应用程序使用的是 Windows 窗体、WPF,还是任何其他技术),也可以在使用 ASP.NET 的服务器应用程序中使用 MEF。
Import
导入,这里建议作为一个名词来理解,即一个接受者,它可以接受外来的东东。就好比是下图中的盒子,它可以接受其它积木。
十三孔智力盒
Export
导出,同样建议以一个名词来理解,即一个第三方的产物。它就像上图中不同颜色的积木,这些积木不属于这个盒子,但是能被放入盒子中,来丰富盒子的功能。
积木
Contract
协议。要想使盒子能接受积木(比如,圆柱体只能放入圆形的接口中),那这些积木必须符合一定的形状。而这些形状就相当于是应用程序和第三方扩展之间的一个协议。
Compose
组合(动词),即将多个符合协议要求的部件组合在一起,构成一个功能丰富的应用程序。就好比是将不同形状的积木,按照接口的形状组合在一起。
MEF 会动态查找用户所指定的目录,如果发现该目录中的程序集满足协议要求,就会启动自身的组合引擎,然后根据不同的协议约定把这些扩展导入到应用程序内部。
对几个关键的概念清楚了之后,我们就可以开始实践了。最终的效果是窗体上会动态加载某一目录下的dll,并自动为每个新功能添加一个按钮,当点击按钮就会执行新的功能。
最终效果
首先,定义一个协议。
这个和普通定义接口没什么两样。
1 public interface IPlugin 2 { 3 string Text { get; } 4 5 void Do(); 6 }
安装接受者
有了协议之后,就需要给应用程序安一个接受者。让这个应用程序可以通过接受者来获取第三方扩展。MEF 提供了[Import] 和 [ImportMany] 两种 attribute。 区别就是 Import 只能接受符合协议的一个扩展,而 ImportMany 可以接受多个,并把多个扩展放入集合中。
1 public partial class Form1 : Form 2 { 3 public Form1() 4 { 5 InitializeComponent(); 6 } 7 8 [ImportMany] 9 public IEnumerable<IPlugin> plugins; 10 11 private void Form1_Load(object sender, EventArgs e) 12 { 13 } 14 15 }
提供一个符合协议的产物
这个产物的生产过程其实就是实现接口的过程,唯一的区别是我们要为这个实现打上个标签,从而告诉我们的组合引擎这个东西是给接受者的。MEF 提供了 Export 来暗示这是一个可以提供给接受者的产物。
1 [Export(typeof(IPlugin))] 2 public class MyPlugin:IPlugin 3 { 4 public string Text 5 { 6 get 7 { 8 return "This is a demo"; 9 } 10 } 11 12 public void Do() 13 { 14 MessageBox.Show(Text); 15 } 16 }
发动引擎
万事俱备,就差发动了。前面说了引擎的主要作用就是把发现扩展,同时把这些扩展组合到应用程序中。
1 private CompositionContainer _container; 2 private void Init() 3 { 4 try 5 { 6 MyPlugin my = new MyPlugin(); 7 this._container.ComposeParts(this, my);//把扩展和实例组合在一起 8 } 9 catch (CompositionException compositionException) 10 { 11 Console.WriteLine(compositionException.ToString()); 12 } 13 }
上面的代码虽然实现了组合的功能,但是却硬把产物给编码进去了。要是每次开发了新的扩展,都得这样修改应用程序代码,那就完全没有使用MEF的必要了,而且也违反了开放封闭的设计原则。
把上面的代码改一改。
1 private CompositionContainer _container; 2 private void Init() 3 { 4 //设置目录,让引擎能自动去发现新的扩展 5 var catalog = new AggregateCatalog(); 6 catalog.Catalogs.Add(new DirectoryCatalog("D:\\plugin\\")); 7 8 //创建一个容器,相当于是生产车间 9 _container = new CompositionContainer(catalog); 10 11 //调用车间的ComposeParts把各个部件组合到一起 12 try 13 { 14 this._container.ComposeParts(this);//这里只需要传入当前应用程序实例就可以了,其它部分会自动发现并组装 15 } 16 catch (CompositionException compositionException) 17 { 18 Console.WriteLine(compositionException.ToString()); 19 } 20 }
上面的代码会自动去发现扩展,然后加入到应用程序中来。你要做的只是把新扩展的程序集放入 D:\\plugin 目录中就可以了。是不是很方便呢?
Managed Extensibility Framework (MEF)
本文源代码下载
本文来源于 《如何用 MEF 扩展应用程序》