51cto下载-mef程序设计指南一

lihui5566 贡献于2016-10-21

作者 sunhai  创建于2012-08-18 07:00:00   修改者sunhai  修改于2012-08-18 07:26:00字数22052

文档摘要:在应用程序中宿主MEF  在应用程序中宿主MEF其实非常简单,只需要创建一个组合容器对象(CompositionContainer)的实例,然后将需要组合的部件(Parts)和当前宿主程序添加到容器中即可。
关键词:

MEF程序设计指南一:在应用程序中宿主MEF   在应用程序中宿主MEF其实非常简单,只需要创建一个组合容器对象(CompositionContainer)的实例,然后将需要组合的部件(Parts)和当前宿主程序添加到容器中即可。首先需要添加MEF框架的引用,既System.ComponentModel.Composition.dll,详细如下代码块: private void Compose() { var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catalog); container.ComposeParts(this); }   通过上面的代码实现就可以完成MEF的宿主,实际上在使用MEF的开发过程中并不会如此简单的应用。可能会定义一个或多个导入(Import)和导出(Export)部件,然后通过MEF容器进行组合,其实也可以理解为“依赖注入”的一种实现。比如定义一个图书接口和一个接口的实现类,在此基础上使用MEF的导入导出特性: public interface IBookService { void GetBookName(); } /// /// 导入 /// [Export(typeof(IBookService))] public class ComputerBookService : IBookService { public void GetBookName() { Console.WriteLine("《Hello Silverlight》"); } }   如上代码通过使用MEF的[System.ComponentModel.Composition.Export]对接口的实现进行导出设置,让接口的实现以容器部件的方式存在,然后通过组合容器进行装配加载,这个过程中就包括了接口的实例化的过程。接下来就需要在MEF的宿主程序中定义一个接口的属 性,并为其标注[System.ComponentModel.Composition.Import]特性以实现接口实现类的导入。如下代码块: /// /// 导入接口的实现部件(Part) /// [Import] public IBookService Service { get; set; }   完成了导入导出的接口与实现的开发及特性配置,下面就剩下一步组合了,也就是本文提到的将部件和宿主程序自身添加到组合容器中,以实现导入(Import)和导出(Export)的组合装配。 /// /// 宿主MEF并组合部件 /// private void Compose() { var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catalog); //将部件(part)和宿主程序添加到组合容器 container.ComposeParts(this,new ComputerBookService()); }   通过以上步骤就完成了MEF的宿主以及一个简单的部件组合的应用示例,下面是本文的完整代码示例: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.Reflection; namespace HostingMef { public interface IBookService { void GetBookName(); } /// /// 导入 /// [Export(typeof(IBookService))] public class ComputerBookService : IBookService { public void GetBookName() { Console.WriteLine("《Hello Silverlight》"); } } class Program { /// /// 导入接口的实现部件(Part) /// [Import] public IBookService Service { get; set; } /// /// 宿主MEF并组合部件 /// private void Compose() { var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catalog); //将部件(part)和宿主程序添加到组合容器 container.ComposeParts(this,new ComputerBookService()); } static void Main(string[] args) { Program p = new Program(); p.Compose(); p.Service.GetBookName(); } } } MEF程序设计指南二:Silverlight中使用CompositionInitializer宿主MEF   MEF可以在传统应用程序中使用(包括桌面的Winform、控制台程序和Web的ASP.NET),也可以在RIA的Silverlight中使用。在Silverlight中只是宿主的方式有所不同,实际上在Silverlight中也可以像传统应用程序中是方式去宿主,MEF也为Silverlight提供了独特的宿主方式,使用中需要引用System.ComponentModel.Composition.Initialization.dll动态链接库。   Silverlight中提供了一种新的宿主MEF的方式,只需要在Application_Startup事件处理函数中通过调用System.ComponentModel.CompositionInitialization程序集的 System.ComponentModel.Composition.CompositionInitializer类里面的方法一行代码就可以完成MEF的宿主,这是非常方便、简单的。详细如下代码块: private void Application_Startup(object sender, StartupEventArgs e) { CompositionInitializer.SatisfyImports(this); }   下面用一个简单的示例程序演示下在Silverlight中的MEF的基本应用。比如编写一个日志组件,分别提供两种写日志的方式,既写文本日志和数据库日志,那么对于需要通过MEF托管的部件可以如下代码定义: public interface ILogger { void WriteLog(string message); } [Export("TXT",typeof(ILogger))] public class TXTLogger : ILogger { public void WriteLog(string message) { System.Console.WriteLine(message); } } [Export("DB",typeof(ILogger))] public class DBLogger : ILogger { public void WriteLog(string message) { System.Console.WriteLine(message); } }   分别将两个不同的日志记录组件都通过[ExportAttribute]进行标注为导出部件,并分别为其设置好通信契约,那么在导入他们的地方就直接通过契约确定分别导入什么类型的日志记录实现部件。可以通过提供一个统一的门面服务以供系统中统一调用,大致如下代码所示: /// /// 门面服务,聚合不同的日志记录部件,通过MEF进行组合 /// [Export] public class FacadeService { [Import("TXT")] public ILogger TXTLogger { get; set; } [Import("DB")] public ILogger DBLogger { get; set; } }   实际上门面服务作为一个简单、单纯的辅助类,也是可以通过MEF进行动态装配的,如上代码就将FacadeService也进行了[ExportAttribute]标注,表示此门面类也是一个可装配的部件,那么在App 中同样可以通过[ImportAttribute]进行导入的。 /// /// 导入FacadeService /// [Import] public FacadeService Service { get; set; }   接下来看看如何调用,FacadeService是被导入在App中的,在应用的时候可以通过Application.Current获取到当前Silverlight的App对象,总而达到可调用被导入在App中的部件。 public MainPage() { InitializeComponent(); var service = (Application.Current as App).Service; service.TXTLogger.WriteLog("写日志到文本文件"); service.DBLogger.WriteLog("写日志到数据库"); } MEF程序设计指南三:MEF中组合部件(Composable Parts)与契约(Contracts)的基本应用   按照MEF的约定,任何一个类或者是接口的实现都可以通过[System.ComponentModel.Composition.Export] 属性将其他定义组合部件(Composable Parts),在任何需要导入组合部件的地方都可以通过在特定的组合部件对象属性上使用[System.ComponentModel.Composition.Import ]实现部件的组合,两者之间通过契约(Contracts)进行通信,实际上这一步可以简单的理解为“依赖注入”,本质上就是对象的实例初始化过程。   我个人理解,凡是通过MEF的[ExportAttribute]标注的对象都可以理解为一个可进行组合的部件,包括对象和对象的属性、字段、方法、事件等;且该对象可以通过[ImportAttribute]进行导入。如下示例代码: public class StringProvider { /// /// 定义导出部件--契约为“Message” /// [Export("Message")] public string Output { get { return "Hello World"; } } } public class Client { /// /// 导入指定契约的部件 /// [Import("Message")] public string Input { get; set; } public void Print() { Console.WriteLine(Input); } }   所谓的契约也就是一种约定,或者叫做一种规则。如上代码中就使用到了契约,在对象StringProvider中就定义了一个导出部件属性(Output),并为其指定了通信契约为“Message”。这里的“Message”就是一种约定,既约定为在需要使用到这个属性的地方,都可以通过[ImportAttribute]使用契约(Message)进行部件的导入。     接下来结合《Silverlight中使用CompositionInitializer宿主MEF》一文中使用的日志记录的应用实例为基础来看看关于契约(Contracts)在较为复杂的部件中的的具体使用方法。假设定义了如下的接口与部件实现代码: public interface ILogger { void WriteLog(string message); } [Export(typeof(ILogger))] public class TXTLogger : ILogger { public void WriteLog(string message) { MessageBox.Show("TXTLogger>>>>>" + message); } } [Export(typeof(ILogger))] public class DBLogger : ILogger { public void WriteLog(string message) { MessageBox.Show("DBLogger>>>>>" + message); } }   对于熟悉面向对象设计方法的人一眼就能明白,上面代码演示了一个接口具有多个实现的场景,仔细观察会发现在每个实现类上面都添加了[ExportAttribute]将其标注为导出部件,并为其添加了通信契约,而且两个实现类的通信契约都是使用的接口(ILogger)的类型参数。   这里需要注意的是在进行导入的时候如果辨别到底是使用的哪一个实现呢?在MEF中提供了一个专门用于导入多个实现的特性[System.ComponentModel.Composition.ImportManyAttribute],如上的日志实现示例就可以通过如下的方式实现多部件导入。 [ImportMany] public IEnumerable Loggers { get; set; }        ImportManyAttribute特性可以将实现接口的所有实现全部组合起来。下面为使用[ImportMany]的完整示例代码: namespace MEFTraining.CPC { public partial class MainPage : UserControl { [ImportMany] public IEnumerable Loggers { get; set; } public MainPage() { InitializeComponent(); CompositionInitializer.SatisfyImports(this); if (Loggers == null) { foreach (var logger in Loggers) { logger.WriteLog("Hello World"); } } } } public interface ILogger { void WriteLog(string message); } [Export(typeof(ILogger))] public class TXTLogger : ILogger { public void WriteLog(string message) { MessageBox.Show("TXTLogger>>>>>" + message); } } [Export(typeof(ILogger))] public class DBLogger : ILogger { public void WriteLog(string message) { MessageBox.Show("DBLogger>>>>>" + message); } } }   上面介绍了如何在相同的契约下获取所有导出部件的实例,在某种情况下或许我们就只直接指导需要使用那一种那个实现方式,那么是否可以通过直接指定一个“契约名”就可以从多个实现中获取到指定的组合部件呢?答案是肯定的,接下来先看看在MEF中中对ExportAttribute和ImportAttribute的定义,源代码如下: public class ExportAttribute : Attribute { public ExportAttribute() : this((string)null, (Type)null){} public ExportAttribute(Type contractType) : this((string)null, contractType){} public ExportAttribute(string contractName) : this(contractName, (Type)null) { } public ExportAttribute(string contractName, Type contractType) { this.ContractName = contractName; this.ContractType = contractType; } public string ContractName { get; private set; } public Type ContractType { get; private set; } }   ImportAttribute同ExportAttribute一样提供了相同的重载构造函数,在将一个对象进行导出部件处理的时候可以直接通过ImportAttribute的属性给对象指定一个契约名,如本篇前面的日志组件的实现就可以修改为如下代码格式。 public interface ILogger { void WriteLog(string message); } [Export("TXT", typeof(ILogger))] public class TXTLogger : ILogger { public void WriteLog(string message) { MessageBox.Show("TXTLogger>>>>>" + message); } } [Export("DB", typeof(ILogger))] public class DBLogger : ILogger { public void WriteLog(string message) { MessageBox.Show("DBLogger>>>>>" + message); } }   通过为不同的导出部件指定了特定的契约名称,那么在装配部件的时候就可以通过契约名进行指定部件的装配并组合部件,为了方便调用可以提供一个服务类,将不同的实现通过不同的契约名装载组合起来以对系统提供一个统一的调用入口。以下为完整的示例代码: public partial class MainPage : UserControl { /// /// 导入日志服务对象 /// [Import] public LogService Service { get; set; } public MainPage() { InitializeComponent(); CompositionInitializer.SatisfyImports(this); Service.DBLogger.WriteLog("Hello MEF"); Service.TXTLogger.WriteLog("Hello MEF"); } } /// /// 聚合不同的日志记录部件,通过MEF进行组合 /// [Export] public class LogService { /// /// 根据契约名进行部件的装配 /// [Import("TXT")] public ILogger TXTLogger { get; set; } [Import("DB")] public ILogger DBLogger { get; set; } } public interface ILogger { void WriteLog(string message); } [Export("TXT", typeof(ILogger))] public class TXTLogger : ILogger { public void WriteLog(string message) { MessageBox.Show("TXTLogger>>>>>" + message); } } [Export("DB", typeof(ILogger))] public class DBLogger : ILogger { public void WriteLog(string message) { MessageBox.Show("DBLogger>>>>>" + message); } } MEF程序设计指南四:使用MEF声明导出(Exports)与导入(Imports)   在MEF中,使用[System.ComponentModel.Composition.ExportAttribute]支持多种级别的导出部件配置,包括类、字段、属性以及方法级别的导出部件,通过查看ExportAttribute的源代码就知道ExportAttribute被定义为Attribute,并为其设置了使用范围。 [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 { //...... }   当任何一个类对象或是其内部的字段、属性、方法需要作为可组合部件的时候,就可以使用[ExportAttribute]将其标注为可导出部件。比如需要将一个对象做为可组合部件进行导出(就是类级别的导出),只需要在类上添加[ExportAttribute]就行了,详细的应用可参考《MEF中组合部件(Composable Parts)与契约(Contracts)的基本应用》,下面为演示代码: [System.ComponentModel.Composition.Export] public class DBLogger { }   对于字段、属性级别的导出同类是一样的,通样使用[ExportAttribute]进行标注,下面代码块演示了一个完整的属性导入与导出的示例。 namespace MEFTraining.ExmprtImport { public partial class MainPage : UserControl { [Import("Name")] public string BookName { get; set; } public MainPage() { InitializeComponent(); CompositionInitializer.SatisfyImports(this); MessageBox.Show(BookName); } } public class BookService { [Export("Name")] public string BookName { get { return "《MEF程序设计指南》"; } } } }   方法级的导入与导出主要是利用委托实现,既Action或Action,其使用也是非常简单的,无论是方法所需的参数还是返回值,都可以通过匿名委托去实现。如下代码中定义了一个BookService类,里面通过MEF导出了PrintBookName方法,且带有一个字符串类型参数,此时就可以通过匿名委托进行形参的和方法的导出。 public class BookService { [Export(typeof(Action))] public void PrintBookName(string name) { Console.WriteLine(name); } }   在需要使用到此方法的地方,只需要通过匿名委托的方法对该方法进行导入就可以了,下面的代码是对上面的导出方法的调用示例。 public partial class MethodExportImport : UserControl { [Import(typeof(Action))] public Action PrintBookName { get; set; } public MethodExportImport() { InitializeComponent(); CompositionInitializer.SatisfyImports(this); PrintBookName("《MEF程序设计指南》"); } }   另外,MEF也支持继承的导入与导出应用,使用[System.ComponentModel.Composition.InheritedExportAttribute]实现基于继承的导出,其他的和字段、属性、方法级的应用完全一致,下面的代码演示了基于继承的导出与导出应用。 namespace MEFTraining.ExmprtImport { public partial class InheritedExportImport : UserControl { [Import] public IUserServie UService { get; set; } public InheritedExportImport() { InitializeComponent(); CompositionInitializer.SatisfyImports(this); string name = UService.GetUserName(); } } [InheritedExport] public interface IUserServie { string GetUserName(); } public class UserService : IUserServie { public string GetUserName() { return "张三"; } } }   MEF还支持构造方法参数的导入,详细这里就不介绍了,有兴趣的可直接查询MEF英文版程序设计指南介绍。 MEF程序设计指南五:迟延(Lazy)加载导出部件(Export Part)与元数据(Metadata)   MEF中使用导出与导入,实质上就是对一个对象的实例化的过程,通过MEF的特性降低了对象的直接依赖,从而让系统的设计达到一种高灵活、高扩展性的效果。在具体的设计开发中,存在着某些对象是不需要在系统运行或者的附属对象初始化的时候进行实例化的,仅仅只需要在需要使用到他的时候才会进行实例化,从系统的上来说这也是提高系统性能的一种可行的实现方式,这种方式就可以理解为对象的迟延初始化,或者叫迟延加载。MEF也对此使用场景提供了完善的实现机制,下面来看看在MEF中的迟延初始化是如何使用的。 namespace MEFTraining.LzayImports { public interface ILogger { void WriteLog(string message); } [Export(typeof(ILogger))] public class DBLogger : ILogger { public void WriteLog(string message) { MessageBox.Show(message); } } }   通过使用前几篇博文中使用的日志组件为例,在日志记录的具体实现对象上进行对象的导出[Export]配置。如果是使用传统的方式进行部件的导入则如下代码块所示: [Import(typeof(ILogger))] public ILogger Logger { get; set; }   如果需要进行迟延(Lazy)加载,MEF专门提供了用于迟延加载的方式,既使用Lazy类来完成迟延加载,然后通过其他属性Value获取到所加载到的对象。详细的使用如下代码块: public partial class MainPage : UserControl { //传统加载 [Import(typeof(ILogger))] public ILogger Logger { get; set; } //迟延加载 [Import] public Lazy Service; public MainPage() { InitializeComponent(); CompositionInitializer.SatisfyImports(this); Logger.WriteLog("日志内容"); Service.Value.WriteLog("日志内容"); } }   通过调试输出可以得到使用迟延导入的对象的详细信息,下面是通过在命令窗口中输出的Service和Service.Value的详细信息。 Service ThreadSafetyMode=PublicationOnly, IsValueCreated=true, IsValueFaulted=false, Value={MEFTraining.LzayImports.DBLogger} IsValueCreated: true Service.Value {MEFTraining.LzayImports.DBLogger} [MEFTraining.LzayImports.DBLogger]: {MEFTraining.LzayImports.DBLogger}   迟延加载还支持元数据的导出和导入,主要使用[MetadataAttribute]特性实现,实际开发中可以进行自定义元数据结构,这里以一个空的元数据接口进行元数据的导入应用演示。 public interface IMetadata { }   在导出部件中就可以使用元数据特性进行声明,如下简单的应用。 [MetadataAttribute] [Export(typeof(Users))] public class Users { public string UserName = "张三"; }   元数据的导入应用如下代码块所示: public partial class MetadataControl : UserControl { [Import(typeof(Users))] public Lazy Users { get; set; } public MetadataControl() { InitializeComponent(); //宿主MEF托管扩展容器 CompositionInitializer.SatisfyImports(this); MessageBox.Show(Users.Value.UserName); } }   对于的调试输出为下面代码块所示: Users ThreadSafetyMode=PublicationOnly, IsValueCreated=true, IsValueFaulted=false, Value={MEFTraining.LzayImports.Users} base {System.Lazy}: ThreadSafetyMode=PublicationOnly, IsValueCreated=true, IsValueFaulted=false, Value={ MEFTraining.LzayImports.Users} Metadata: {_proxy_MEFTraining.LzayImports.IMetadata_0174a468-9771-4271-a37e-9a4a83eca6bd}   MEF中也提供了专门用于元数据导入、导出的特性[ExportMetadata],使用ExportMetadata基本可以满足大部分元数据的导出、导入支持。通过修改上面的示例来实现自定义元数据结构的导入、导出应用演示。 public interface IMetadata { string Name { get; } } [ExportMetadata("Name","李四")] [Export(typeof(Users))] public class Users { public string UserName = "张三"; }   上面的示例代码演示了通过元数据导出属性名为“Name”,其值为“李四”的元数据信息,并且还定义了一个用于承载元数据结构的结构,接下来就可以通过迟延加载导入,进行元数据的获取了。 public partial class MetadataControl : UserControl { [Import(typeof(Users))] public Lazy Users { get; set; } public MetadataControl() { InitializeComponent(); //宿主MEF托管扩展容器 CompositionInitializer.SatisfyImports(this); MessageBox.Show(Users.Value.UserName); } }   下图为允许调试中的截图,可以很清楚的看到,在进行迟延导入的时候已经将导出部件中的元数据信息成功的导入到了当前对象实例属性中。        下面是完整的元数据应用实例代码。 namespace MEFTraining.LzayImports { public partial class MetadataControl : UserControl { [Import(typeof(Users))] public Lazy Users { get; set; } public MetadataControl() { InitializeComponent(); //宿主MEF托管扩展容器 CompositionInitializer.SatisfyImports(this); MessageBox.Show(Users.Value.UserName); } } public interface IMetadata { string Name { get; } } [ExportMetadata("Name","李四")] [Export(typeof(Users))] public class Users { public string UserName = "张三"; } }   除此之外,迟延加载也是支持弱类型的元数据类型的,也可以对元数据进行过滤,这里就不做详细的介绍,有兴趣的朋友可以自己去研究研究。 MEF程序设计指南六:MEF中的目录服务(DeploymentCatalog)   MEF提供的基于特性的编程模型,可以动态的根据目录找出程序集里面的所有程序部件。 对于MEF的目录服务MEF分别为WPF和Silverlight提供了不同的目录机制。使用目录的主要功能就是方便实现程序部件的装载,以及动态的组合应用程序部件等功能,更可以非常方便的得到程序部件的程序集、导出部件等相关数据。   如下代码块演示了如何在Silverlight中获取到当前应用程序的目录信息,包括了程序集和程序部件等。 var catalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly()); var assembly = catalog.Assembly; var parts = catalog.Parts;   在WPF中可以使用DirectoryCatalog装配基于目录的程序部件,Silverlight中则使用DeploymentCatalog来实现目录的管理,包括程序集下载、装配等。下面以MEF的目录在Silverlight中的具体应用为例介绍目录比较通用的用法,既动态下载与装配。为了提供系统的灵活性,可以通过抽象接口以高层次的抽象而存在,那么可以通过如下接口来封装目录(DeploymentCatalog)。 public interface IDeploymentService { void AddXap(string relativeUri, Action completedAction); }   在具体实现中就可以对DeploymentCatalog进行深度封装,以达到灵活应用的目的。并且可以将具体的实现通过[Export]标记为可装配的程序部件,在需要的地方就可以通过接口式的导入[Import]使用了。 [Export(typeof(IDeploymentService))] public class DeploymentCatalogService : IDeploymentService { private static AggregateCatalog _aggregateCatalog; Dictionary _catalogs; public DeploymentCatalogService() { _catalogs = new Dictionary(); } public static void Initialize() { _aggregateCatalog = new AggregateCatalog(); _aggregateCatalog.Catalogs.Add(new DeploymentCatalog()); CompositionHost.Initialize(_aggregateCatalog); } public void AddXap(string relativeUri, Action completedAction) { DeploymentCatalog catalog; if (!_catalogs.TryGetValue(relativeUri, out catalog)) { catalog = new DeploymentCatalog(relativeUri); if (completedAction != null) catalog.DownloadCompleted += (s, e) => completedAction(e); else catalog.DownloadCompleted += DownloadCompleted; catalog.DownloadAsync(); _catalogs[relativeUri] = catalog; _aggregateCatalog.Catalogs.Add(catalog); } } void DownloadCompleted(object sender, AsyncCompletedEventArgs e) { if (e.Error != null) { throw new InvalidOperationException(e.Error.Message, e.Error); } } }   有了上面的封装,下面只需要一句代码就可以完成独立程序包(.xap)的下载和动态装配,下面为调用封装的目录服务接口示例: private void button1_Click(object sender, System.Windows.RoutedEventArgs e) { this.Service.AddXap("MEFTraining.MefCatalogs.Parts.xap", null); } MEF程序设计指南七:使用目录(Catalog)动态装载xap与目录筛选(Filtered Catalog)   如果不使用MEF进行托管扩展处理,只有通过WebClient进行程序包的下载、解析。实际上MEF的动态下载的底层实现一样是使用的WebClient,然后利用AggregateCatalog进行动态组合,详细可查看MEF的源代码(路径:Composition.Initialization\System\ComponentModel\Composition\Hosting\DeploymentCatalog.cs)。   在上一篇程序设计指南《MEF程序设计指南六:MEF中的目录服务(DeploymentCatalog)》中介绍了MEF的目录服务,并对MEF的目录服务进行了接口封装,其中有一个接口就是专门封装的使用MEF的目录进行.xap程序包的动态装载的。 public void AddXap(string relativeUri, Action completedAction) { DeploymentCatalog catalog; if (!_catalogs.TryGetValue(relativeUri, out catalog)) { catalog = new DeploymentCatalog(relativeUri); if (completedAction != null) catalog.DownloadCompleted += (s, e) => completedAction(e); else catalog.DownloadCompleted += DownloadCompleted; catalog.DownloadAsync(); _catalogs[relativeUri] = catalog; _aggregateCatalog.Catalogs.Add(catalog); } }   其应用也非常简单,通过MEF的导入将接口导入到需要使用的地方,然后直接调用上面的方法即可实现对指定路径的xap包的动态下载以及组合。 [Import] public IDeploymentService Service { get; set; } private void button1_Click(object sender, System.Windows.RoutedEventArgs e) { this.Service.AddXap("MEFTraining.MefCatalogs.Parts.xap", null); }   到这里我们还需要学习另外一个接口的使用,IPartImportsSatisfiedNotification接口就是一个当有新的部件进行装配成功后的一个通知接口,可以准确的监听到MEF容器的组合,一旦有新的插件部件进行导入装载到MEF容器中,此接口就会自动的得到通知。其内部就一个接口方法,详细如下代码块: public void OnImportsSatisfied() { }   在使用MEF目录进行导出部件托管的时候,在某些需求下或许只需要其中的一个部件,这种情况可以通过遍历部件集合得到。然而MEF也为此提供了一种解决方案,那就是使用目录过滤筛选功能。MEF中的ComposablePartCatalog类和INotifyComposablePartCatalogChanged接口就是专门用来实现目录筛选的,可以如下代码段中演示的对目录过滤的封装。 public class FilteredCatalog : ComposablePartCatalog, INotifyComposablePartCatalogChanged { private readonly ComposablePartCatalog _inner; private readonly INotifyComposablePartCatalogChanged _innerNotifyChange; private readonly IQueryable _partsQuery; public FilteredCatalog(ComposablePartCatalog inner, Expression> expression) { _inner = inner; _innerNotifyChange = inner as INotifyComposablePartCatalogChanged; _partsQuery = inner.Parts.Where(expression); } public event EventHandler Changed { add { if (_innerNotifyChange != null) _innerNotifyChange.Changed += value; } remove { if (_innerNotifyChange != null) _innerNotifyChange.Changed -= value; } } public event EventHandler Changing { add { if (_innerNotifyChange != null) _innerNotifyChange.Changing += value; } remove { if (_innerNotifyChange != null) _innerNotifyChange.Changing -= value; } } public override System.Linq.IQueryable Parts { get { return _partsQuery; } } }   通过上面的封装,使用目录过滤功能之需要传入正确的筛选表达式就可以了,按照MEF中的约定其筛选表达式应如下格式。 var filteredCat = new FilteredCatalog(catalog, def => def.Metadata.ContainsKey(CompositionConstants.PartCreationPolicyMetadataName) && ((CreationPolicy)def.Metadata[CompositionConstants.PartCreationPolicyMetadataName]) == CreationPolicy.NonShared);   MEF中提供了一个专门用于目录过滤筛选的元数据特性PartMetadata,要进行目录部件的筛选过滤就需要通过PartMetadata特性的标注,MEF容器才能进行正确的装配。以一个简单的实例演示,比如说有三个用户控件(UserControl),分别进行导入和筛选过滤元数据的配置。 [PartMetadata("UC", "AA")] [Export(typeof(UserControl))] public partial class AA : UserControl { public AA() { InitializeComponent(); } } [PartMetadata("UC", "BB")] [Export(typeof(UserControl))] public partial class BB : UserControl { public BB() { InitializeComponent(); } } [PartMetadata("UC", "CC")] [Export(typeof(UserControl))] public partial class CC : UserControl { public CC() { InitializeComponent(); } }   三个用户控件的元数据名称都为"UC",其值分别是AA、BB、CC,那么就可以通过下面代码的方式实现对目录中部件的筛选,下面是代码块演示了如何从目录中筛选出元数据名称为"UC",其值为"CC"的部件。 //获取当前应用程序目录 var catalog = new AssemblyCatalog(typeof(MainPage).Assembly); //将目录装载进MEF组合容器 var parent = new CompositionContainer(catalog); //通过元数据过滤筛选出元数据名称为"UC"值为"CC"的组合部件 var filteredCat = new FilteredCatalog(catalog, def => def.Metadata.ContainsKey("UC") && def.Metadata["UC"].ToString() == "CC"); var perRequest = new CompositionContainer(filteredCat, parent); var control = perRequest.GetExportedValue(); MEF程序设计指南八:部件生命周期(Parts Lifetime)托管   MEF中的每一个可进行动态装配的导出部件都是具有生命周期的,在没有特别需求的情况下一般都没有对生命周期进行管理,而实际上MEF已为每一个部件进行了默认的生命周期管理,MEF的生命周期分为三种:Any、Shared及NonShared,被定义在System.ComponentModel.Composition.CreationPolicy枚举对象中。 namespace System.ComponentModel.Composition { public enum CreationPolicy { Any = 0, Shared = 1, NonShared = 2, } }   Any表示可共享或不共享,部件的实例用MEF容器根据不同的请求需求自动控制;Shared表示共享部件,既Shared类型的插件部件可以在多个MEF组合容器中共用;其次是NonShared类型,表示不共享部件实例,每当有新的请求就会创建一个新的对象实例。在MEF中,通过PartCreationPolicyAttribute特性实现对部件的生命周期配置。 public interface IBookService { string GetBookName(); } [PartCreationPolicy(CreationPolicy.Any)] [Export(typeof(IBookService))] public class MEFBookService : IBookService { public string GetBookName() { return "《MEF程序设计指南》"; } } [PartCreationPolicy(CreationPolicy.NonShared)] [Export(typeof(IBookService))] public class ASPNETBookService : IBookService { public string GetBookName() { return "《ASP.NET项目案例》"; } } [PartCreationPolicy(CreationPolicy.Shared)] [Export(typeof(IBookService))] public class SLBookService : IBookService { public string GetBookName() { return "《Silverlight高级编程》"; } }   如上示例代码分别演示了使用Any、Shared及NonShared类型的生命周期托管类型对不同的对象进行导出部件配置。接下来通过导入部件并加入生命周期托管筛选,详细应用如下代码块所示: public partial class MainPage : UserControl { [ImportMany(RequiredCreationPolicy = CreationPolicy.Shared)] public List Service { get; set; } public MainPage() { InitializeComponent(); CompositionInitializer.SatisfyImports(this); //得到成功装配的部件总数为2,因为Any类型是可以共存于多个MEF容器的。 int count = Service.Count; } }   表面上看去和上一篇指南中介绍的部件的筛选过滤功能非常相似,不同是的过滤筛选是通过自定义筛选策略实现,而这里是通过MEF生命周期范围托管来实现的。在实际的项目开发中需根据不同的应用场景确定具体的技术实现方案。   除了容器部件的生命周期托管,我们也得考虑部件容器自身的生命周期,容器什么时候释放资源,时候时候释放其内部部件的资源占用等。为了提高系统的整体性能,MEF建议将每一个可导入部件实现IDisposable 接口,用于资源的占用处理,如果部件从MEF容器中移除那么所对应占用的资源也会自动的清理。这里需要注意一点,就是当组合容器被释放掉后迟延加载的操作就不能再继续工作了,会抛出System.ObjectDisposedException异常。          如上图所示,MEF的容器是具有层次结构的,最高层级容器为程序直接应用级容器,可以通过以下的代码获取到。 var catalog = new AssemblyCatalog(typeof(MainPage).Assembly); var container = new CompositionContainer(catalog);   位于顶级容器之下的子容器,通常来说都是控制着一些可动态装配的部件对象,如同上一篇指南中介绍到使用过滤器筛选部件的应用案例,通过过滤表达式从顶级容器中进行筛选,得出了新的MEF容器child,child托管着根据条件过滤筛选出来的所有结果部件对象。详细代码如下: //获取当前应用程序目录 var catalog = new AssemblyCatalog(typeof(MainPage).Assembly); //将目录装载进MEF组合容器 var parent = new CompositionContainer(catalog); //通过元数据过滤筛选出元数据名称为"UC"值为"CC"的组合部件 var filteredCat = new FilteredCatalog(catalog, def => def.Metadata.ContainsKey("UC") && def.Metadata["UC"].ToString() == "CC"); var child = new CompositionContainer(filteredCat, parent); var control = child.GetExportedValue();

下载文档到电脑,查找使用更方便

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享文档获得金币 ] 0 人已下载

下载文档