原文鏈接:http://www.codeproject.com/Articles/560798/ASP-NET-MVC-Controller-Dependency-Injection-for-Be
前言:在這篇文章中,我將通過一個demo,直截了當地說明依賴注入在MVC框架中的使用。
內容列表:
1.介紹
2.為什么使用控制器依賴注入
3.控制器靜態結構
4.自定義控制器
5.Framework中控制器的創建
6.為什么使用控制器工廠模式
7.控制器工廠模式
7.1.目標1
7.2.目標2
8.使用MEF實現控制器工廠模式
9.重點補充
一:介紹

首先簡單地說明控制器在MVC框架中要做的幾件事情
1.接收HTTP請求
2.處理HTTP請求
3.操作客戶端輸入的數據
4.發送回復給客戶端
5.作為Model和View的中轉站

MVC框架在運行時自己創建控制器對象,有一個先決條件,控制器類的構造函數是無參的。如果你想傳遞一個對象作為控制器的參數,這種情況我們又該如何處理?創建這種類型的控制器會失敗,我們需要創建自己的控制器,將控制器參數注入到控制器。
有多種方式可以將參數注入到控制器的構造方法中
1.設置屬性
2.通過方法
3.構造方法
在這篇文章中,我將解釋如何使用控制器注入到MVC中的構造函數中。如果不使用自定義控制器工廠模式,控制器注入是無法實現的。當然我也會解釋如何創建簡單的控制器工廠,然后注冊到MVC框架。我也會展示一種方法注入控制器,使用MEF。
二.為什么使用控制器注入
在現實的程序開發中,你會看到絕大多數的MVC程序需要注入它所依賴的組件。你可以直接創建組件在控制器中,而不需要注入它們。在這種情況下,組件和控制器緊密結合,如果一個組件的擴展發生了改變,或者一個新版本的組件要使用,你就需要改變控制器中的實現(PS:講解為什么使用控制器注入)
當你想使用單元測試的時候,另一種困難你可能會遇到。你不能測試這些控制器在一個獨立的單元。你不能模仿一些新的特性,如果不能模仿,你將不能成功運行你的代碼在一個獨立的環境。
三.控制器靜態結構
MVC框架中的控制器結構是定義在一個叫Controller的抽象類中,如果你想創建一些控制器,首先你需要創建一個類,從抽象類Controller中繼承,UML類圖如下:

所有的控制器都有一個根接口IController,抽象類ControllerBase從它去實現自己的方法。另一個抽象類從ControllerBase中繼承,這個類就是Controller,所有的自定義的控制器類都要從Controller中繼承,或者從它的子類中繼承。
四.簡單的自定義控制器
如果你創建一個MVC工程,你將會得到兩個控制器,AccountController和HomeController


如果你去看HomeController中的代碼實現,你會發現它沒有自己的構造方法。
1 public class HomeController : Controller 2 { 3 public ActionResult Index() 4 { 5 ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application."; 6 return View(); 7 } 8 public ActionResult About() 9 { 10 ViewBag.Message = "Your app description page."; 11 return View(); 12 } 13 }
我們都知道這里沒有一個構造方法,但在編譯時會創建一個無參的構造方法。
1 ublic class HomeController : Controller 2 { 3 public HomeController() 4 { 5 } 6 }
現在我將要創建一個ILogger接口及它的一個實現類DefaultLogger類,Home控制器會使用ILogger類型的對象作為參數,注入到它的控制器構造方法中。
1 public interface ILogger 2 { 3 void Log(string logData); 4 } 5 public class DefaultLogger : ILogger 6 { 7 public void Log(string logData) 8 { 9 System.Diagnostics.Debug.WriteLine(logData, "default"); 10 } 11 }
帶參ILogger的Home控制器構造方法如下:
1 public class HomeController : Controller 2 { 3 private readonly ILogger _logger; 4 public HomeController(ILogger logger) 5 { 6 _logger = logger; 7 } 8 }
直到現在你也沒找到我們在什么地方實例化了DefaultLogger對象,也不知道如何傳遞這個對象到控制器的構造方法中,在編寫代碼階段程序不會報錯,但是在運行代碼會報錯,如下:

看上面的線程記錄,DefaultControllerActivator對象會拋出一個異常MissingMethonException。如果你到MSDN,找這個異常是如何引發的,你會發現找到不到適當的方法。看接下來的異常InvalidOperationException,它確實包含了MissingMethodException,將下來你會看到更加有用的信息,確保在控制器構造方法中有一個帶參數的構造方法。如果想讓代碼工作正常,我必需要加帶一個參數的構造方法,框架會創建控制器對象通過我們創建的那個構造方法。問題在於,我們如何傳遞一個一個DefaultLogger對象到這個控制器。請繼續你好練習,我們接着往下看。
五.MVC框架是如何創建控制器對象
在我們開始注入DefaultLogger對象到HomeController之前,我們要有一個概念,MVC框架是如何創建一個控制器對象的。IControllerFactory接口的主要責任就是創建控制器對象。DefaultControllerFactory是框架默認提供的可擴展的類。如果你添加一個無參的構造方法,然后設置一個斷點,你將會發現程序在這一刻,將停留在這里。

看上面的圖片,你可以看到IControllerFactory類型的一個DefaultControllerFactory對象,DefaultControllerFactory有一些方法,如:Create,GetControllerInstance,CreateController,這些方法將會創建一個HomeController對象,MVC框架是開源的,如果你想知道更多的方法,可以下載官方的資源,自己去閱讀。你看調試的代碼,你可以看到DefaultControllerFactory對象被CurrentControllerFactory

六.為什么要自定義控制器工廠
現在我們知道默認的控制器工廠使用一個無參的構造方法創建一個控制器對象。我們可以注入自己的帶參的控制器構造方法。
1 public class HomeController : Controller 2 { 3 private readonly ILogger _logger; 4 public HomeController():this(new DefaultLogger()) 5 { 6 } 7 public HomeController(ILogger logger) 8 { 9 _logger = logger; 10 } 11 }
我發現許多的開發者都對上面依賴注入有所誤解,它不是一個依賴注入的形式。這個確實違反了組件的原則。而這個原則的願意是:上層的模塊不能依賴於低層級的模塊,雙方都要依賴於抽象層,細節要在體現在抽象層。在上面的代碼中,HomeController創建了自己的DefaultLogger對象。它直接依賴於ILogger接口的擴展(DefaultLogger),如果在將來一個新的擴展(擴展了ILogger接口),我們需要修改我們HomeController中的方法,所以我們需要使用適當的方法去注入我們的組件。我們要使用一個帶參的構造方法去注入我們的ILogger 組件,但是默認的 DefaultControllerFactory不支持我們這么做,所以我們要創建自己的控制器工廠。
七.自定義控制器工廠
我使用兩種方法展示如何創建自己的控制器工廠。
7.1途徑1
我們可以創建一個新的控制器工廠,擴展了IControllerFactory接口,假定我們自定義的控制器工廠的名稱叫CustomControllerFactory,如下
1 public class CustomControllerFactory : IControllerFactory 2 { 3 public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName) 4 { 5 ILogger logger = new DefaultLogger(); 6 var controller = new HomeController(logger); 7 return controller; 8 } 9 public System.Web.SessionState.SessionStateBehavior GetControllerSessionBehavior( 10 System.Web.Routing.RequestContext requestContext, string controllerName) 11 { 12 return SessionStateBehavior.Default; 13 } 14 public void ReleaseController(IController controller) 15 { 16 IDisposable disposable = controller as IDisposable; 17 if (disposable != null) 18 disposable.Dispose(); 19 } 20 }
現在第一步,我們需要將CustomControllerFactory注冊到MVC框架,完成這件事要在Application_Start事件中書寫代碼。
1 public class MvcApplication : System.Web.HttpApplication 2 { 3 protected void Application_Start() 4 { 5 RegisterCustomControllerFactory (); 6 } 7 } 8 private void RegisterCustomControllerFactory () 9 { 10 IControllerFactory factory = new CustomControllerFactory(); 11 ControllerBuilder.Current.SetControllerFactory(factory); 12 }
如果你運行你的程序,你會發現那個無參的構造方法沒有被執行,那個帶參的構造方法執行了。你的問題就這么簡單的解決了。

你可以構建你的控制器工廠使用反射機制。
1 public class CustomControllerFactory : IControllerFactory 2 { 3 private readonly string _controllerNamespace; 4 public CustomControllerFactory(string controllerNamespace) 5 { 6 _controllerNamespace = controllerNamespace; 7 } 8 public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName) 9 { 10 ILogger logger = new DefaultLogger(); 11 Type controllerType = Type.GetType(string.Concat(_controllerNamespace, ".", controllerName, "Controller")); 12 IController controller = Activator.CreateInstance(controllerType, new[] { logger }) as Controller; 13 return controller; 14 } 15 }
7.2途徑2
這里方法不是去擴展IControllerFactory接口,而是去繼承DefaultControllerFactory類,通過修改其中的方法。當然也要將控制器工廠注入到程序啟動的事件中。代碼如下:
1 public class CustomControllerFactory : DefaultControllerFactory 2 { 3 protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType) 4 { 5 ILogger logger = new DefaultLogger(); 6 IController controller = Activator.CreateInstance(controllerType, new[] { logger }) as Controller; 7 return controller; 8 } 9 }
(去掉了MEF創建自定義控制器工廠的方法,因為自己也實在不能理解,但是要看啊)。
