2

我正在編寫WPF MEF應用程序。在過去,我已經寫了IoC的WPF應用程序和結構使用PRISM的代碼模塊,像這樣:共享MEF部件/導出模塊之間的可重用性

  • 殼牌 - 主要可執行
  • BusinessArea.Module.Interface - 包含服務的所有接口,的ViewModels和BusinessArea模塊的視圖。
  • BusinessArea.Module - 包含PRISM IModule的實現並實現了 BusinessArea.Module.Interface項目中的接口。
  • OtherBusinessArea.Module.Interface - 另一個模塊
  • OtherBusinessArea.Module - 另一個模塊接口

在世界的IoC當加載時每個模塊註冊其與IoC容器組件。如果我想讓我的兩個模塊重用彼此的組件,他們只需引用我的一個接口項目並注入組件。

然而,在MEF,我真的不能找到任何好的慣例或準則分離部分成模塊,並對於這種類型的組件模塊間的重用。我有五個問題:

  1. 我應該繼續爲我的所有零件創建接口嗎?
  2. 我應該有一個跨應用程序或共享容器每個模塊一個(每個模塊是一種從Windows 8風格的開始菜單中啓動單獨的應用程序)。
  3. 如果一個模塊想要在另一個模塊中使用零件,我該如何維護分離。
  4. 如果一個模塊想要在另一個容器中使用零件,我該如何維護分離。
  5. 如何在具有大量模塊的應用程序中最大限度地保持性能。
+1

如果我也許再使用基本IBusinessAreaModule導入業務模塊的炮彈?我正在對一個有幾個業務領域的大型Delphi應用程序進行增量重寫。我們的策略是首先複製每個業務領域的最低限度,然後慢慢添加到其中。 – ProfK

回答

7
  1. 是的,你應該。如果將模塊導出爲它們自己的實現類型而不是接口,則導入模塊的消費模塊需要引用包含模塊實現的庫。而使用IoC的主要原因之一就是避免這一點。

  2. 是的,你應該有一個容器。如果你的模塊容納了容器,你將無法使用它導出/導入該模塊,因爲你需要在容器存在之前擁有該模塊的一個實例。這裏沒有真正的MEF特定問題,這與Unity或其他任何東西都是一樣的。對於您的PRISM應用程序,這個想法是分開關注在一個地方實例化和連接模塊,這是容器。容器是在Bootstrapper的其他任何東西之前創建的,然後創建shell,模塊,服務以及任何你需要的東西。在您的應用程序中使用其他IoC容器是非常有意義的,它可以在完全不同的上下文中管理對象的實例化和引用,比如說不是針對您的UI,而是將複雜的業務對象連接在一起。而且,可能是有意義的,以便在內部(私人)容器中使用MEF構建自己的模塊,其中主容器不知道。比你有一個複合用戶界面與模塊本身複合用戶界面。如果你確實需要的話,可以徹底想一想。你很容易遇到這種東西的問題,例如加載組件兩次,等等。等等。

  3. 和以前一樣。模塊B引用ModuleA的接口項目,然後導入IModuleA類型的字段或參數。容器將解析相關性以注入ModuleA。

  4. 如前所述,您應該真正讓自己的架構更直觀。如果你想注入模塊之間的依賴關係,它們應該在同一個容器中。這就是IoC的想法。

  5. 我正在處理一個具有多個IoC容器的複雜應用程序。我使用MEF作爲用戶界面,這是外殼和一些UI模塊。對於更多的業務邏輯相關的東西,我使用AutoFac IoC容器。主要是因爲Autofac是一個'真正的'IoC容器,而MEF不是,而且因爲速度更快。 Autofac可以做任何MEF都可以做的事情,下次我將使用Autofac而不是MEF用於UI。

地塊的questionsfor一個問題....

這裏的答案是我給了一個類似的問題在不久前,它希望可以幫助您太:

我只能解釋系統原則上在這裏,但它可能會指出你在正確的方向。總是有許多方法的一切,但是這是我的理解是最好的做法,我有取得了很好的經驗:

自舉

與棱鏡和團結,這一切都與開始Bootstrapper,源自MefBootstrapper,在Microsoft.Practices.Prism.MefExtensions。引導程序設置MEF容器,從而導入所有類型,包括服務,視圖,視圖模型和模型。

導出視圖(模塊)

這是MatthiasG指的部分。我的做法是,GUI模塊的結構如下:

  • 模型出口本身作爲其具體類型(可以是一個接口也一樣,見MatthiasG),採用[Export(typeof(MyModel)]屬性。用[PartCreationPolicy(CreationPolicy.Shared)]標記以表示僅創建一個實例(單例行爲)。

  • 視圖模型出口本身作爲其具體類型就像模型和通過構造噴射進口型號:

    [ImportingConstructor] 公共類MyViewModel(爲MyModel模型) { _model =模型; }

  • 的閱覽通過構造函數注入進口視圖模型,以同樣的方式視圖模型導入模型

  • 而現在,這一點很重要:視圖出口本身具有特定屬性的,它是從派生'standard'[Export]屬性。下面是一個例子:

    [ViewExport(RegionName = RegionNames.DataStorageRegion)] 公共部分類DataStorageView { [ImportingConstructor] 公共DataStorageView(DataStorageViewModel視圖模型) { 的InitializeComponent(); DataContext = viewModel; }}

的[ViewExport]屬性

[ViewExport]屬性做了兩兩件事:因爲它從[Export]屬性派生,它告訴MEF容器導入視圖。作爲什麼?這是隱藏在它的確定指標:構造函數簽名如下所示:

public ViewExportAttribute() : base(typeof(UserControl)) {} 

通過調用[Export]類型的UserControl構造,每個視圖被註冊爲MEF容器UserControl

其次,它定義了一個屬性RegionName,稍後將用它來決定你的Shell UI的哪個區域應該被插入。 RegionName屬性是接口IViewRegionRegistration的唯一成員。屬性類:

/// <summary> 
/// Marks a UserControl for exporting it to a region with a specified name 
/// </summary> 
[Export(typeof(IViewRegionRegistration))] 
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] 
[MetadataAttribute] 
public sealed class ViewExportAttribute : ExportAttribute, IViewRegionRegistration 
{ 
    public ViewExportAttribute() : base(typeof(UserControl)) {} 

    /// <summary> 
    /// Name of the region to export the View to 
    /// </summary> 
    public string RegionName { get; set; } 
} 

導入瀏覽

現在,該系統的最後一個關鍵部分是行爲,你重視你的shell的地區:AutoPopulateExportedViews行爲。這樣可以導入所有的模塊從MEF容器與這條線:

[ImportMany] 
private Lazy<UserControl, IViewRegionRegistration>[] _registeredViews; 

這將導入從容器註冊爲UserControl所有類型的,如果他們有一個元數據屬性,它實現IViewRegionRegistration。由於您的[ViewExport]屬性具有此功能,因此這意味着您導入標有[ViewExport(...)]的每種類型。

的最後一步是堵塞查看到的區域,其中bahvior確實在它的OnAttach()屬性:

/// <summary> 
/// A behavior to add Views to specified regions, if the View has been exported (MEF) and provides metadata 
/// of the type IViewRegionRegistration. 
/// </summary> 
[Export(typeof(AutoPopulateExportedViewsBehavior))] 
[PartCreationPolicy(CreationPolicy.NonShared)] 
public class AutoPopulateExportedViewsBehavior : RegionBehavior, IPartImportsSatisfiedNotification 
{ 
    protected override void OnAttach() 
    { 
     AddRegisteredViews(); 
    } 

    public void OnImportsSatisfied() 
    { 
     AddRegisteredViews(); 
    } 

    /// <summary> 
    /// Add View to region if requirements are met 
    /// </summary> 
    private void AddRegisteredViews() 
    { 
     if (Region == null) return; 

     foreach (var view in _registeredViews 
      .Where(v => v.Metadata.RegionName == Region.Name) 
      .Select(v => v.Value) 
      .Where(v => !Region.Views.Contains(v))) 
      Region.Add(view); 

    } 

    [ImportMany()] 
    private Lazy<UserControl, IViewRegionRegistration>[] _registeredViews; 
} 

通知.Where(v => v.Metadata.RegionName == Region.Name)。這將使用該屬性的RegionName屬性來僅獲取爲特定區域導出的那些視圖,您將該行爲附加到。

行爲被附加到引導程序你的shell的地區:

保護覆蓋IRegionBehaviorFactory ConfigureDefaultRegionBehaviors(){ ViewModelInjectionBehavior.RegionsToAttachTo.Add(RegionNames.ElementViewRegion);

var behaviorFactory = base.ConfigureDefaultRegionBehaviors(); 
behaviorFactory.AddIfMissing("AutoPopulateExportedViewsBehavior", typeof(AutoPopulateExportedViewsBehavior)); 

}

我們兜了一圈,我希望,這可以讓你的事情是如何陷入與MEF和PRISM地方的想法。

而且,如果你仍然不無聊:這是完美的:

Mike Taulty's screencast

+1

這個答案值得更多的投票只是爲了它的全面性和程度。 – ProfK

+1

@ProfK謝謝,夥計! – Marc