Managed Extensibility Framework 简介

概述

Managed Extensibility Framework (MEF) 使开发人员能够在其 .NET 应用程序中提供挂钩,以供用户和第三方扩展。可以将 MEF 看成一个通用应用程序扩展实用工具。

MEF 使开发人员能够动态创建扩展,无需扩展应用程序,也不需要任何特定于扩展内容的知识。这可以确保在编译时两者之间不存在耦合,使应用程序能够在运行时扩展,不需要重新编译。MEF 还可以在将扩展加载到应用程序之前,查看扩展程序集的元数据,这是一种速度更快的方法。

本实验中包含几个与可扩展性相关的关键概念:

•              Composition(复合)是将几个带有不同功能的对象组合为一个或多个复杂对象的过程。复合不是从父类中继承功能,而是将几个不同的对象组合为一个对象。例如,Wing、Propeller、Fuselage 和 VerticalStablizer 对象可以组成 Aircraft 对象的一部分。

•              ComposableParts 是 MEF 的关键构建块。ComposableParts 支持应用程序通过 Exports 和 Imports 公开和使用组件扩展。

•              Contracts 是 Export 和 Import 组件之间的通信途径。Contract 通常通过 Interface 类实现。Contracts 支持 MEF ComposableParts 以避免依赖关系或者与其他组件之间的紧密耦合。

•              Conditional Binding 允许加载满足特定元数据标准的组件。以上述示例为例,您可以选择加载 VerticalStabilizer  组件,这些组件仅由复合石墨 (composite graphite) 组成。

实现扩展的主要方式是,在应用程序的扩展点添加 Import 属性并向扩展添加相应的 Export 属性。Import 和 Export 可以看做是供应商和消费者的关系:Export 组件提供了一些价值;Import 组件消费这些价值。其他扩展选项对于开发人员是开放的,包括完全自定义的扩展方法;但是,本实验仅关注上文提到的主要方法。

目标

在本次动手实验中,您将学习如何:

•              定义组件的可扩展性选项

•              执行条件绑定和组件创建

•              在应用程序运行时导入扩展的程序集

               

系统要求

您必须拥有以下工具才能完成本实验:

•             Microsoft Visual Studio 2010

•             .NET Framework 4

               

安装

使用 Configuration Wizard 验证本实验的所有先决条件。要确保正确配置所有内容,请按照以下步骤进行。

注意: 要执行安装步骤,您需要使用管理员权限在命令行窗口中运行脚本。

1.            如果之前没有执行,运行 Training Kit 的 Configuration Wizard。要做到这一点,运行位于 %TrainingKitInstallationFolder%\Labs\IntroToMEF\Setup 文件夹下的 CheckDependencies.cmd 脚本。安装先决条件中没有安装的软件(如有必要请重新扫描),并完成向导。

注意:为了方便,本实验中管理的许多代码都可用于 Visual Studio 代码片段。CheckDependencies.cmd 文件启动 Visual Studio 安装程序文件安装该代码片段。

               

练习

本次动手实验由以下练习组成:

1.            使用 MEF 向应用程序动态添加模块

2.            动态扩展窗体

               

初始材料

这次动手实验包括以下初始材料。

•              Visual Studio 解决方案。您将发现可以用作练习起点的 Visual Studio 解决方案,具体解决方案取决于练习。

如果我束手无策了怎么办?

该动手实验中的源代码包括一个最终文件夹,如果完成了每个练习的每一步,您可以在该文件夹中找到应该获取的 Visual Studio 解决方案。如果需要其他帮助来完成练习,您可以使用该解决方案作为指南。

完成本实验的估计时间:30 分钟。

               

下一步

练习 1:使用 MEF 向应用程序动态添加模块

               

练习 1:使用 MEF 向应用程序动态添加模块

Managed Extensibility Framework 的一个实际应用是在运行时向应用程序添加模块。这在以下场景非常有用:用户选择购买或初始安装特定模块,之后可能需要添加更多模块。使用 MEF,您可以配置应用程序来监控众所周知的目录,并在该目录中添加找到的任何模块程序集。将程序集放入目录可以使应用程序加载这些程序集,无需明确设置对它们的引用。

任务 1 –更新主窗体以加载支持的模块

在本任务中,您将向现有窗体添加代码,以创建扩展挂钩并动态导入稍后在本练习中创建的类。仅导入那些与配置元数据匹配的类。

MainForm.cs 中定义的主窗体需要一种方法来从文件系统中读取模块。为了本例的简便起见,您将在窗体初始加载过程中导入模块。

1.            从 Start | All Programs | Microsoft Visual Studio 2010 | Microsoft Visual Studio 2010 打开 Microsoft Visual Studio 2010。

2.            打开 MefLab.sln 解决方案文件。默认情况下,该文件位于以下文件夹:%TrainingKitInstallFolder%\Labs\IntroToMEF\Source\Ex01-DynamicallyAddModules\begin\C#。

3.            在代码视图中打开 MainForm(右键单击解决方案资源管理器中的 MainForm,然后选择 View Code)。

4.            更新窗体以使用 MEF 库。为此,在 MainForm 类定义中的顶部添加以下语句。

C#

using System.ComponentModel.Composition;

using System.ComponentModel.Composition.Hosting;

注意:MEF 框架通过契约导入和导出。这些契约由接口指定,用于定义哪些可扩展点 (Imports) 与哪些导出组件相关。您的 MainForm 将导入实现 IMainFormContract 的模块,后者在 MefCommon 项目中定义。要导入这些模块,您需要定义一个集合来通过属性保存和公开它们。MEF 的复合阶段将解析适用的 Exports 和 Imports 并加载您的集合。

5.            将以下代码添加到 MainForm.cs 文件中构造函数的上方。

(代码片段– MEF 简介实验 - 练习 1 ImportedMainFormContracts)

C#

[ImportMany]

public Lazy<IMainFormContract, IDictionary<string, object>>[] ImportedMainFormContracts { get; set; }

6.            MEF 的 CompositionContainer 用于匹配组件与导出和导入值。CompositionContainer 支持加载、绑定和检索组件及其值。通过在 MainForm 类的上方添加以下变量声明创建全局 CompositionContainer。

C#

private CompositionContainer _container;

7.            您现在必须创建一个帮助函数从文件夹导入对象,您已经使用 MainForm.cs 文件中的 _extensionDir 变量指定了该文件。在 MainForm.cs 文件 MainForm 类的下方添加以下代码。

(代码片段 – MEF 简介实验 - 练习 1 GetContainerFromDirectory)

C#

private CompositionContainer GetContainerFromDirectory()

{

      var catalog = new AggregateCatalog();

      var thisAssembly =

          new AssemblyCatalog(

              System.Reflection.Assembly.GetExecutingAssembly());

      catalog.Catalogs.Add(thisAssembly);

      catalog.Catalogs.Add(

          new DirectoryCatalog(_extensionDir));

      var container = new CompositionContainer(catalog);

      return container;

}

8.            接下来,您需要使用已加载的容器。您必须遵从常用 MEF 模式,这需要使用 Compose 方法让 MEF 解析所有适用的导入和导出操作。在 GetContainerFromDirectory 方法下方添加以下方法。

(代码片段 – MEF 简介实验 - 练习 1 Compose)

C#

private bool Compose()

{

    _container = GetContainerFromDirectory();

    try

    {

        _container.ComposeParts(this);

    }

    catch (CompositionException compException)

    {

        MessageBox.Show(compException.ToString());

        return false;

    }

    return true;

}

9.            您将调用窗体构造函数中的 Compose。添加代码,构造函数应该如下所示:

(代码片段 – MEF 简介实验 - 练习 1 Compose 调用)

C#

public MainForm()

{

InitializeComponent();

    bool successfulCompose = Compose();

    if (!successfulCompose)

    {

        this.Close();

    }

}

               

任务 2 –创建一个扩展模块

在本任务中,您将使用窗体处理现有的 Windows Forms 项目以维护员工数据。您将使用 MEF 标记窗体,表示其可用于导出到其他应用程序。MefEmployeeModule 项目预建了几个类,以节省时间。

1.            在继续下一步前,您将需要引用 MEF 库。将对 MEF 库的引用添加到 MefEmployeeModule 项目。

a.            选择 Solution Explorer 中的 MefEmployeeModule 项目并选择 Project | Add Reference 。将出现 Add References 对话框。 

b.            选择 .NET 选项卡。

 

图 1

MEF 引用

c.             选择 System.ComponentModel.Composition 组件。单击 OK 按钮添加对该库的引用。

注意: 如果您没有找到 System.ComponentModel.Composition 组件,则转到 Browse 选项卡,在 %SystemRoot%\Microsoft.net\Framework\v4.0.20704 文件夹或相应的 .NET Framework 4.0 文件夹中查询该程序集。

2.            您还需要使用合适的 using 语句更新类文件。在代码视图中打开 EmployeeMaintenance 窗体,并在文件顶部 using 块中添加以下行。

C#

using System.ComponentModel.Composition;

3.            EmployeeMaintenance 窗体就是需要添加到主窗体的模块。记住,您已经在 MainForm 中使用了 ImportMany 属性,以便仅拉入实现 IMainFormContract 契约的组件。您需要根据该契约指定 EmployeeMaintenance 窗体。类声明之后的代码如下:

C#

[Export(typeof(IMainFormContract))]

public partial class EmployeeMaintenance :Form, IMainFormContract

4.            IMainFormContract 指定必须实现的两个属性。将以下代码添加到 EmployeeMaintenance 构造函数上方。

(代码片段 – MEF 简介实验 - 练习 1 Implement IMainFormContract)

C#

public string MenuItemText

{

    get { return "&Employees"; }

}

public string SubFormTitle

{

    get { return "Employee Pane"; }

}

5.            您还需要使用将用于导入 MainForm 类的一些元数据来修饰该类。MEF 允许您在各个位置查询该元数据。添加 ExportMetaData 属性如下:

(代码片段 – MEF 简介实验 - 练习 1 ExportMetaData attributes)

C#

[Export(typeof(IMainFormContract))]

[ExportMetadata("Name", "Employee Pane")]

[ExportMetadata("MenuText", "&Employees")]

public partial class EmployeeMaintenance :Form, IMainFormContract

6.            保存窗体并编译 MefEmployeeModule 项目。选择 Build | Build MefEmployeeModule。MefEmployeeModule 程序集现在已经编译,并可使用 MEF 导入到其他应用程序中。

               

任务 3 –导入 Employee Maintenance 窗体

在本任务中,您将更新主应用程序窗体,以在运行时用户单击菜单项时导入可用的模块。

1.            在 Solution Explorer 的 MefLabMain 项目中双击 MainForm.cs,在设计视图下打开 MainForm。

2.            现在需要浏览 CompositionContainer 中的组件并确定导出菜单文本信息的组件。您需要调整窗体,以根据导出的元数据加载菜单项。Compose 方法加载支持 IMainFormContract 的模块列表,因此您需要浏览该集合并收集将添加到菜单中的项。为窗体的 Load 事件创建一个事件处理程序,以便可以根据需要调整菜单。在窗体中央双击。将出现代码视图窗口,并带有定义的新 MainForm_Load 方法。添加以下代码以对集合进行迭代,根据需要更新菜单项。注意,您还要与事件处理程序挂钩以处理每个新的菜单项。

(代码片段– MEF 简介实验 - 练习 1 填充菜单)

C#

private void MainForm_Load(object sender, EventArgs e)

{

    foreach (var export in this.ImportedMainFormContracts)

    {

        var exportedMenuText = export.Metadata["MenuText"] as string;

        if (String.IsNullOrEmpty(exportedMenuText))

        {

            return;

        }

        ToolStripItem menuItem =

            modulesToolStripMenuItem.DropDownItems.Add(exportedMenuText);

        menuItem.Click += new System.EventHandler(this.LaunchModule_Click);

    }

}

注意:如下所示,将逻辑混合到应用程序表示层不是一种好的软件工程实践。很难测试、混合问题,以及打破许多其他可靠的设计原则。

您将打破该原则,在 MainForm_Load 等窗体方法中显示逻辑混合,以保证示例的简明扼要。在实际的应用程序中,该逻辑应该分离到另一个类,最好使用 Model View Presenter 或类似方法。我们建议您浏览 MVP 及其相关项目作为设计原则,帮助您编写更多灵活、可维护、可测试的应用程序。

3.       下一步是添加从菜单操作启动组件的功能。您将使用与之前相同的方法:查看导入组件列表中加载的组件,通过查看元数据找到适用的项。Value 属性允许您启动特定的组件。在 MainForm 类定义的底部添加以下方法。该方法与 MainForm_Load 方法结合可处理动态添加菜单的单击事件。

(代码片段– MEF 简介实验 - 练习 1 菜单单击处理程序)

C#

private void LaunchModule_Click(object sender, EventArgs e)

{

    ToolStripItem thisItem = sender as ToolStripItem;

    if (thisItem == null) { return; }

    string thisItemTitle = thisItem.Text;

    foreach (var export in this.ImportedMainFormContracts)

    {

        string menuTitle = export.Metadata["MenuText"] as string;

        if (String.IsNullOrEmpty(menuTitle))

        {

            return;

        }

        if (menuTitle == thisItemTitle)

        {

            Form frm = export.Value as Form;

            if (frm == null) { return; }

            frm.Show(this);

            return;

        }

    } 

}

               

下一步

练习 1:验证

               

练习 1:验证

在本验证中,您将分别使用和不使用导入模块来运行应用程序。

1.            添加以下 Post-Build 命令以生成附加输出文件夹。为此,右键单击 MefEmployeeModule 项目并选择 Properties。单击 Build Events 选项卡并将以下内容剪切到 Post-build 事件命令行字段中。确定包含了引号,以正确处理带空格的目录。

Post-Build Command

if not exist "$(SolutionDir)MefLabMain\bin\Debug\ExtModules" mkdir "$(SolutionDir)MefLabMain\bin\Debug\ExtModules"

2.            编译解决方案 (CTRL+SHIFT+B)。

3.       设置 MefLabMain 作为启动项目。在 Solution Explorer 中,右键单击 MefLabMain 并选择 Set as startup project。

4.            按 F5 运行应用程序。应该会出现父窗口,其顶部应有一个菜单。Modules 菜单下方应该没有任何项目。

 

图 2

空的 Modules 菜单

5.            退出应用程序。选择 File | Exit。

6.            您已经构建了该应用程序来监控 MefLabMain\bin\Debug\ExtModules 目录的更改。您可以更新 MefEmployeeModule 项目,以自动通过项目的 Post Build 事件将其输出二进制文件保存到该文件夹。将以下粗体显示的行添加到 Post-Build 命令行中。

Post-Build Command

if not exist "$(SolutionDir)MefLabMain\bin\Debug\ExtModules" mkdir "$(SolutionDir)MefLabMain\bin\Debug\ExtModules"

copy "$(TargetPath)" "$(SolutionDir)MefLabMain\bin\Debug\ExtModules"

7.            按 F5 运行应用程序。应该会出现父窗口,其顶部应有一个菜单。Employees 菜单项应该在 Modules 菜单下方出现。

 

图 3

加载了模块的 Modules 菜单

8.            单击 Employees 菜单项。应该出现一个 Employee Maintenance 窗体。

 

图 4

Employee Maintenance 窗体

注意: 加载模块时抛出 CompositionExceptions 通常表示您忘了将所需的某个文件复制到 ExtModules 文件夹中。

9.            注意,将从父项目调用 Employee 模块,无需明确设置任何引用。MEF 处理了对所有程序集的加载操作,允许您创建一个完全解耦的组件集合。

10.          单击 EmployeeMaintenance 窗体右上方的关闭按钮 ( ) 关闭它。

11.          选择 File | Exit 关闭父窗体。

               

下一步

练习 2:动态扩展窗体

               

练习 2:动态扩展窗体

在最后一个练习中,您将程序集放入众所周知的目录中,向应用程序动态添加一个新的 Employee Maintenance 窗体。现在,您将使用 MEF 扩展这个新添加的窗体,动态加载可以为窗体提供更多功能的窗体控件。

无论有没有扩展,应用程序都必须能够正常工作,因此需要通过 Employee Maintenance 窗体来松散耦合扩展的功能。两者都不需要了解另一方的内在工作机制。您将通过 Interface 使用 Contract 支持此功能。

本练习将使用几个已经预建的类,使您能关注本实验的关键方面。

我们的计划是扩展 EmployeeMaintenance 窗体,动态添加操作员工列表的命令按钮。您将使用现有接口 ICmdButtonInfo,该接口包含命令按钮和一个 CompanyInfo 对象,该对象可以从导出的对象继承而来。

任务 1 –将 Add Employee Button 标记为可导出

MEFEmployeeExtender 项目包含可以在运行时动态加载到 Employee Maintenance 窗体的控件。对于本实验,您将修改两个控件– AddEmployeeButton 和 DeleteEmployeeButton,它们都实现 ICmdButtonInfo 契约。

在本任务中,您将修改 AddEmployeeButton,通过 ICmdButtonInfo 契约导出其信息。

1.            从 Start | All Programs | Microsoft Visual Studio 2010 | Microsoft Visual Studio 2010 打开 Microsoft Visual Studio 2010。

2.            打开 MefLab.sln 解决方案文件。默认情况下,该文件位于以下文件夹:%TrainingKitInstallFolder%\Labs\IntroToMEF\Source\Ex02-DynamicallyExtendAForm\begin\C#。您也可以继续使用上一个练习完成时获得的解决方案。

3.            您还需要使用 EmployeeMaintenance 窗体中的 AddEmployee 按钮,因此需要将其标记为可通过契约导出。修改 AddEmployeeButton.cs 类,使用 Export 属性将该类标记为可导出。

C#

[Export(typeof(ICmdButtonInfo))]

public class AddEmployeeButton :ICmdButtonInfo

4.            保存文件。

               

任务 2 –将 DeleteEmployee Button 标记为可导出

1.            与 AddEmployeeButton 一样,您需要通知 MEF 通过 ICmdButtonInfo 契约导出 DeleteEmployeeButton。修改 DeleteEmployeeButton.cs 类,使用 Export 属性将该类标记为可导出。

C#

[Export(typeof(ICmdButtonInfo))]

public class DeleteEmployeeButton:ICmdButtonInfo

2.            保存文件。

               

任务 3 –向 Employee Maintenance 窗体添加一个 Panel 控件

在本任务中,向 Employee Maintenance 窗体添加一个 Panel 控件。之后,您可以动态向窗体添加按钮。将它们放在该面板上将更容易将这些按钮对齐。

1.            双击 Solution Explorer 中 MEFEmployeeModule 项目下方的 EmployeeMaintenance.cs,在设计器中打开 EmployeeMaintenance 窗体。

2.            如果 Toolbox 面板不可见,单击 View 菜单的 Toolbox。

3.            从工具箱 Containers 选项卡中将 Panel 控件拖到窗体上。

a.            在 DataGridView 下找到 Panel。 

b.            拖动 Panel 的边缘,将其宽度拉伸到 DataGridView 的宽度。

c.             将 Panel 的名称更改为“ButtonsPanel”。

               

任务 4 –向 Employee Maintenance 窗体添加代码以加载扩展的对象

在本任务中,您将向 Employee Maintenance 窗体的 Load 事件处理程序添加代码。该代码将查看包含导出控件的众所周知的程序集目录。这些控件将在运行时自动添加到窗体中。

1.            在 Solution Explorer 中右键单击并选择 View Code,以打开 EmployeeMaintenance.cs。

2.            添加一个集合来保存扩展的对象。您将引用该集合作为导入对象,因为您将把这些对象导入到这个特定类中。注意,您只接受满足 ICmdButtonInfo 契约的导入。在 EmployeeMaintenance 类的顶部添加以下代码。

(代码片段– MEF 简介实验 - 练习 2 ImportedButtons)

C#

[ImportMany]

public Lazy<ICmdButtonInfo>[] ImportedButtons

{

    get;

    set;

}

3.            现在,您需要迭代该集合,并加载导入的对象,本例中为按钮。将代码添加到 MainForm 之后,您需要使用 Value 属性访问导出对象本身。在 EmployeeMaintenance 类的底部添加以下方法。

(代码片段– MEF 简介实验 - 练习 2 CreateButtons)

C#

private void CreateButtons()

{

    int left = 10;

    foreach (var importedButton in this.ImportedButtons)

    {

        var btnInfo = importedButton.Value;

        btnInfo.CompanyInfo = this.CompanyInfo;

        btnInfo.CompanyInfo.EmployeeListChanged +=

            new EventHandler(CompanyInfo_EmployeeListChanged);

        Button btn = btnInfo.CommandButton;

        this.ButtonsPanel.Controls.Add(btn);

        btn.Left = left;

        left += btn.Width;

        left += 20;

    }

}

4.            更新窗体的加载方法以调用 CreateButtons 方法。

C#

private void EmployeeMaintenance_Load(object sender, EventArgs e)

{

GetAllEmployees();

    CreateButtons();

}

5.            当用户选择 DataGridView 中的行时,您需要更新 CompanyInfo 对象的 SelectedEmployee 字段。这样一来,松散耦合的 CompanyInfo 对象中的按钮可以了解要对哪个员工进行操作。在 EmployeeMaintenance 类中添加以下方法。

(代码片段– MEF 简介实验 - 练习 2 empDataGridView_SelectionChanged)

C#

private void empDataGridView_SelectionChanged(object sender, EventArgs e)

{

    if (empDataGridView.SelectedRows.Count > 0)

    {

        Employee selectedEmployee

            = (Employee)empDataGridView.SelectedRows[0].DataBoundItem;

        this.CompanyInfo.SelectedEmployee = selectedEmployee;

    }

    else

    {

        this.CompanyInfo.SelectedEmployee = null;

    }

}

6.            将该方法连接到 DataGridView 的 SelectionChanged 事件。在设计模式中打开 EmployeeMaintenance 窗体。右键单击 DataGridView 控件并从上下文菜单中选择 Properties。在 Properties 表单上,单击 Event 图标列出事件。在列表中找到 SelectionChanged 事件,并使用下拉列表选择 empDataGridView_SelectionChanged 方法。

 

图 5

连接 SelectionChanged 事件

7.            编译解决方案。选择 Build | Build Solution。

               

下一步

练习 2:验证

               

练习 2:验证

在本验证中,您将运行带有新 MEF 扩展的解决方案。

1.            如果它尚未打开,可以从 Start | All Programs | Microsoft Visual Studio 2010 | Microsoft Visual Studio 2010 中打开 Microsoft Visual Studio 并打开 MEFLab 解决方案。

2.            测试带有新扩展的应用程序。MefEmployeeExtender 程序集已经通过预配置的 Post-Build 事件复制到 ExtModules 文件夹中。

a.            按 F5 运行应用程序。选择 Modules | Employees。 

b.            应该出现一个 Employee Maintenance 窗体。这一次,它应该包含两个新按钮:Delete Employee 和 Add Employee。

 

图 6

EmployeeMaintenance 窗体

c.             单击 Add Employee 按钮。DataGridView 中将显示一个新行。

d.            单击任何行左边的灰色框,突出显示 DataGridView 该行。单击 Delete Employee 按钮。该行应该会消失。

 

图 7

删除行

e.            在 Employee Maintenance Load 方法中设置断点,每个按钮的单击方法都可以在该代码中步进,以查看发生了什么。

f.             关闭 Employee Maintenance 窗体和主窗体

               

下一步

总结

               

总结

在本实验中,您使用 Microsoft Managed Extensiblity Framework 为应用程序添加了几个可扩展性点,并构建了扩展来插入这些可扩展性点。

使用 MEF,您可以设置导出属性,无需明确引用即可加载外部程序集,还可以在运行时动态扩展应用程序。您了解了导入和导出的交互,并通过接口使用契约指定了要处理的导出组件。