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,您可以設置導出屬性,無需明確引用即可加載外部程序集,還可以在運行時動態擴展應用程序。您了解了導入和導出的交互,並通過接口使用契約指定了要處理的導出組件。