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。任何人均可如下载玩一玩。

相关文章
相关标签/搜索