若干年前,老周写了几篇有关MEF的烂文,简单地说,MEF是一种动态扩展技术,好比能够指定以某个程序集或某个目录为搜索范围,应用程序在运行时会自动搜索符合条件的类型,并自动完成导入,这样作的好处是,主程序的代码不用改来改去,只须要把扩展的程序集放到对应的目录下就能够了。windows
MEF不只能够用于“看不见”的类型扩展上,对于“看得见”的类型照样适用,好比窗口、控件之属,你要是够牛逼的话,甚至能够把它用到ASP.NET上,不过这个玩意儿估计要配合重写路由规则才能实现,根据URL传的参数来跳转到具体的页面。布局
较为简单的,像Windows Forms中的窗口,WPF中的窗口或控件,就能够直接运用MEF来完成扩展,主应用程序界面能够动态生成菜单项或按钮来打开窗口就能够了。而各个窗口的实现代码能够写在一个类库项目中。测试
下面,我们用一个实实在在的例子来讲明一下。优化
新建一个类库项目,而后在里面作三个WPF窗口,XAML文档如何与代码类关联,这个不要问我,问MSDN姐姐去。ui
由于这是作测试,窗口的UI布局你能够随便设计。spa
给你们一个提示吧,XAML文件和窗口类的代码文件的关联方法,和ASP.NET中.aspx文件与代码文件的关联方法同样。例如XAML文件名叫 test.xaml,那么对应的代码文件名就是test.xaml.cs(VB语言的话,是test.xaml.vb)。设计
对窗口来讲,通常是从Window类派生,因此,XAML文档的根元素要写Window,好比3d
<Window> …… </Window>
XAML中有两个必备的命名空间要引入:code
<Window x:Class="wpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" …………
.../xaml/presentation 表示的WPF中的UI类型,好比Button、Canvas等;而那个带x前缀的.../xaml表示的是XAML语法自己特有的东西,好比x:Class,这个特性就是联合XAML文件和代码文件的关键,用它来指定窗口类的名字,类名要包括命名空间名。orm
下面的步骤至关重要,否则就没法MEF了。
打开窗口的代码文件,在窗口类声明上添加导出声明。以下
[Export(typeof(Window))] [ExportMetadata("name", "窗口 A")] public partial class DemoWindow1 : Window { public DemoWindow1() { InitializeComponent(); Title = $"演示窗体 -- {nameof(DemoWindow1)}"; } }
声明导出须要一个协定,由于类型是能够动态扩展的,因此这些扩展的类型必需要向运行时代表它们有一个共同点,以便让MEF可以找到它,这就是类型协定。咱们知道,全部窗口类都有一个共同点——从Window类派生,故而在声明ExportAttribute时,用Window类的Type来标注协定。
ExportMetadataAttribute表示的是元数据,它是可选的,指定方式和字典的key - value形式差很少,name是字符串,value是Object类型,虽然能够指任何类型的value,但最好是可序列化的类型或者基础类型(byte,string,int等),这样方便传递。在接收扩展的代码中,能够用IDictionary<string, object>类型来接收元数据,也能够自定义一个类型(接口、类)来接收,只要属性/字段的名字和ExportMetadataAttribute中的name相等就好了,这样元数据就会自动填充到类型的属性/字段成员中。
好比,若是你指定元数据的name为“Age”,value为25,那么你自定义的类型只要公开一个名为Age的属性或字段便可,获取时会自动填充数据。
这里我一口气作了三个窗口,最后,能够定义一个类,把上面的N个窗口批量导入这个类的一个属性中,随后导出这个类的这个属性。
class WindowsCompos { [Export("extWindows")] [ImportMany(typeof(Window))] public IEnumerable<ExportFactory<Window, IDictionary<string,object>>> ExtWindows { get; set; } }
ImportMany能够一次性导入多个类型,由于扩展的窗口有N个,因此要使用这个特性来批量导入,还记得吧,前面的窗口都是以Window的Type做为协定来导出的,因此在导入时,必定指定匹配的协定,否则没法导入。
由于类型有多个,因此要用IEnumerable<T>(协变)来存放,而其中的T为ExportFactory<T, TMetadata>,原本用ExportFactory<T>就能够了,但因为我为每一个窗口的导出定义了元数据,因此要使用支持获取元数据的工厂类型。
这个类能够不定义为public,由于导出的是它的属性,并且对于MEF来讲,非public的成员均可以导出,只要你指定导出协定便可。
对于ExtWindows属性,导出声明就没必要使用Type做为协定了,直接指定一个名字来作协定就能够了,本例是extWindows,注意这个协定名是区分大小写的,ext和Ext被视为不一样的协定。
一般,接收扩展类型用的是Lazy<T>,以达到延迟实例化,可是,这个项目比较特殊,不能用Lazy来承载类型。WPF的窗口类有个特色,就是每次显示窗口必须使用新的实例,由于窗口一旦Close以后,就不能再次Show了,只能从新new一个实例才能Show。基于这缘由,用ExportFactory类最好,这个类每次访问都能从新建立实例,调用CreateExport方法能建立一个ExportLifetimeContext<T>实例,再经过这个ExportLifetimeContext<T>实例的Value属性来获得窗口实例。
ExportLifetimeContext<T>实现了IDisposable接口,能够写在using语句中,用完后释放掉。
如今回到主应用程序项目,开始导入扩展窗口。
主窗口用一个菜单就好了,每一个导入的窗口类型将做为菜单项。
<Grid> <Menu VerticalAlignment="Top"> <MenuItem Header="窗口" Name="menuWindows"> <!-- ****** --> </MenuItem> </Menu> </Grid>
下面代码将获取导出对象,因为刚才用IEnumable<T>来导入了窗口类型,因此此处只须要获取这个属性的值便可。
IEnumerable<ExportFactory<Window, IDictionary<string, object>>> ext_windowslist; CompositionContainer container = null; public MainWindow() { InitializeComponent(); Assembly extAss = Assembly.Load(nameof(ExtWindowLib)); AssemblyCatalog catelog = new AssemblyCatalog(extAss); container = new CompositionContainer(catelog); CompositionExtWindows(); AddExtToMenuitems(); menuWindows.AddHandler(MenuItem.ClickEvent, new RoutedEventHandler(OnMenuItemClicked)); }
CompositionContainer是个容器,用它能够组合全部获取到的扩展类型,实例化容器时,要指定一个搜索范围,这里我指定它从刚才那个类库项目中搜索。由于我已经引用了这个类库项目,因此调用Assembly.Load(程序集名)就能够直接加载了。
CompositionExtWindows方法负责从容器中获取导出的IEnumrable<T>对象,代码以下:
private void CompositionExtWindows() { if (container == null) return; ext_windowslist = container.GetExportedValue<IEnumerable<ExportFactory<Window, IDictionary<string, object>>>>("extWindows"); }
直接调用GetExportedValue方法就能够获取到导出的属性值,参数是刚刚给ExtWindows属性指定的协定名。
AddExtToMenuitems方法把获取到的扩展窗口类型添加到子菜单项,这样一来,有多少个扩展窗口,就有多少个菜单项。
private void AddExtToMenuitems() { foreach (var factory in ext_windowslist) { // 元数据 IDictionary<string, object> metadata = factory.Metadata; string hd = metadata["name"] as string; MenuItem mnitem = new MenuItem(); mnitem.Header = hd; mnitem.Tag = factory; menuWindows.Items.Add(mnitem); } }
让菜单项的Tag属性引用 ExportFactory实例,以便在Click事件处理方法中访问。
菜单项的Click事件处理以下:
private void OnMenuItemClicked(object sender, RoutedEventArgs e) { MenuItem item = e.Source as MenuItem; ExportFactory<Window> fact = item.Tag as ExportFactory<Window>; if (fact != null) { using (var lifeobj = fact.CreateExport()) { Window w = lifeobj.Value; w.Show(); } } }
从Value属性中获取窗口实例,就能够调用Show方法来显示窗口了。
来,运行一下,看看如何。运行后,会自动添加三个菜单项,由于我刚刚作了三个窗口。
点击对应的菜单,就能打开对应窗口。
如今,不妨往类库项目中再添加一个窗口。
[Export(typeof(Window))] [ExportMetadata("name", "窗口 D")] public partial class DemoWindow4 : Window { public DemoWindow4() { InitializeComponent(); Title = $"演示窗体 -- {nameof(DemoWindow4)}"; } }
主应用程序的代码不用作任何改动,而后直接运行。
此时,你会看到,第4个窗口也自动加进来了。
有没有发现,这几个菜单项的排序好像不太好看,要是能按必定顺序排列多好。这个实现起来不难,老周就不实现了,你本身试着干吧。
老周能够给个提示,还记得在ExportAttribute声明导出类型时,能够指定元数据,例子中,老周指定了一个叫name的元数据,你能够指定一个叫order的元数据,值为数值,好比第一个窗口为1,第二个窗口为2……
而后,在主程序项目中获取组合扩展时,能够用IEnumerable<T>的扩展方法进行排序,也能够用LinQ语法来排序。
好了,文章就写到这里吧,See you.
===================================================================
有热心朋友给老周留言,问老周,为何你的博文的右下角,老有人点“反对”,老周你是否是得罪人了。
谢谢朋友,你不说我还真没注意,由于老周历来不在乎那些虚的东西,故一直没注意到这个。实话说,老周历来不得罪人,老周只会得罪妖魔鬼怪,因此朋友多虑了。
至于说右下角那两个按钮,多是一些没文化的人,原本是想点击左边的,因为不认识汉字,错点了右边的按钮。
总之,你们不要在乎这些可有可无的东西,若是你以为老周写的烂文对你有用,那你就姑且当娱乐新闻看看吧,毕竟老周的写做水平不高,老周已经在努力优化了,争取多读点经典名著和大师著做,提高水平。