Fork me on GitHub

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.

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

 
 
posted @ 2013-07-05 10:19  Halower  阅读(1559)  评论(0编辑  收藏  举报