MVC Controller Dependency Injection for Beginners【翻譯】

Halower發表於2013-07-05

  在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.

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框架本身在執行時會建立控制器物件。但是有一個前提,那就是控制器類至少必須有一個引數的建構函式。但是,如果你需要傳遞一些物件給建構函式,那麼會發生什麼呢?簡單的框架,將無法建立控制器物件。在這種情況下,我們需要建立控制器實現自我依賴注入。

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

 

這裡有很多方法可以向類中進行依賴注入。例如,它可能是屬性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.  

在現實生活中的應用程式開發,你會看到,幾乎所有的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的 InheritedExportAttributeExportAttribute, 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。任何人都可以下載玩一玩。

 
 

相關文章