本文章是由機器翻譯。

切邊

應用程式擴充性:MEF vs。IoC

Dino Esposito

在 Microsoft 中還有一個有趣的新元件。NET 架構 4 特別設計來提供玉蘭問題的有效回答:如何撰寫可延伸應用程式,他們所做的所有組件在執行階段可以探索?

如 Glenn 區塊所述他年 2 月 2010年篇文章中,"建置可撰寫內的應用程式。NET 管理擴充性架構 4 」” (msdn.microsoft.com/magazine/ee291628), 管理擴充性架構 (MEF) 可以用來簡化建置可撰寫和外掛程式的基礎應用程式。做為啟動 [上一步中 1994年接近問題的其中一個 (是的它是我第一的真實挑戰之一身為開發人員),我絕對樂於任何解決方案建議在此問題空間。

MEF 並不需要您購買、 下載和參考任何其他的程式庫,而且它還提供簡單的程式設計介面,因為它主要針對解決一般、 協力廠商的現有的應用程式的擴充性的問題。Glenn 的發行項是 MEF 簡介,應該考量需要的讀取如果您考慮外掛程式的基礎的應用程式。

在本文中我將逐步建置使用 MEF 作為基礎黏附保留主要內文及應用程式的外部組件放在一起可延伸應用程式所需的步驟。

從與 MEF 之間的來回 IoC

取得範例應用程式之前,不過,我想要共用 MEF 和另一個常用系列架構的一些想法:反向的控制項 (IoC)。

簡單的說,則是正確的功能的 MEF 和一般 IoC 架構的物件重疊,但不一致的假設。與大多數 IoC 架構中,您可以執行 MEF 只是不支援的工作。您無法可能採用功能豐富的 IoC 容器 ; 點精神和努力自己,以模擬一些 MEF 特定功能。之,當我提到在類別中的 MEF 和日常工作是,要求您經常我的問題:MEF 和 IoC 工具之間有何差別?以及當我真的需要 MEF?

我的想法是的核心,MEF 是直接內建至 IoC 架構。NET Framework。它不是為強大許多受歡迎的 IoC 架構今日,但是它可以效果非常好執行典型的 IoC 容器的基本工作。

現在,IoC 架構都有三個一般功能。首先,他們可以做為物件的 graph 處理站,並逐一瀏覽物件關聯性和相依性,以建立任何必要和註冊型別的執行個體的鏈結。第二,IoC 架構可以管理建立執行個體的存留期,並提供快取和共用功能。第三,大部分 IoC 架構支援攔截和優惠若要建立動態 proxies 周圍特定型別的執行個體,讓開發人員可以前置和後續處理方法的執行。我涵蓋攔截凝聚力 2.0 在年 1 月 (msdn.microsoft.com/magazine/gg535676).

MEF,方式,可以做為物件,圖形的工廠這表示它可以辨識和處理需要在執行階段解析的類別上的成員。MEF 也提供最小的支援快取執行個體,這表示存在一些快取功能,但是它們並不像其他 IoC 架構為功能豐富。最後,在隨附的版本。NET 架構 4 MEF 缺少攔截功能完全。

所以當您應該使用 MEF?如果您從未使用過 IoC 架構,而且只需要清除系統的設計,藉由加入相依性的位元資料隱碼,然後 MEF 可以是簡單的開始。只要您可以快速達成您與它的目標,MEF 最好 IoC 架構。

相反地,如果您已經花了年使用一或多個 IoC 架構,並可以擠任何位元的這些功能,然後可能沒有執行任何動作,MEF 可以讓您以外,可能是,它能夠掃描不同類型的類別目錄來尋找相符的型別。請注意,但是,有些 IoC 架構,例如 StructureMap (structuremap.net/structuremap/ScanningAssemblies.htm) 已經提供給掃描目錄和尋找特定的型別或實作的組件指定介面。使用 MEF,這是可能更容易且更為直接的方式比處理 StructureMap (和一些其他)。

在 [摘要] 要回答的第一個問題是要尋找的一般擴充性。如果答案是肯定的則必須視為 MEF — 或許是除了 IoC 工具,如果您還必須處理相依性、 單一子句和攔截的情況。如果答案為否,然後最好的方法使用 IoC 架構除非您有 MEF 可以同時處理的基本需求。遲早,MEF 最好給 IoC 架構,因為它已內建之中。NET Framework 而不需要採取任何其他的相依性。

MEF 及 「 可延伸應用程式

而 MEF] 可以協助的可延伸應用程式的建築物,最精細的組件的工作設計擴充性的應用程式。這是設計,而且有 MEF、 IoC 或其他技術不大。特別是,您必須找出您想要讓使用外掛? 式的應用程式的哪一個部分。

外掛程式通常都是可見的項目因此需要互動的 UI 的主要應用程式中,新增或擴充功能表、 建立窗格、 顯示對話方塊,或甚至新增或調整主視窗的大小。取決於如何規畫外掛? 式的特定應用程式,與外掛? 式共用資訊的數量可能只是商務資料 (基本上應用程式的目前狀態的區段) 所組成,或參考與容器等等的視覺元素功能表、 工具列和更特定的控制項。您群組這項資訊在資料結構,並在初始設計階段將它傳遞到外掛程式。根據這項資訊,此外掛程式應該要能夠調整它自己的 UI 和實作它自己額外的自訂邏輯。

接下來要外掛? 式的介面。介面取決於您已經識別了主應用程式中的插入點。由 「 資料隱碼點 」 我是說您就從中叫用外掛? 式,讓他們可開始運作並操作有機會的應用程式的程式碼中的位置。

為插入點的範例,請考慮 Windows 檔案總管]。您可能已經知道,Windows 檔案總管] 可讓您擴充其 UI 透過殼層延伸。這些外掛? 式會叫用在非常特定的時間 — 例如,當使用者按一下滑鼠右鍵,顯示所選檔案的內容。為應用程式的架構設計人員,您必須負責找出這些資料隱碼點為單位,而且您想要傳遞至何種資料此時註冊外掛? 式。

一旦已清除每個設計層面,您可以尋找架構,可簡化建立外掛程式的基礎的應用程式的工作。

範例外掛程式的架構應用程式

可以進行更簡單的應用程式這類的"Find 數目",更豐富和功能上吸引人使用外掛? 式。圖 1 示範應用程式的基本 UI。您可以建立個別的專案來定義應用程式的 SDK。它也會讓您定義所有類別和介面實作外掛? 式所需的類別程式庫。圖 2 顯示範例。

圖 1最簡單的範例應用程式

圖 2的 SDK 的應用程式定義

public interface IFindTheNumberPlugin {
  void ShowUserInterface(GuessTheNumberSite site);
  void NumberEntered(Int32 number);
  void GameStarted();
  void GameStopped();
}
public interface IFindTheNumberApi {
  Int32 MostRecentNumber { get; }
  Int32 NumberOfAttempts { get; }
  Boolean IsUserPlaying { get; }
  Int32 CurrentLowerBound { get; }
  Int32 CurrentUpperBound { get; }
  Int32 LowerBound { get; }
  Int32 UpperBound { get; }
  void SetNumber(Int32 number);
}
public class FindTheNumberFormBase : Form, IFindTheNumberApi {
  ...
}

所有外掛? 式都需要實作 IFindTheNumberPlugin 介面。 應用程式主表單會繼承自指定的表單類別,以定義公用的 helper 成員可用來傳遞資訊到外掛? 式的清單。

因為您可能已經猜到從 IFindTheNumberPlugin,當應用程式會顯示其 UI 中,當使用者進行新嘗試猜測數字,以及啟動及停止遊戲,會叫用已註冊的外掛? 式。 GameStarted 和 GameStopped 都只是通知方法,而不需任何輸入。 NumberEntered 是顯示在數使用者只需輸入,並針對新的重試送出通知。 最後,當外掛程式必須顯示在視窗時,會叫用 ShowUserInterface。 在這種情況下,一個站台物件傳遞,如中所定義 圖 3

圖 3 外掛? 式的站台物件

public class FindTheNumberSite {
  private readonly FindTheNumberFormBase _mainForm;
  public FindTheNumberSite(FindTheNumberFormBase form) {
    _mainForm = form;
  }
  public T FindElement<T>(String name) where T:class { ... }
  public void AddElement(Control element) { ... }
  public Int32 Height {
    get { return _mainForm.Height; }
    set { _mainForm.Height = value; }
  }
  public Int32 Width { ... }
  public Int32 NumberOfAttempts { ... }
  public Boolean IsUserPlaying { ... }
  public Int32 LowerBound { ... }
  public Int32 UpperBound { ... }
  public void SetNumber(Int32 number) { ... }
}

站台物件代表外掛程式與主應用程式之間的連絡的點。 外掛程式,則必須取得某些可見性的主應用程式狀態,並甚至必須能夠修改主應用程式的 UI,但是永遠不會獲得知識的主應用程式的內部詳細資料。 這就是為什麼您可能想要建立外掛程式的專案必須參考中繼站台物件 (您的 SDK 組件的一部份)。

我為了簡潔而省略中的大多數方法實作 圖 3,但站台物件的建構函式會接收應用程式的主視窗中,並使用中的 helper 方法的參考 圖 2 (主視窗物件所公開) 它能夠讀取和寫入應用程式的狀態和視覺項目。 例如,高度成員會顯示如何外掛程式可能讀取和寫入主應用程式視窗的高度。

特別是,FindElement 方法可讓外掛程式範例應用程式) 中來擷取特定的視覺項目表單中。 它會假設您 unveil 您 SDK 的一部分如何存取特定容器,例如工具列、 功能表和類似的某些技術詳細資料。 在這種簡單的應用程式,它已假設您的實體控制文識別碼。 下面是 FindElement 的實作:

public T FindElement<T>(String name) where T:class {
  var controls = _mainForm.Controls.Find(name, true);
  if (controls.Length == 0)
    return null;
  var elementRef = controls[0] as T;
  return elementRef ?? null;
}
With the design of the application’s extensibility model completed, we’re now ready to introduce the MEF.

定義匯入的外掛? 式

主應用程式一定會公開列出所有目前已登錄的外掛? 式的屬性。 以下是範例:

public partial class FindTheNumberForm : 
  FindTheNumberFormBase {
  public FindTheNumberForm() {
    InitializeMef();
    ...
 }
 [ImportMany(typeof(IFindTheNumberPlugin)]
 public List<IFindTheNumberPlugin> Plugins { 
    get; set; 
  }
  ...
}

正在初始化 MEF 表示準備撰寫容器指定您想要使用的類別目錄和選擇性匯出提供者。 外掛程式的基礎的應用程式的常見解決方案正在載入外掛? 式,從固定的資料夾。 圖 4 MEF 啟始程式碼顯示在我的範例。

圖 4 正在初始化 MEF

private void InitializeMef() {
  try {
    _pluginCatalog = new DirectoryCatalog(@"\My App\Plugins");
    var filteredCatalog = new FilteredCatalog(_pluginCatalog, 
      cpd => cpd.Metadata.ContainsKey("Level") && 
      !cpd.Metadata["Level"].Equals("Basic")); 
    // Create the CompositionContainer with the parts in the catalog
    _container = new CompositionContainer(filteredCatalog);
    _container.ComposeParts(this);
  }
  catch (CompositionException compositionException) {
    ...
  }
  catch (DirectoryNotFoundException directoryException) { 
    ...
  }
}

您可以使用 DirectoryCatalog 來群組可用外掛? 式,並使用 FilteredCatalog 類別 (這並不在於 MEF,而範例所示的 MEF 文件,在 bit.ly/gf9xDK) 以篩選出某些選取的外掛程式。 特別是,您可以要求所有可載入外掛? 式有指出層級的中繼資料屬性。 遺漏了屬性,外掛程式會被忽略。

ComposeParts 的呼叫有填入應用程式的 [增益集] 屬性的效果。 下一步就叫用外掛? 式從不同的插入點。 應用程式載入為他們提供機會修改的 UI 後第一次叫用外掛? 式就對了:

void FindTheNumberForm_Load(Object sender, EventArgs e) {
  // Set up UI
  UserIsPlaying(false);
  // Stage to invoke plugins
  NotifyPluginsShowInterface();
}
void NotifyPluginsShowInterface() {
  var site = new FindTheNumberSite(this);
  if (Plugins == null)
    return;
  foreach (var p in Plugins) {
    p.ShowUserInterface(site);
  }
}

類似的呼叫將會出現在 「 事件發出信號時使用者剛開始新遊戲、 結束目前遊戲或剛建立的新嘗試猜測神祕的數字的處理常式。

撰寫外掛程式的範例

外掛程式是只是實作您的應用程式擴充性介面的類別。 一次有趣外掛程式中的應用程式的 圖 1 是指到目前為止所做的使用者顯示的嘗試次數。 嘗試的次數追蹤的應用程式的商務邏輯,它會公開給外掛? 式透過站台物件。 外掛程式必須執行的就是準備自己 UI、 繫結至的嘗試次數並將它附加至主視窗。

外掛? 式的範例應用程式會在主視窗的 UI 中建立新的控制項。 圖 5 外掛程式會顯示範例。

圖 5計數器外掛程式

[Export(typeof(IFindTheNumberPlugin))]
[PartMetadata("Level", "Advanced")]
public class AttemptCounterPlugin : IFindTheNumberPlugin {
  private FindTheNumberSite _site;
  private Label _attemptCounterLabel;
  public void ShowUserInterface(FindTheNumberSite site) {
    _site = site;
    var numberToGuessLabelRef = _host.FindElement<Label>("NumberToGuess");
    if (numberToGuessLabelRef == null)
      return;
    // Position of the counter label in the form 
    _attemptCounterLabel = new Label {
      Name = "plugins_AttemptCounter",
      Left = numberToGuessLabelRef.Left,
      Top = numberToGuessLabelRef.Top + 50,
      Font = numberToGuessLabelRef.Font,
      Size = new Size(150, 30),
      BackColor = Color.Yellow,
      Text =  String.Format("{0} attempt(s)", _host.NumberOfAttempts)
    };
    _site.AddElement(_attemptCounterLabel);
  }
  public void NumberEntered(Int32 number = -1) {
    var attempts = _host.NumberOfAttempts;
    _attemptCounterLabel.Text = String.Format("{0} attempt(s)", attempts);
    return;
  }
  public void GameStarted() {
    NumberEntered();
  }
  public void GameStopped() {
  }
}

外掛程式會建立一個新的標籤控制項,並將其放正下方的現有 UI 項目。接下來,每當外掛程式收到通知已輸入新的數字時,此計數器會更新以顯示目前的商務邏輯的狀態的嘗試次數。圖 6 顯示作用中的外掛程式。

圖 6範例應用程式和幾個外掛? 式

插入

在一天結束時,最精細的設計可延伸應用程式工作,且設計主應用程式的外掛? 式的介面。這是純粹的設計工作,並與功能清單以及使用者的需求。

進行實作時,不過,有不少實際任務完成不論外掛程式的介面,例如選取、 載入和驗證外掛? 式。在這方面,MEF 會提供您重要說明在簡化建立来載入時,外掛程式的目錄,並自動載入它們以相同的方式 IoC 架構所做的一樣。

請注意 MEF 是在連續的開發,而且您可以找到最新的位元、 文件和範例程式碼在 mef.codeplex.com.

Dino Esposito 是作者的 < 程式設計 Microsoft ASP。NET MVC"(在 [微軟出版品,2010年) 和與他人合著過"Microsoft。NET:架構的企業應用程式 」 (在 [微軟出版品,2008年)。居住在義大利,Esposito 是在世界各地的產業活動演說。 您可以加入自己的部落格,在 weblogs.asp.net/despos.

感謝至下列技術專家檢閱這份文件: Glenn Block