MVC Controller Dependency Injection for Beginners【翻譯】

  在codeproject看到一篇文章,羣裏的一個朋友要幫忙我翻譯一下順便貼出來,這篇文章適合新手,也算是對MEF的一個簡單用法的介紹。設計模式

Introduction   架構

In a simple statement if I want to define an ASP.NET MVC controller then I can say that classes that are responsible for receiving and processing incoming http requests, handling client input, and sending response back to the client. Controllers also act as a coordinator between Model (Business) and View (Presentation). ASP.NET MVC framework itself creates controller objects at run time. There is only one prerequisite, that is controller class must have a parameter less constructor. But if you need to pass some objects with constructor then what will happen? Simply framework will fail to create controller object. In that case we need to create controller objects by our self and inject dependency there.app

There are many ways you can inject dependency to a class. For example, it might be Property setter,MethodConstructor injection. In this article I will explain how to inject controller dependency to ASP.NET MVC framework with the help of constructor. Without creating custom controller factory inject dependency to controllers are not possible. So I will also explain how to create a very simple custom controller factory and register it to ASP.NET MVC framework.  I will also demonstrate a way to inject dependency to controllers using Managed Extensible Framework (MEF). 框架

介紹   

若是我想用一個簡單的聲明去定義一個ASP.NET MVC 控制器。而後我會說這個類是負責接收和處理傳入的http請求,操做客戶端輸入,並將響應發送回客戶端。Controllers 還能夠扮演Model(業務層)和View(表現層)之間的中間人。ASP.NET MVC框架自己在運行時會建立控制器對象。可是有一個前提,那就是控制器類至少必須有一個參數的構造函數。可是,若是你須要傳遞一些對象給構造函數,那麼會發生什麼呢?簡單的框架,將沒法建立控制器對象。在這種狀況下,咱們須要建立控制器實現自我依賴注入。less

有不少方法,你能夠注入依賴一類。例如,它多是物業二傳手,方法,  構造函數注入。在這篇文章中,我將解釋如何注入控制器的依賴ASP.NET MVC框架的構造函數的幫助。沒有建立自定義控制器工廠注入依賴控制器是不可能的。因此,我也將解釋如何建立一個很是簡單的自定義控制器工廠,並把它註冊到ASP.NET MVC框架。我還將展現一種方式來注入依賴使用託管擴展框架(MEF的控制器。 ide

 

這裏有不少方法能夠向類中進行依賴注入。例如,它多是屬性setter方法、構造函數注射。在本文中我將解釋向ASP.NETMVC的控制器的使用構造函數輔助進行依賴注入。淨MVC框架構造函數的幫助。沒有建立自定義控制器工廠注入依賴控制器是不可能的。因此我也會演示如何建立一個很是簡單的自定義控制器廠,並把它註冊到ASP.NET MVC框架, 我還將演示一種使用託管擴展的框架(MEF)的依賴注入控制器方式。函數

Why Need to Inject Controller Dependency? 

In real life application development, you will see almost all ASP.NET MVC applications are needed to inject its dependent component. You can create component directly inside the controller instead of inject them. In that case the controller will be strongly coupled on those components. If any component's implementation is changed or new version of that component is released then you must change controller class itself. Another problem you will face when you will do unit test. You cannot do unit test of those controllers independently (within isolation). You cannot take mocking features from unit testing framework. Without mocking, you cannot do unit test of your code in isolated environment.  post

在現實生活中的應用程序開發,你會看到,幾乎全部的ASP.NET MVC應用程序都須要注入其依賴的組件。您能夠在控制器內部創直接建組件對象代替注入。在這種狀況下,控制器將被強耦合到這些組件。若是任何組件的實現變動或新版本組件被髮布,那麼,你必須修改控制器類。當你要作單元測試時,您將面臨的另外一個問題。 你不能單獨對這些控制器類作單元測試。你不能經過單元測試框架實現功能的模擬。沒有模擬,你就不能在隔離的環境中作單元測試的代碼。單元測試

Controller Static Structure  

ASP.NET MVC framework’s controller structure is defined inside an abstract class named Controller. If you want to create any controller, first you will create a class which must be inherited from abstract class Controller.  The UML class diagram of controller class and its hierarchy looks: 學習

控制器靜態結構  

ASP.NET MVC框架的控制器結構被定義在名爲Controller的抽象類若是你想建立任何控制器,首先您必須必須繼承自抽象的Controller類  Controller類的層次結構的UML類圖以下:

1

All controllers root interface is IController interface. Its abstract implementation is ControllerBase class. Another abstract class is inherited from ControllerBase class and name of that class is Controller. All our custom controller classes should be inherited from that Controller abstract class or its any child class. 

全部控制器的根接口 IController其抽象實現類是ControllerBase類。另外一個名爲Controller的抽象類繼承自ControllerBase咱們全部的自定義控制器類都應繼承自Controller抽象類或者它的任何子類。 

Simple Custom Controller Definition   

 

If you create an ASP.NET MVC project, you will get two default controllers. One is AccountController and another is HomeController

簡單的自定義控制器定義   

若是你建立一個ASP.NET MVC項目時,你會獲得兩個默認控制器。一個是AccountController另外一個是 HomeController。 

2


If you look at the HomeController class definition, then you will find that the class has no constructor. 

若是你查看HomeController中類定義,那麼你會發現,這個類沒有構造函數。 

 

public class HomeController : Controller
{
    public ActionResult Index()
    {
        ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
        return View();
    }
    public ActionResult About()
    {
        ViewBag.Message = "Your app description page.";
        return View();
    }
 

} 
 
We all know that if there is no constructor defined then at compile time .NET Framework creates a default parameter-less constructor. 
咱們都知道,若是沒有構造函數的定義,那麼在編譯的時候,NET Framework會建立一個默認的無參數的構造函數。 
public class HomeController : Controller
{
    public HomeController()
    {
    }
} 
Now I will create ILogger interface and its implementation DefaultLogger class. Home controller class will use that ILogger object. I will inject it throw constructor.   
如今我將建立一個ILogger接口及其實現類defaultlogger。HomeController類將使用ILogger對象。我會把它注入構造函數。
public interface ILogger
{
    void Log(string logData);
}
public class DefaultLogger : ILogger
{
    public void Log(string logData)
    {
        System.Diagnostics.Debug.WriteLine(logData, "default");
    }
}
HomeController with ILogger constructor injection looks: 
HomeController的用的ILogger注入構造函數以下:
public class HomeController : Controller
{
    private readonly ILogger _logger;
    public HomeController(ILogger logger)
    {
        _logger = logger;
    }
}

 

Still I do not find any place where I can create DefaultLogger object in my codebase and though I am not creatingHomeController object by myself so I do not find the place where I can create DefaultLogger object and how I can pass it to the HomeController with defined parameterized HomeController (ILogger logger) constructor. In that state if I build my project, it will build without any errors. But in run time it will throw exception. Exception details in error page looks: 

我仍然沒有找到任何地方,在那裏我能夠在個人代碼庫建立DefaultLogger對象,我不是本身建立 HomeController的對象,因此我找不到 建立 DefaultLogger對象 的地方以及如何傳遞 HomeController 參數給 HomeController( ILogger logger )構造函數。若是我生成這個項目,那麼,它確定不會出錯。但在運行時將引起異常。 異常詳細信息的錯誤頁面將會顯示以下:
4
See the stack trace above, object of class  DefaultContollerActivator   throw exception named  MissingMethodException . If you go MSDN, search the exception which is raised, you will find it there and there clearly mentions  「The exception that is thrown when there is an attempt to dynamically access a method that does not exist.」  That is the inner exception message. If see next  exception named  InvalidOperationException , it is actually wrapped  MissingMethodException  and there you will find  more user friendly message and that is  「Make sure that the controller has a parameterless public constructor.」  If I want to make   HomeController  workable then I must add a parameter less constructor and framework will create controller object with the help of that constructor. Question will rise how I can pass  DefaultLogger  object to that controller? Please keep your patience.   
查看堆棧跟蹤以上,DefaultContollerActivator 對象  拋出 名爲MissingMethodException異常  。若是你去MSDN,搜索是誰引起的異常,你會發現它有明確的指出「有一個試圖動態訪問的方法時不存在 引起的異常 。」這是內部異常消息。若是看到一個異常名爲  InvalidOperationException異常,它其實是包含MissingMethodException,而且你能夠找到更多的友好的提示消息,那就是「確保該控制器具備一個無參數的公共構造函數。」若是我要讓  HomeController的可行的話,我必須添加一個無參數的 構造函數 在框架的幫助下 建立控制器對象 。問題是,我怎麼向控制器傳入   DefaultLogger對象 ?請保持您的耐心。   

How MVC Framework Create Controllers? 

Before start to describe dependency injection process of DefaultLogger object to the HomeController, we should have one clear picture and that is how to create controller object by MVC framework? IControllerFactory interface is responsible for creating controller object. DefaultControllerFactory is its default framework provided implementation class. If you add a parameter less constructor to the HomeController class and set break point to that and run application with debug mode, you will find that the code execution process is hold on to that breakpoint. 

MVC框架如何建立控制器? 

開始前描述HomeController的DefaultLogger對象的依賴注入過程中,咱們應該有一個清晰的畫面,那就是MVC框架建立controller對象?IControllerFactory接口負責建立控制器對象。 DefaultControllerFactory是其默認框架提供的實現類。若是沒有數的構造函數 HomeController的類,並設置斷點,調試模式下運行應用程序,代碼執行過程當中,斷點停留時你會發現。


If you look at the bellow image, you can see that IControllerFactory type variable namedfactory contains DefaultControllerFactory object. DefaultControllerFactory has various methods likeCreateGetControllerInstance, CreateController those are playing vital role for creating HomeControllerobject. You know that ASP.NET MVC framework is open source project, so if you want to know more details about those methods and their behavior then you can download its source code and see the implementation. ASP.NET MVC implements abstract factory design pattern for creating controller class. If you debug code and see the value with quick watch then you can see that DefaultControllerFactory is automatically created asCurrentControllerFactory (IControllerFactory). 

 

若是你看看波紋圖像,你能夠看到,  IControllerFactory  類型工廠命名的變量包含 DefaultControllerFactory 對象。  DefaultControllerFactory 有Create GetControllerInstance CreateController  等方法爲創造 HomeController的對象發揮重要做用。你知道,ASP.NET MVC框架是一個開源項目,因此若是你想了解更多的細節,那麼你能夠下載它的源代碼,看看這些方法和他們的行爲的實現。ASP.NET MVC爲建立控制器類實現抽象工廠設計模式。若是調試代碼你會很快發現, DefaultControllerFactory會自動建立爲CurrentControllerFactory  (IControllerFactory)。 

Why Need Custom Controller Factory? 

Already you know that Default controller factory creates controller object using parameter less constructor.  We can inject our controller by parameterless constructor too. See the code below: 

爲何須要自定義控制器工廠? 

你已經知道,默認控制器工廠建立控制器對象使用無參構造函數。咱們可的含參的控制器構造函數實現注入見下面的代碼: 

public class HomeController : Controller
{
    private readonly ILogger _logger;
    public HomeController():this(new DefaultLogger())
    {
    } 
    public HomeController(ILogger logger)
    {
        _logger = logger;
    }
}
I found many developers who are misguided the way of the above dependency injection process. This is not dependency injection at all. This actually clearly violate the dependency inversion principle. The principle say that "High level module should not depend upon the low level module, both should depend on abstraction. Details should depends upon abstraction". In above code HomeController itself create DefaultLogger object. So it directly depends on ILogger implementation (DefaultLogger). If in future another implementation comes of ILogger interface then HomeController code itself need to change. So it is probed that it is not proper way to do. So if we need proper way to inject dependency. We need parameterised constructor, by which we can inject our ILogger component. So current implementation of DefaultControllerFactory does not support this requirement. So we need a new custom controller factory.  
 
我發現許多開發者的錯誤就是上述依賴注入過程的方式。這根本不是依賴注入。實際上,這顯然違反了依賴倒置原則。原則上說,「 高級別不該依賴於低級別的模塊模塊,都應該依賴於抽象。細節上都要取決於抽象「。在上面的代碼中的HomeController自己建立DefaultLogger對象。所以,它直接取決於ILogger實現(DefaultLogger)。若是在將來有 ILogger接口 的另外一種實現 ,那麼HomeController的代碼自己須要改變。因此 這種嘗試是不正確的方式 所以,若是咱們須要適當的方式進行注入依賴。咱們須要含參構造函數,咱們能夠注入ILogger組件。所以,當前執行的  DefaultControllerFactory  不支持這一要求。所以,咱們須要一個新的自定義控制器工廠。

建立自定義控制器廠  

經過實現IControllerFactory接口,咱們能夠建立一個新的自定義控制器工廠 假設咱們的新的控制器廠名爲CustomControllerFactory的所以,它的實現應該是這樣:

public class CustomControllerFactory : IControllerFactory
{
    public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
    {
        ILogger logger = new DefaultLogger();
        var controller = new HomeController(logger);
        return controller;
    }
    public System.Web.SessionState.SessionStateBehavior GetControllerSessionBehavior(
       System.Web.Routing.RequestContext requestContext, string controllerName)
    {
        return SessionStateBehavior.Default;
    }
    public void ReleaseController(IController controller)
    {
        IDisposable disposable = controller as IDisposable;
        if (disposable != null)
            disposable.Dispose();
    }
} 

 

Now at first you need to register CustomControllerFactory to MVC framework. We can do it insideApplication_Start event.  

 如今首先你須要在Application_Start方法中向MVC框架註冊CustomControllerFactory。  

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
    RegisterCustomControllerFactory ();
    }
}
private void RegisterCustomControllerFactory ()
{
    IControllerFactory factory = new CustomControllerFactory();
    ControllerBuilder.Current.SetControllerFactory(factory);
} 

 

If you run the application and see that your parameter-less constructor HomeController is not called, instead of that parameterized HomeController(ILogger logger) constructor is called by MVC framework. Wow! your problem is solved so easily. 
 
若是你運行應用程序會看到無參的HomeController構造函數並沒有被調用,含參的HomeController的(ILogger logger)  構造函數被 MVC 框架 調用 哇!你的問題很容易就解決了。
 

You can create your controller creation with little generic way using reflection. 
您可使用反射建立控制器泛型方法
public  class  CustomControllerFactory : IControllerFactory
{
    private readonly string _controllerNamespace;
    public CustomControllerFactory(string controllerNamespace)
    {
        _controllerNamespace = controllerNamespace;
    }
    public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
    {
        ILogger logger = new DefaultLogger();
        Type controllerType = Type.GetType(string.Concat(_controllerNamespace, ".", controllerName, "Controller"));
        IController controller = Activator.CreateInstance(controllerType, new[] { logger }) as Controller;
        return controller;
    }
} 

first you need to create controller type object from controller full name. Then you create controller object at run time using Type.GetType method and inject dependent object through reflection. In current code implementation has some problems, which are:

 

  1. You need to pass controller namespace (using constructor) so every controller should be same namespace and
  2. All controllers need single parameterized construction which accept ILogger object only. Without that it will throw MissingMethodException

首先,您須要從控制器的全名建立控制器類型的對象。而後在運行時使用 Type.GetType方法建立控制器對象,並經過反射注入依賴對象。在當前的代碼執行有一些問題,這是:

 

  1. 你須要經過控制器命名空間(使用構造),因此每一個控制器都應該是相同的命名空間。
  2. 全部控制器都須要單一參數的構造用以只接受 ILogger 對象。不然,它會拋出MissingMethodException
 

Another Approach 

 

Another way you can create your own controller factory. Though MVC framework’s default implementation ofIControllerFactory is DefaultControllerFactory and it is not a sealed class. So you can extend that class and override virtual methods and changed/implement whatever you need. One implementation example might be as follows:  

另外一種方法 

 

另外一種方法,你能夠建立本身的控制器工廠。雖然MVC框架的默認實現 IControllerFactory的是DefaultControllerFactory,但它是否是一個密封類。因此,你能夠擴展該類並重寫虛方法,改變/實現任何你須要的。一中實現示例可能以下:  

 
public class CustomControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
    {
        ILogger logger = new DefaultLogger();
        IController controller = Activator.CreateInstance(controllerType, new[] { logger }) as Controller;
        return controller;
    }
} 
Just create my own CustomControllerFactory class which is inherited from DefaultControllerFactory and override GetControllerInstance method and implement my custom logic.  
 
只要繼承自 DefaultControllerFactory和重寫 GetControllerInstance方法實現自定義邏輯就能夠建立 本身的 CustomControllerFactory類

MEF for Creating Custom Controller Factory 

In real life project you will see experts are using IOC containers inside controller factory for creating/fetching theController object. Why because many problems need to raise and handle if you try to dynamically create dependent object and controller object. So it will be better approach to use any IOC container to your project and register all your dependent objects there and use it. You can use various popular IOC containers like Castle Windsor, Unity, NInject, StructureMap etc. I will demonstrate how Managed Extensibility Framework (MEF) is used in this situation. MEF is not an IOC container. It is a composition layer. You can also called it plug-able framework  by which you can plugin  your dependent component at run time. Almost all types of work you can do by MEF which you can do with IOC. When to use MEF when IOC it depends on your requirements, application architecture. In my suggestion is when you need to compose your component at run time( run time plug-in play) in that case you can use MEF, when you create your components with its dependent component with static reference then you can use IOC. There are many articles published online where you can find more details/technical write-up regarding that. I hope you can study more on that and easily take decision which you can use and when. By the way MEF comes with .NET framework 4.0 So main benefit is, no third party component dependency is there. First you take reference ofsystem.ComponentModel.Composition to your project.  

MEF建立自定義控制器工廠 

在現實生活中,您將看到專業的項目在控制器工廠內部使用的是Ioc容器建立/讀取Controller對象的。若是您嘗試動態建立依賴對象和控制器對象會引起不少 問題。所以,這將是更好的方法在您的項目使用任意的IOC容器到並註冊全部的依賴對象。您可使用各類流行的IOC容器,如Castle Windsor, Unity, NInject, StructureMap etc.等,在這種狀況下我將演示如何使用託管擴展框架(MEF)。MEF並非一個IOC容器。它是一種層組合。你也能夠叫它可插拔的框架,在運行時,你能夠插入你的依賴組件。幾乎全部IOC的工做均可以由MEF完成。採用MEF仍是IOC這取決於你應用架構的要求。個人建議是,當你須要在運行時修改你的組件時(運行時間插件在運行),在這種狀況下,您可使用MEF,當您建立組件以及依賴於它的靜態引用的組件,那麼你可使用IOC。有不少網上公佈文章,哪裏能夠找到有關的更多詳細信息/技術文章。我但願你能多學習,那麼當你使用時很容易地做出決定。順便說一下MEF來自.NET 4.0框架,所以,主要的好處是,不存在第三方組件的依賴。首先,你須要在你的您的項目引用system.ComponentModel.Composition。  


Then you create custom Controller Factory. The name of the controller factory is MefControllerFactory

接下來建立自定義控制器工廠-----  MefControllerFactory。 

public class MefControllerFactory : DefaultControllerFactory
{
    private readonly CompositionContainer _container;
    public MefControllerFactory(CompositionContainer container)
    {
        _container = container;
    }
    protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
    {
        Lazy<object, object> export = _container.GetExports(controllerType, null, null).FirstOrDefault();
  
        return null == export
                            ? base.GetControllerInstance(requestContext, controllerType)
                            : (IController)export.Value;
    }
    public override void ReleaseController(IController controller)
    {
        ((IDisposable)controller).Dispose();
    }
} 
CompositContainer object is works like as IOC container here. Inside GetControllerInstance method I fetch controller object from that. If found null value then I fetch it from default controller (base class) object otherwise from CompositContainer object. After creating MefControllerFactory class we need to register it to MVCframework. Registration code in Application Start event 
 

這裏的CompositContainer對象是很像IOC容器。內部GetControllerInstance方法用於獲取取控制器對象。若是發現空值,那麼返回默認的控制器(基類)的對象,不然返回 CompositContainer對象。在建立MefControllerFactory 類,咱們須要在在Application Start事件將其註冊到MVC 框架。 

protected void Application_Start()
{
    var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    var composition = new CompositionContainer(catalog);
    IControllerFactory mefControllerFactory = new MefControllerFactory(composition);
    ControllerBuilder.Current.SetControllerFactory(mefControllerFactory);
}
I use InheritedExportAttributeExportAttribute,  PartCreationPolicyAttribute of MEF to interfaceILoggerHomeController.  我使用 MEF的   InheritedExportAttribute ExportAttribute,  PartCreationPolicyAttribute到接口  ILogger ,   HomeController
10
Based on these attributes MEF framework create objects. Just one important think you should remember that when you will use MEF, you should decorate your controllers through PartCreationPolicyAttribute and set controllers life time as create object per request policy.  [PartCreationPolicy (CreationPolicy.NonShared)] Without that you will get an error. By default it will use SharedCreation policy. 
基於這些屬性MEF框架建立對象。只是一個重要的知識點你應該記住,當你將使用MEF,你應該裝飾你的控制器經過PartCreationPolicyAttribute 併爲控制器建立的每一個請求策略的對象設置生命週期 [PartCreationPolicy(CreationPolicy.NonShared)], 若是沒有就會報錯。默認狀況下,它將使用SharedCreation策略。

Points of Interest  

I tried to explain various ways to create controller factory and inject its dependency in a very simple and straight forward way. First and second approach is just make understandable to the process of building and injecting controller and its dependency. You should not use that approach directly to the real life application. You can take IOC or MEF framework for that. You can do more research on MEF and learn MEF usage and best practices after that you will use it to your real life projects. Near future I have a plan to write another article that will demonstrate to use of various IOC containers like Windsor, Unity, NInject, StrucutureMap etc. to inject controller dependency injection and a comparative study with them.

In my sample downloadable code I used visual studio 2012 with .NET framework 4.5. Anyone can download and play with that. 

亮點  

我試圖解釋各類不一樣的方法來建立一個很是簡單直觀的注入依賴的控制器的工廠方法。這兩種方法是要理解構造和控制器的依賴注入過程。你不該該將其直接應用與真實的項目中。你可使用IOC或MEF框架。你能夠作更多的研究和學習使用MEF及最佳作法以後,能夠將它應用到你的現實生活中的項目。不久,我計劃寫一篇文章,將展現使用多種IoC容器像Windsor, Unity, NInject, StrucutureMap etc.等注入控制器依賴注入並進行比較和研究。

在個人示例下載的代碼,我用的是Visual Studio 2012 .NET Framework 4.5。任何人均可如下載玩一玩。

相關文章
相關標籤/搜索