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,Method, Constructor 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 StructureASP.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 控制器静态结构ASP.NET MVC框架的控制器结构被定义在名为Controller的抽象类。如果你想创建任何控制器,首先您必须必须继承自抽象的Controller类 。Controller类的层次结构的UML类图如下: All controllers root interface is 所有控制器的根接口 Simple Custom Controller Definition
If you create an ASP.NET MVC project, you will get two default controllers. One is 简单的自定义控制器定义如果你创建一个ASP.NET MVC项目时,你会得到两个默认控制器。一个是 If you look at the 如果你查看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对象,我不是自己创建 HomeController的对象,所以我找不到创建 DefaultLogger对象的地方以及如何传递
HomeController 参数给HomeController(ILogger logger)构造函数。如果我生成这个项目,那么,它肯定不会出错。但在运行时将引发异常。 异常详细信息的错误页面将会显示如下: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 MVC框架如何创建控制器?开始前描述HomeController的 If you look at the bellow image, you can see that
如果你看看波纹图像,你可以看到, 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 不支持这一要求。因此,我们需要一个新的自定义控制器工厂。创建自定义控制器厂通过实现 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 现在首先你需要在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构造函数并没有被调用,含参的 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
首先,您需要从控制器的全名创建控制器类型的对象。然后在运行时使用
Another Approach
Another way you can create your own controller factory. Though MVC framework’s default implementation of 另一种方法
另一种方法,你可以创建自己的控制器工厂。虽然MVC框架的默认实现 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 MEF for Creating Custom Controller FactoryIn real life project you will see experts are using IOC containers inside controller factory for creating/fetching the 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 接下来创建自定义控制器工厂----- 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对象是很像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 MEF的 InheritedExportAttribute ExportAttribute, PartCreationPolicyAttribute 到接口 ILogger , HomeController
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 基于这些属性MEF框架创建对象。只是一个重要的知识点你应该记住,当你将使用MEF,你应该装饰你的控制器通过PartCreationPolicyAttribute 并为控制器创建的每个请求策略的对象设置生命周期 [PartCreationPolicy(CreationPolicy.NonShared)], 如果没有就会报错。默认情况下,它将使用SharedCreation策略。
Points of InterestI 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。任何人都可以下载玩一玩。 |