原文链接:http://www.codeproject.com/Articles/560798/ASP-NET-MVC-Controller-Dependency-Injection-for-Be
前言:在这篇文章中,我将通过一个demo,直截了当地说明依赖注入在MVC框架中的使用。
内容列表:
1.介绍
2.为什么使用控制器依赖注入
3.控制器静态结构
4.自定义控制器
5.Framework中控制器的创建
6.为什么使用控制器工厂模式
7.控制器工厂模式
7.1.目标1
7.2.目标2
8.使用MEF实现控制器工厂模式
9.重点补充
一:介绍
首先简单地说明控制器在MVC框架中要做的几件事情
1.接收HTTP请求
2.处理HTTP请求
3.操作客户端输入的数据
4.发送回复给客户端
5.作为Model和View的中转站
MVC框架在运行时自己创建控制器对象,有一个先决条件,控制器类的构造函数是无参的。如果你想传递一个对象作为控制器的参数,这种情况我们又该如何处理?创建这种类型的控制器会失败,我们需要创建自己的控制器,将控制器参数注入到控制器。
有多种方式可以将参数注入到控制器的构造方法中
1.设置属性
2.通过方法
3.构造方法
在这篇文章中,我将解释如何使用控制器注入到MVC中的构造函数中。如果不使用自定义控制器工厂模式,控制器注入是无法实现的。当然我也会解释如何创建简单的控制器工厂,然后注册到MVC框架。我也会展示一种方法注入控制器,使用MEF。
二.为什么使用控制器注入
在现实的程序开发中,你会看到绝大多数的MVC程序需要注入它所依赖的组件。你可以直接创建组件在控制器中,而不需要注入它们。在这种情况下,组件和控制器紧密结合,如果一个组件的扩展发生了改变,或者一个新版本的组件要使用,你就需要改变控制器中的实现(PS:讲解为什么使用控制器注入)
当你想使用单元测试的时候,另一种困难你可能会遇到。你不能测试这些控制器在一个独立的单元。你不能模仿一些新的特性,如果不能模仿,你将不能成功运行你的代码在一个独立的环境。
三.控制器静态结构
MVC框架中的控制器结构是定义在一个叫Controller的抽象类中,如果你想创建一些控制器,首先你需要创建一个类,从抽象类Controller中继承,UML类图如下:
所有的控制器都有一个根接口IController,抽象类ControllerBase从它去实现自己的方法。另一个抽象类从ControllerBase中继承,这个类就是Controller,所有的自定义的控制器类都要从Controller中继承,或者从它的子类中继承。
四.简单的自定义控制器
如果你创建一个MVC工程,你将会得到两个控制器,AccountController和HomeController
如果你去看HomeController中的代码实现,你会发现它没有自己的构造方法。
1 public class HomeController : Controller
2 {
3 public ActionResult Index()
4 {
5 ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
6 return View();
7 }
8 public ActionResult About()
9 {
10 ViewBag.Message = "Your app description page.";
11 return View();
12 }
13 }
我们都知道这里没有一个构造方法,但在编译时会创建一个无参的构造方法。
1 ublic class HomeController : Controller
2 {
3 public HomeController()
4 {
5 }
6 }
现在我将要创建一个ILogger接口及它的一个实现类DefaultLogger类,Home控制器会使用ILogger类型的对象作为参数,注入到它的控制器构造方法中。
1 public interface ILogger
2 {
3 void Log(string logData);
4 }
5 public class DefaultLogger : ILogger
6 {
7 public void Log(string logData)
8 {
9 System.Diagnostics.Debug.WriteLine(logData, "default");
10 }
11 }
带参ILogger的Home控制器构造方法如下:
1 public class HomeController : Controller
2 {
3 private readonly ILogger _logger;
4 public HomeController(ILogger logger)
5 {
6 _logger = logger;
7 }
8 }
直到现在你也没找到我们在什么地方实例化了DefaultLogger对象,也不知道如何传递这个对象到控制器的构造方法中,在编写代码阶段程序不会报错,但是在运行代码会报错,如下:
看上面的线程记录,DefaultControllerActivator对象会抛出一个异常MissingMethonException。如果你到MSDN,找这个异常是如何引发的,你会发现找到不到适当的方法。看接下来的异常InvalidOperationException,它确实包含了MissingMethodException,将下来你会看到更加有用的信息,确保在控制器构造方法中有一个带参数的构造方法。如果想让代码工作正常,我必需要加带一个参数的构造方法,框架会创建控制器对象通过我们创建的那个构造方法。问题在于,我们如何传递一个一个DefaultLogger对象到这个控制器。请继续你好练习,我们接着往下看。
五.MVC框架是如何创建控制器对象
在我们开始注入DefaultLogger对象到HomeController之前,我们要有一个概念,MVC框架是如何创建一个控制器对象的。IControllerFactory接口的主要责任就是创建控制器对象。DefaultControllerFactory是框架默认提供的可扩展的类。如果你添加一个无参的构造方法,然后设置一个断点,你将会发现程序在这一刻,将停留在这里。
看上面的图片,你可以看到IControllerFactory类型的一个DefaultControllerFactory对象,DefaultControllerFactory有一些方法,如:Create,GetControllerInstance,CreateController,这些方法将会创建一个HomeController对象,MVC框架是开源的,如果你想知道更多的方法,可以下载官方的资源,自己去阅读。你看调试的代码,你可以看到DefaultControllerFactory对象被CurrentControllerFactory
六.为什么要自定义控制器工厂
现在我们知道默认的控制器工厂使用一个无参的构造方法创建一个控制器对象。我们可以注入自己的带参的控制器构造方法。
1 public class HomeController : Controller
2 {
3 private readonly ILogger _logger;
4 public HomeController():this(new DefaultLogger())
5 {
6 }
7 public HomeController(ILogger logger)
8 {
9 _logger = logger;
10 }
11 }
我发现许多的开发者都对上面依赖注入有所误解,它不是一个依赖注入的形式。这个确实违反了组件的原则。而这个原则的愿意是:上层的模块不能依赖于低层级的模块,双方都要依赖于抽象层,细节要在体现在抽象层。在上面的代码中,HomeController创建了自己的DefaultLogger对象。它直接依赖于ILogger接口的扩展(DefaultLogger),如果在将来一个新的扩展(扩展了ILogger接口),我们需要修改我们HomeController中的方法,所以我们需要使用适当的方法去注入我们的组件。我们要使用一个带参的构造方法去注入我们的ILogger 组件,但是默认的 DefaultControllerFactory不支持我们这么做,所以我们要创建自己的控制器工厂。
七.自定义控制器工厂
我使用两种方法展示如何创建自己的控制器工厂。
7.1途径1
我们可以创建一个新的控制器工厂,扩展了IControllerFactory接口,假定我们自定义的控制器工厂的名称叫CustomControllerFactory,如下
1 public class CustomControllerFactory : IControllerFactory
2 {
3 public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
4 {
5 ILogger logger = new DefaultLogger();
6 var controller = new HomeController(logger);
7 return controller;
8 }
9 public System.Web.SessionState.SessionStateBehavior GetControllerSessionBehavior(
10 System.Web.Routing.RequestContext requestContext, string controllerName)
11 {
12 return SessionStateBehavior.Default;
13 }
14 public void ReleaseController(IController controller)
15 {
16 IDisposable disposable = controller as IDisposable;
17 if (disposable != null)
18 disposable.Dispose();
19 }
20 }
现在第一步,我们需要将CustomControllerFactory注册到MVC框架,完成这件事要在Application_Start事件中书写代码。
1 public class MvcApplication : System.Web.HttpApplication
2 {
3 protected void Application_Start()
4 {
5 RegisterCustomControllerFactory ();
6 }
7 }
8 private void RegisterCustomControllerFactory ()
9 {
10 IControllerFactory factory = new CustomControllerFactory();
11 ControllerBuilder.Current.SetControllerFactory(factory);
12 }
如果你运行你的程序,你会发现那个无参的构造方法没有被执行,那个带参的构造方法执行了。你的问题就这么简单的解决了。
你可以构建你的控制器工厂使用反射机制。
1 public class CustomControllerFactory : IControllerFactory
2 {
3 private readonly string _controllerNamespace;
4 public CustomControllerFactory(string controllerNamespace)
5 {
6 _controllerNamespace = controllerNamespace;
7 }
8 public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
9 {
10 ILogger logger = new DefaultLogger();
11 Type controllerType = Type.GetType(string.Concat(_controllerNamespace, ".", controllerName, "Controller"));
12 IController controller = Activator.CreateInstance(controllerType, new[] { logger }) as Controller;
13 return controller;
14 }
15 }
7.2途径2
这里方法不是去扩展IControllerFactory接口,而是去继承DefaultControllerFactory类,通过修改其中的方法。当然也要将控制器工厂注入到程序启动的事件中。代码如下:
1 public class CustomControllerFactory : DefaultControllerFactory
2 {
3 protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
4 {
5 ILogger logger = new DefaultLogger();
6 IController controller = Activator.CreateInstance(controllerType, new[] { logger }) as Controller;
7 return controller;
8 }
9 }
(去掉了MEF创建自定义控制器工厂的方法,因为自己也实在不能理解,但是要看啊)。