1. 程式人生 > >MVC實用架構設計:使用MEF應用IOC(依賴倒置)(1)

MVC實用架構設計:使用MEF應用IOC(依賴倒置)(1)

  原文地址:http://developer.51cto.com/art/201309/409351.htm

面向介面程式設計,Controller應該只依賴於站點業務層的介面,而不能依賴於具體的實現,否則,就違背了在層之間設定介面的初衷了。

另外,如果上層只依賴於下層的介面,在做單元測試的時候,就可以用MoqFakes等Mock工具來按實際需求來模擬介面的實現,就可以靈活的控制介面的返回值來對各種情況進行測試,如果依賴於具體的實現,專案的可測試性將大大減小,不利於進行自動化的單元測試。

要不依賴於具體的實現,就不能使用通常的 T t = new T() 的方式來獲得一個類的例項了,需要通過IOC容器來對物件生命週期,依賴關係等進行統一的管理。

二、MEF的優勢

.net中可用的IOC容器非常多,如 CastleWindsor,Unity,Autofac,ObjectBuilder,StructureMap,Spring.Net等,這些第三方工具各不相同,但功能大體都相同,大都需要事先對介面與實現進行配對(通過程式碼或配置檔案),然後由系統自動或手動來通過介面來獲得相應實現類的例項,物件例項化的工作由IOC容器自動完成。

MEF相對於上面的這些IOC容器有什麼優勢呢?下面是我推薦的理由:

  1. .net4.0 自帶:MEF的功能在 System.ComponentModel.Composition.dll 程式集中,直接引用即可使用,不用安裝第三方元件
  2. 0 配置:MEF是不需要使用配置檔案或程式碼對介面與實現進行一一配對的,只需要簡單的使用幾個Attribute特性,就能自動完成源與目標的配對工作
  3. 自動化:系統初始化時自動遍歷程式目錄或指定資料夾下的dll,根據程式集中介面與類的特定Attribute特性進行自動配對。

三、MEF在桌面程式中的使用

在桌面程式中,需要完成兩個部分的目錄匹配,一個是dll中的匹配,另一個為exe程式集中的匹配,分別使用到 DirectoryCatalog與AssemblyCatalog兩個目錄類。而兩個目錄類需加入到 AggregateCatalog 目錄類中,才能參與組合容器CompositionContainer的初始化。

在服務提供方的實現類中,使用 ExportAttribute 標記要與之匹配的介面,如下圖所示。在服務呼叫方,使用 ImportAttribute 來給介面注入實現類的例項,如上圖所示。

由於呼叫方法為靜態的方法,Program類的例項仍需手動從元件容器中獲得,然後嘗試登入:

輸出結果,介面AccountContract並沒有賦值,但能輸出其實現類的資訊,同時登入也能成功呼叫:

四、MEF在MVC中的使用

在MVC的專案中,IOC元件是通過 DependencyResolver類中的 SetResolver(IDependencyResolver resolver) 方法來向MVC提供註冊點的,所以我們只需要實現一個 IDependencyResolver 介面的MEF實現類,即可完成MEF在MVC中的註冊工作。

另外考慮到Web應用程式的無狀態性,即每次訪問都是獨立進行的,所以IOC元件產生的物件例項也必須唯一,否則不同使用者的操作就可能串線,產生相互干擾。在這裡,我們使用HttpContext.Current.Items集合來儲存 組合容器CompositionContainer的例項,以使每個使用者的資料保持獨立,並且同一使用者的同一個Http請求中使用同一物件例項。另外考慮到可能會有某種情況下需要手動獲取組合容器中的例項,把組合容器快取到了當前上下文中的Application中。

MefDependencySolver實現程式碼如下: 

  1. /// <summary>
  2. /// MEF依賴關係解析類
  3. /// </summary>
  4. publicclass MefDependencySolver : IDependencyResolver  
  5.     {  
  6. private readonly ComposablePartCatalog _catalog;  
  7. privateconst string HttpContextKey = "MefContainerKey";  
  8. public MefDependencySolver(ComposablePartCatalog catalog)  
  9.         {  
  10.             _catalog = catalog;  
  11.         }  
  12. public CompositionContainer Container  
  13.         {  
  14.             get  
  15.             {  
  16. if (!HttpContext.Current.Items.Contains(HttpContextKey))  
  17.                 {  
  18.                     HttpContext.Current.Items.Add(HttpContextKey, new CompositionContainer(_catalog));  
  19.                 }  
  20.                 CompositionContainer container = (CompositionContainer)HttpContext.Current.Items[HttpContextKey];  
  21.                 HttpContext.Current.Application["Container"] = container;  
  22. return container;  
  23.             }  
  24.         }  
  25.         #region IDependencyResolver Members
  26. public object GetService(Type serviceType)  
  27.         {  
  28.             string contractName = AttributedModelServices.GetContractName(serviceType);  
  29. return Container.GetExportedValueOrDefault<object>(contractName);  
  30.         }  
  31. public IEnumerable<object> GetServices(Type serviceType)  
  32.         {  
  33. return Container.GetExportedValues<object>(serviceType.FullName);  
  34.         }  
  35.         #endregion
  36.     } 

在Global.asax.cs的Application_Start方法中初始化MEF容器,由於Web應用程式中只需要在DLL中查詢匹配,所以只使用DirectoryCatalog即可。

在AccountController類中加入MEF的Attribute標籤,並刪除原來建構函式中的AccountContract屬性的賦值程式碼

在加入了IOC元件之後,我們的架構就變成了面向介面程式設計的架構了,上層程式碼僅依賴於下層的介面,而不依賴於下層的具體實現,為了防止站點業務層的實現程式碼中出現如下所示的程式碼:

  1. IAccountSiteContract accountContract = new AccountSiteService(); 

上一篇文章中也提到過,需要把站點業務層中的實現類的可訪問性由 public 修改為 Internal,以實現上層程式碼對下層程式碼的真正隔離。

其他程式碼不變,執行網站,同樣能正常呼叫登入功能

最後,給個溫馨提示:

MEF的匯出匯入是整體關聯的,只要樹中某一個部件匹配失敗,整個樹將無法例項化,也就是會出現Import的屬性值為null的情況,這種情況下,可以使用MEF開發團隊提供的除錯工具MEFX來進行問題的快速定位。具體使用方法參考如下: