为什么要使用MEF
在商业应用软件开发过程中,对于各个软件项目,都需要建立相应的系统框架,为了更好的规范系统的开发,提高生产效率,应该在公司级别制定相应的API标准。这些API标准将站在系统架构层次,以同样一个核心框架构建出不同的商业应用。
对于各个商业应用中存在花样繁多的需求,同时又存在一些公用的模块,为了将这些可变的和相对稳定的功能模块有机的整合在一个系统框架下,那么就需要实现系统框架的可自定义插件开发。目前在MEF之前,业界也在大量的使用如 Castle Windsor、Structure Map、Spring.Net 以及Unity等依赖注入方式实现插件开发。而这些体系在.net平台中应用案例较少,在目前公司来说,基本还是空白,因此选择MEF这样一个全新的技术方案,相对其他方案门槛较低。
MEF概念的理解
可组合的部件(或简称“Part”):一个部件可以向其他部件提供服务,也可以使用其他部件提供的服务,它可以存在任何位置,可以是Web服务,外部系统,本系统。
导出:导出是服务提供者
导入:导入是服务使用者
约定:服务提供者与使用者之间使用的标示符,类似于身份识别。
组合:对部件实例化,建立组合关系,是的导出部件和导入部件相匹配。
MEF的工作原理
MEF的核心包括一个catalog(目录)和一个CompositionContainer(组合容器)。category用于发现扩展,而container用于协调创建和梳理依赖性。每个可组合的Part提供了一个或多个Export(导出),并且通常依赖于一个或多个外部提供的服务或 Import(导入)。
MEF的Demo
demo1:宿主mef ,学习compose的过程以及部件基本的特性标记
1.定义服务接口
interface IGetString { void WriteString(); }
2.定义服务的实现
[Export(typeof(IGetString))] class GetString :IGetString { public void WriteString() { Console.WriteLine("Hello Mef demo1!"); } }
3.配置宿主程序
class Program { /// <summary> /// 导入部件 /// </summary> [Import(typeof(IGetString))] public IGetString Service { get; set; } //组合部件 void Compose() { var catelog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catelog); container.ComposeParts(this); } static void Main() { Program p = new Program(); p.Compose(); p.Service.WriteString(); Console.ReadLine(); } }
4.运行效果
demo2:多个部件的组合,学习ImportMany
当同一个接口有多个实现的时候,MEF提供了ImportMany的方式,将实现多个
[Export(typeof(IGetString))] class GetString1 :IGetString { public void WriteString() { Console.WriteLine("Hello string1!"); } } [Export(typeof(IGetString))] class GetString2 : IGetString { public void WriteString() { Console.WriteLine("Hello string2!"); } }
宿主程序代码:
class Program { /// <summary> /// 导入 /// </summary> [ImportMany(typeof(IGetString))] public IEnumerable<IGetString> Service { get; set; } /// <summary> /// 组合 /// </summary> void Compose() { var catelog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catelog); container.ComposeParts(this); } static void Main() { Program p = new Program(); p.Compose(); foreach (var server in p.Service) server.WriteString(); Console.ReadLine(); } }
运行效果:
demo3:多个部件和契约的配合
对export添加字符串标示信息
[Export("txt",typeof(IGetString))] class GetString :IGetString { public void WriteString() { Console.WriteLine("Hello string1!"); } } [Export("db",typeof(IGetString))] class GetString2 : IGetString { public void WriteString() { Console.WriteLine("Hello string2!"); } }
宿主程序导入部分同样增加字符串信息,与导出部件保持一致
class Program { /// <summary> /// 导入 /// </summary> [Import("db",typeof(IGetString))] public IGetString Service { get; set; } /// <summary> /// 组合 /// </summary> void Compose() { var catelog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catelog); container.ComposeParts(this); } static void Main() { Program p = new Program(); p.Compose(); p.Service.WriteString(); Console.ReadLine(); } }
运行效果:
demo4:Import和Export的应用场景
在MEF中,导入和导出可以应用在类,字段,属性,方法,并允许多个部件同时实现一个接口,和继承的特性。
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes")] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = true, Inherited = false)] public class ExportAttribute : Attribute { //...... }
导出属性、字段或方法
class GetString { /// <summary> /// 导出属性 /// </summary> [Export("txt")] public string GetString1 { get { return "this is a fileds!"; } } [Export(typeof(Action<string>))] public void GetString2(string name) { Console.WriteLine(name); } }
导入属性、字段或方法
///// <summary> ///// 导入 ///// </summary> [Import("txt")] public string WriteString1 { get; set; } [Import(typeof(Action<string>))] public Action<string> WriteString2 { get; set; }
宿主程序
/// <summary> /// 组合 /// </summary> void Compose() { var catelog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catelog); container.ComposeParts(this); } static void Main() { Program p = new Program(); p.Compose(); Console.WriteLine(p.WriteString1); p.WriteString2("this is a parameter"); Console.ReadLine(); }
输出结果:
demo5:组合部件的嵌套
在导出部件中进行了导入操作
/// <summary> /// 服务接口 /// </summary> interface IGetString { void WriteString(); } /// <summary> ///导出部件 /// </summary> [Export("txt",typeof(IGetString))] class GetString1 :IGetString { public void WriteString() { Console.WriteLine("Hello string1!"); } } /// <summary> /// 导出部件 /// </summary> [Export("db",typeof(IGetString))] class GetString2 : IGetString { public void WriteString() { Console.WriteLine("Hello string2!"); } } /// <summary> /// 导出部件导入了其他部件 /// </summary> [Export] class Getstring { [Import("txt",typeof(IGetString))] public IGetString Txt { get; set; } [Import("db", typeof(IGetString))] public IGetString Db { get; set; } }
宿主程序:
/// <summary> /// 导入 /// </summary> [Import] public Getstring Service { get; set; } /// <summary> /// 组合 /// </summary> void Compose() { var catelog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catelog); container.ComposeParts(this); } static void Main() { Program p = new Program(); p.Compose(); //根据最近一层的服务提供输出 p.Service.Txt.WriteString(); p.Service.Db.WriteString(); Console.ReadLine(); }
测试结果:
demo6:组合部件的延迟加载
部件准备工作
/// <summary> /// 服务接口 /// </summary> interface IGetString { void WriteString(); } /// <summary> ///导出部件 /// </summary> [Export("txt",typeof(IGetString))] class GetString1 :IGetString { private string initTime; public GetString1() { initTime = DateTime.Now.ToString("hh:mm:ss:"); } public void WriteString() { Console.WriteLine("部件1初始化时间:\"{0}\"",initTime); } } /// <summary> /// 导出部件 /// </summary> [Export("db",typeof(IGetString))] class GetString2 : IGetString { private string initTime; public GetString2() { initTime = DateTime.Now.ToString("hh:mm:ss:"); } public void WriteString() { Console.WriteLine("部件2初始化时间:\"{0}\"", initTime); } }
导入
/// <summary> /// 导入 /// </summary> [Import("txt",typeof(IGetString))] public IGetString Service1 { get; set; } /// <summary> /// 导出 /// </summary> [Import("db", typeof(IGetString))] public Lazy<IGetString> Service2 { get; set; }
宿主程序:
static void Main() { Program p = new Program(); //组合部件工作 p.Compose(); System.Threading.Thread.Sleep(2000); p.Service1.WriteString(); //通过延迟加载,时间间隔为2秒 p.Service2.Value.WriteString(); Console.ReadLine(); }
输出效果:
demo7:组合部件的继承
导出部件,在接口中使用InheritedExport特性,在实现类中将省略Export标记
/// <summary> /// 继承导出特性 /// </summary> [InheritedExport] interface IGetString { void WriteString(); } /// <summary> ///继承导出功能 /// </summary> class GetString :IGetString { public void WriteString() { Console.WriteLine("这是继承的导出部件"); } }
宿主程序:
/// <summary> /// 导入 /// </summary> [Import] public IGetString Service { get; set; } /// <summary> /// 组合 /// </summary> void Compose() { var catelog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catelog); container.ComposeParts(this); } static void Main() { Program p = new Program(); //组合部件工作 p.Compose(); p.Service.WriteString(); Console.ReadLine(); }
输出效果:
组合容器(CompositionContainer)和目录(Catalog)
经过前面的demo练习,我们已经了解了MEF中的导入(Import)和导出(Export)。在本系列的第一篇文章中我们知道MEF其实还包括另外两个核心内容:组合容器(CompositionContainer)和目录(Catalog)。
在宿主程序中,我们需要通过组合容器和目录,将部件功能引入到当前的宿主程序应用中。
/// <summary> /// 组合 /// </summary> void Compose() { var catelog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catelog); container.ComposeParts(this); }
组合容器:比较常用的有CompositionContainer,而有时候会需要用到CompositionBatch,这里讲不做讲解。
目录:Assembly Catalog(程序集目录),Directory Catalog,Aggregate Catalog,Type Catalog,和仅使用在Silverlight中得目录Deployment Catalog( Silverlight only),Filtered Catalog.其中将着重讲解Assembly Catalog(程序集目录),Directory Catalog,Aggregate Catalog。
1.Assembly Catalog
可以在给定的Assembly 发现所有的导出部件,使用类型AssemblyCatalog。
2.Directory Catalog
它可以在给定的目录(路径,相对路径或绝对路径)中发现导出部件,使用类型DirectoryCatalog。如果你使用的是相对路径,则相对的是当前AppDoamin的基路径。DirectoryCatalog只会对给定目录进行一次性扫描,目录发生变化是容器不会主动刷新,如果需要刷新给定的目录需要调用方法:Refresh() ,当目录刷新时,容器也会重新组合部件。这个通常将一些动态链接库(.dll)作为部件导入到宿主程序中,可以灵活将DirectoryCatalog中的dll增加或移除,以对同一个API实现不同的应用。
demo8:使用Directory Catalog
在项目中开发导出部件:通过添加类库的方式,生成相应功能的.dll文件,其中需要遵照一个API标准(IGetString)。
1.通过MEFDemo8Service,定义API:
/// <summary> /// 定义API,同时定义为继承导出方式 /// </summary> [InheritedExport] public interface IGetString { void OutPut(); }
2.通过MEFDemoPart1和Part2分别实现相应的接口
分别实现了部件1和部件2的方法
/// <summary> /// 实现接口 /// </summary> public class GetString:MEFDemo8Service.IGetString { public void OutPut() { Console.WriteLine("执行了部件1的方法"); } }
3.在宿主程序中,添加API的引用以及添加分别生成part1和part2的.dll文件
其中Lib文件夹中的dll将属性设置为内容,并复制
在宿主程序中,通过导入,组合(使用DirectoryCatalog,指定Lib文件夹),再通过Main的执行,显示出具体的实现内容,代码如下:
class Program { /// <summary> /// 导入部件 /// </summary> [ImportMany(typeof(MEFDemo8Service.IGetString))] public IEnumerable<MEFDemo8Service.IGetString> Service { get; set; } //组合部件 void Compose() { var catelog = new DirectoryCatalog("Lib"); var container = new CompositionContainer(catelog); container.ComposeParts(this); } static void Main() { Program p = new Program(); p.Compose(); foreach (var server in p.Service) server.OutPut(); Console.ReadLine(); }
最终的执行效果:
当在Lib中移出了部件1
最终效果:
当部件添加到Lib中,又得到了最开始的效果。
由此可见,使用Lib,可以将我们的具体实现通过物理方式隔离,在需要的时候添加,不需要的时候移除即可。
3.Aggregate Catalog
聚集目录,有时候我们使用单一的Assembly Catalog和Directory Catalog并不能解决我们的需求,我们可能需要同时使用到他们,这时候我们便可使用Aggregate Catalog,我们可以将Assembly Catalog和Directory Catalog同时添加到目录中,这种添加可以通过构造函数实现,也可以通过目录集合的添加方法来实现:catalog.Catalogs.Add(...)。聚集目录使用类型AggregateCatalog。
MEF带来的联想
留给大家来回答吧!!!
,