Controller激活系统详解 — IoC的应用

来源:互联网 发布:ansys15.0软件 编辑:程序博客网 时间:2024/06/01 18:54

所谓控制反转(IoC: Inversion Of Control)简单地说就是应用本身不负责依赖对象的创建和维护,而交给一个外部容器来负责。这样控制权就由应用转移到了外部IoC容器,控制权就实现了所谓的反转。比如在类型A中需要使用类型B的实例,而B实例的创建并不由A来负责,而是通过外部容器来创建。通过IoC的方式是实现针对目标Controller的激活具有重要的意义。

目录 
一、从Unity来认识IoC 
二、Controller与Model的分离 
三、 创建基于IoC的自定义ControllerFactory 
        实例演示:自定义一个基于Unity的ControllerFactory 
四、ControllerActivator V.S. DependencyResoolver 
五、通过自定义ControllerActivator实现IoC 
六、通过自定义DependencyResoolver实现IoC

一、从Unity来认识IoC

有时我们又将IoC称为依赖注入(DI: Dependency Injection)。所谓依赖注入,就是由外部容器在运行时动态地将依赖的对象注入到组件之中。Martin Fowler在那篇著名的文章《Inversion of Control Containers and the Dependency Injection pattern》中将具体依赖注入划分为三种形式,即构造器注入、属性(设置)注入和接口注入,而我个人习惯将其划分为一种(类型)匹配和三种注入:

  • 类型匹配(Type Matching):虽然我们通过接口(或者抽象类)来进行服务调用,但是服务本身还是实现在某个具体的服务类型中,这就需要某个类型注册机制来解决服务接口和服务类型之间的匹配关系;
  • 构造器注入(Constructor Injection):IoC容器会智能地选择选择和调用适合的构造函数以创建依赖的对象。如果被选择的构造函数具有相应的参数,IoC容器在调用构造函数之前解析注册的依赖关系并自行获得相应参数对象;
  • 属性注入(Property Injection):如果需要使用到被依赖对象的某个属性,在被依赖对象被创建之后,IoC容器会自动初始化该属性;
  • 方法注入(Method Injection):如果被依赖对象需要调用某个方法进行相应的初始化,在该对象创建之后,IoC容器会自动调用该方法。

开源社区具有很有流行的IoC框架,比如Castle Windsor、Unity、Spring.NET、StructureMap和Ninject等。Unity是微软Patterns & Practices部门开发的一个轻量级的IoC框架。该项目在Codeplex上的地址为http://unity.codeplex.com/, 你可以下载相应的安装包和开发文档。Unity的最新版本为2.1。出于篇幅的限制,我不可能对Unity进行前面的介绍,但是为了让读者了解IoC在Unity中的实现,我写了一个简单的程序。

我们创建一个控制台程序,定义如下几个接口(IA、IB、IC和ID)和它们各自的实现类(A、B、C、D)。在类型A中定义了3个属性B、C和D,其类型分别为接口IB、IC和ID。其中属性B在构在函数中被初始化,以为着它会以构造器注入的方式被初始化;属性C上应用了DependencyAttribute特性,意味着这是一个需要以属性注入方式被初始化的依赖属性;属性D则通过方法Initialize初始化,该方法上应用了特性InjectionMethodAttribute,意味着这是一个注入方法在A对象被IoC容器创建的时候会被自动调用。

   1: namespace UnityDemo
   2: {
   3:     public interface IA { }
   4:     public interface IB { }
   5:     public interface IC { }
   6:     public interface ID {}
   7:  
   8:     public class A : IA
   9:     {
  10:         public IB B { get; set; }
  11:         [Dependency]
  12:         public IC C { get; set; }
  13:         public ID D { get; set; }
  14:  
  15:         public A(IB b)
  16:         {
  17:             this.B = b;
  18:         }
  19:         [InjectionMethod]
  20:         public void Initialize(ID d)
  21:         {
  22:             this.D = d;
  23:         }
  24:     }
  25:     public class B: IB{}
  26:     public class C: IC{}
  27:     public class D: ID{}
  28: }

然后我们为该应用添加一个配置文件,并定义如下一段关于Unity的配置。这段配置定义了一个名称为defaultContainer的Unity容器,并在其中完成了上面定义的接口和对应实现类之间映射的类型匹配。

   1: <configuration>
   2:   <configSections>
   3:     <section name="unity" 
   4:          type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, 
   5:                Microsoft.Practices.Unity.Configuration"/>
   6:   </configSections>
   7:   <unity>
   8:     <containers>
   9:       <container name="defaultContainer">
  10:         <register type="UnityDemo.IA, UnityDemo" mapTo="UnityDemo.A, UnityDemo"/>
  11:         <register type="UnityDemo.IB, UnityDemo" mapTo="UnityDemo.B, UnityDemo"/>
  12:         <register type="UnityDemo.IC, UnityDemo" mapTo="UnityDemo.C, UnityDemo"/>
  13:         <register type="UnityDemo.ID, UnityDemo" mapTo="UnityDemo.D, UnityDemo"/>
  14:       </container>
  15:     </containers>
  16:   </unity> 
  17: </configuration>

最后在Main方法中创建一个代表IoC容器的UnityContainer对象,并加载配置信息对其进行初始化。然后调用它的泛型的Resolve方法创建一个实现了泛型接口IA的对象。最后将返回对象转变成类型A,并检验其B、C和D属性是否是空。

   1: static void Main(string[] args)
   2: {
   3:     IUnityContainer container = new UnityContainer();
   4:     UnityConfigurationSection configuration = ConfigurationManager.GetSection(UnityConfigurationSection.SectionName)
   5:         as UnityConfigurationSection;
   6:     configuration.Configure(container, "defaultContainer");
   7:     A a = container.Resolve<IA>() as A;
   8:     if (null != a)
   9:     {
  10:         Console.WriteLine("a.B == null ? {0}", a.B == null ? "Yes" : "No");
  11:         Console.WriteLine("a.C == null ? {0}", a.C == null ? "Yes" : "No");
  12:         Console.WriteLine("a.D == null ? {0}", a.D == null ? "Yes" : "No");
  13:     }
  14: }

从如下给出的执行结果我们可以得到这样的结论:通过Resolve<IA>方法返回的是一个类型为A的对象,该对象的三个属性被进行了有效的初始化。这个简单的程序分别体现了接口注入(通过相应的接口根据配置解析出相应的实现类型)、构造器注入(属性B)、属性注入(属性C)和方法注入(属性D)。[源代码从这里下载]

   1: a.B == null ? No
   2: a.C == null ? No
   3: a.D == null ? No

二、Controller与Model的分离

在《MVC、MVP以及Model2[下篇]》中我们谈到ASP.NET MVC是基于MVC的变体Model2设计的。ASP.NET MVC所谓的Model仅仅表示绑定到View上的数据,我们一般称之为View Model。而真正的Model一般意义上指维护应用状态和提供业务功能操作的领域模型,或者是针对业务层的入口或者业务服务的代理。真正的MVC在ASP.NET MVC中的体现如下图所示。

image

对于一个ASP.NET MVC应用来说,用户交互请求直接发送给Controller。如果涉及到针对某个个业务功能的调用,Controller会直接调用Model;如果呈现业务数据,Controller会通过Model获取相应业务数据并转换成View Model,最终通过View呈现出来。这样的交互协议方式反映了Controller针对Model的直接依赖。

如果我们在Controller激活系统中引入IoC,并采用IoC的方式提供用于处理请求的Controller对象,那么Controller和Model之间的依赖程度在很大程度上降低。我们甚至可以像下图所示的一样,以接口的方式都Model进行抽象,让Controller依赖于这个抽象化的Model接口,而不是具体的Model实现。

image

三、 创建基于IoC的自定义ControllerFactory

ASP.NET MVC的Controller激活系统最终通过ControllerFactory来创建目标Controller对象,要将IoC引入ASP.NET MVC并通过对应的IoC容器实现对目标Controller的激活,我们很自然地会想到自定义一个基于IoC的ControllerFactory。

对于IoC的ControllerFactory的创建,我们可以直接实现IControllerFactory接口创建一个全新的ControllerFactory类型,这需要实现包括Controller类型的解析、Controller实例的创建与释放以及会话状态行为选项的获取在内的所有功能。一般来说,Controller实例的创建与释放才收IoC容器的控制,为了避免重新实现其他的功能,我们可以直接继承DefaultControllerFactory,重写Controller实例创建于释放的逻辑。

实例演示:自定义一个基于Unity的ControllerFactory

现在我们通过一个简单的实例演示如何通过自定义ControllerFactory利用Unity进行Controller的激活与释放。为了避免针对Controller类型解析和会话状态行为选项的获取逻辑的重复定义,我们直接继承DefaultControllerFactory。我们将该自定义ControllerFactory命名为UnityControllerFactory,整个定义如下面的的代码片断所示。[源代码从这里下载]

   1: public class UnityControllerFactory : DefaultControllerFactory
   2: {
   3:     static object syncHelper = new object();
   4:   static Dictionary<string, IUnityContainer> containers = new Dictionary<string,IUnityContainer>();
   5:     public IUnityContainer UnityContainer { get; private set; }
   6:     public UnityControllerFactory(string containerName = "")
   7:     {
   8:         if (containers.ContainsKey(containerName))
   9:         {
  10:             this.UnityContainer = containers[containerName];
  11:             return;
  12:         }
  13:         lock (syncHelper)
  14:         {
  15:             if (containers.ContainsKey(containerName))
  16:             {
  17:                 this.UnityContainer = containers[containerName];
  18:                 return;
  19:             }
  20:             IUnityContainer container = new UnityContainer();
  21:             //配置UnityContainer
  22:             UnityConfigurationSection configSection = ConfigurationManager.GetSection(UnityConfigurationSection.SectionName) 
  23:                 as UnityConfigurationSection;           
  24:             if (null == configSection && !string.IsNullOrEmpty(containerName))
  25:             {
  26:                 throw new ConfigurationErrorsException("The <unity> configuration section does not exist.");
  27:             }
  28:             if (null != configSection )
  29:             {
  30:                 if(string.IsNullOrEmpty(containerName))
  31:                 {
  32:                     configSection.Configure(container);
  33:                 }
  34:                 else
  35:                 {
  36:                     configSection.Configure(container, containerName);
  37:                 }
  38:             }
  39:             containers.Add(containerName, container);
  40:             this.UnityContainer = containers[containerName];
  41:         }       
  42:     }
  43:   protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
  44:     {
  45:         if (null == controllerType)
  46:         {
  47:             return null;
  48:         }
  49:         return (IController)this.UnityContainer.Resolve(controllerType);
  50:     }
  51:     public override void ReleaseController(IController controller)
  52:     {
  53:         this.UnityContainer.Teardown(controller);
  54:     }
  55: }

UnityControllerFactory的UnityConainer属性表示的实现自Microsoft.Practices.Unity.IUnityContainer接口的对象表示定义在Unity中的IoC容器。为了避免UnityConainer对象的频繁创建,我们创建的UnityConainer对象保存在一个通过静态字段(containers)表示的字典对象中,其Key为UnityConainer的配置名称。构造函数中的参数containnerName表示使用的UnityConainer的配置名称,如果静态字典中存在着与之匹配的UnityConainer对象,则直接获取出来作为UnityConainer属性的值;否则创建一个新的UnityConainer对象并加载对应的配置对其进行相关设置,最后将其赋值给UnityConainer属性并添加到静态字典之中。

我们重写了定义在基类DefaultControllerFactory的虚方法GetControllerInstance,在解析出来的Controller类型(controllerType参数)不为Null的情况下,直接调用UnityConainer的Resolve方法激活对应的Controller实例。在用于释放Controller对象的ReleaseController方法中,我们直接将Controller对象作为参数调用UnityConainer的Teardown方法。

整个自定义的UnityControllerFactory就这么简单,为了演示IoC在它身上的体现,我们在一个简单的ASP.MVC实例中来使用我们刚刚定义的UnityControllerFactory。我们沿用在《ASP.NET的路由系统:URL与物理文件的分离》中使用过的关于“员工管理”的场景,如下图所示,本实例由两个页面(对应着两个View)组成,一个用于显示员工列表,另一个用于显示基于某个员工的详细信息。

image

我们通过Visual Studio的ASP.NET MVC项目模板创建一个空的Web应用,并添加针对Unity的两个程序集(Microsoft.Practices.Unity.dll和Microsoft.Practices.Unity.Configuration.dll)引用。然后我们再Models目录下定义如下一个表示员工信息的Employee类型。

   1: public class Employee
   2: {
   3:     [Display(Name="ID")]
   4:     public string Id { get; private set; }
   5:     [Display(Name = "姓名")]
   6:     public string Name { get; private set; }
   7:     [Display(Name = "性别")]
   8:     public string Gender { get; private set; }
   9:     [Display(Name = "出生日期")]
  10:     [DataType(DataType.Date)]
  11:     public DateTime BirthDate { get; private set; }
  12:     [Display(Name = "部门")]
  13:     public string Department { get; private set; }
  14:  
  15:     public Employee(string id, string name, string gender, DateTime birthDate, string department)
  16:     {
  17:         this.Id = id;
  18:         this.Name = name;
  19:         this.Gender = gender;
  20:         this.BirthDate = birthDate;
  21:         this.Department = department;
  22:     }
  23: }

我们创建一个独立的组件来模拟用于维护应用状态提供业务操作功能的Model(在这里我们将ASP.NET MVC中的Model视为View Model),为了降低Controller和Model之间耦合度,我们为这个Model定义了接口。如下所示的IEmployeeRepository就代表了这个接口,唯一的方法GetEmployees用于获取所有员工列表(id参数值为空)或者基于指定ID的某个员工信息。

   1: public interface IEmployeeRepository
   2: {
   3:     IEnumerable<Employee> GetEmployees(string id = "");
   4: }

EmployeeRepository类型实现了IEmployeeRepository接口,具体的定义如下所示。简单起见,我们直接通过一个类型为List<Employee>得静态字段来表示所有员工信息的存储。

   1: public class EmployeeRepository: IEmployeeRepository
   2: {
   3:     private static IList<Employee> employees;
   4:     static EmployeeRepository()
   5:     {
   6:         employees = new List<Employee>();
   7:         employees.Add(new Employee(Guid.NewGuid().ToString(), "张三", "男", new DateTime(1981, 8, 24), "销售部"));
   8:         employees.Add(new Employee(Guid.NewGuid().ToString(), "李四", "女", new DateTime(1982, 7, 10), "人事部"));
   9:         employees.Add(new Employee(Guid.NewGuid().ToString(), "王五", "男", new DateTime(1981, 9, 21), "人事部"));
  10:     }
  11:     public IEnumerable<Employee> GetEmployees(string id = "")
  12:     {
  13:         return employees.Where(e => e.Id == id || string.IsNullOrEmpty(id));
  14:     }
  15: }

现在我们来创建我们的Controller,在这里我们将其起名为EmployeeController。如下面的代码片断所示,EmployeeController具有一个类型为IEmployeeRepository的属性Repository,应用在上面的DependencyAttribute特性我们知道这是一个“依赖属性”,如果采用UnityContainer来激活EmployeeController对象的时候,会根据注册的类型映射来实例化一个实现了IEmployeeRepository的类型的实例来初始化该属性。

   1: public class EmployeeController : Controller
   2: {
   3:     [Dependency]
   4:     public IEmployeeRepository Repository { get; set; }
   5:     public ActionResult Index()
   6:     {
   7:         var employees = this.Repository.GetEmployees();
   8:         return View(employees);
   9:     }
  10:     public ActionResult Detail(string id)
  11:     {
  12:         Employee employee = this.Repository.GetEmployees(id).FirstOrDefault();
  13:         if (null == employee)
  14:         {
  15:             throw new HttpException(404, string.Format("ID为{0}的员工不存在", id));
  16:         }
  17:         return View(employee);
  18:     }
  19: }

默认的Index操作方法中,我们通过Repository属性获取表示所有员工的列表,并将其作为Model显现在对应的View中。至于用于显示指定员工ID详细信息的Detail操作,我们同样通过Repository属性根据指定的ID获取表示相应员工信息的Employee对象,如果该对象为Null,直接返回一个状态为404的HttpException;否则作为将其作为Model显示在相应的View中。

如下所示的名为Index的View的定义,它的Model类型为IEnumerable<Employee>,在这里View中,我们通过一个表格来显示表示为Model的员工列表。值得一提的是,我们通过调用HtmlHelper的ActionLink方法将员工的名称显示为一个执行Detail操作的连接,作为路由变量参数集合中同时包含当前员工的ID和姓名。根据我们即将注册的路由规则,这个链接地址的格式为/Employee/Detail/{Name}/{Id}。

   1: @model IEnumerable<Employee>
   2: @{
   3:     ViewBag.Title = "Index";
   4: }
   5: <table id="employees" rules="all" border="1">
   6:     <tr>
   7:         <th>姓名</th>
   8:         <th>性别</th>
   9:         <th>出生日期</th>
  10:         <th>部门</th>
  11:     </tr>
  12:     @{
  13:         foreach(Employee employee in Model)
  14:         {
  15:             <tr>
  16:                 <td>@Html.ActionLink(employee.Name, "Detail", 
  17:                     new { name = employee.Name, id = employee.Id })</td>
  18:                 <td>@employee.Gender</td>
  19:                 <td>@employee.BirthDate.ToString("dd/MM/yyyy")</td>
  20:                 <td>@employee.Department</td>
  21:             </tr>
  22:         }
  23:     }    
  24: </table>

用于显示具有某个员工信息的名为Detail的View定义如下,这是一个Model类型为Employee的强类型的View,我们通过通过表格的形式将员工的详细信息显示出来。

   1: @model Employee
   2: @{
   3:     ViewBag.Title = Model.Name;
   4: }
   5: <table id="employee" rules="all" border="1">
   6:     <tr><td>@Html.LabelFor(m=>m.Id)</td><td>@Model.Id</td></tr>
   7:     <tr><td>@Html.LabelFor(m=>m.Name)</td><td>@Model.Name</td></tr>
   8:     <tr><td>@Html.LabelFor(m=>m.Gender)</td><td>@Model.Gender</td></tr>
   9:     <tr><td>@Html.LabelFor(m=>m.BirthDate)</td><td>@Model.BirthDate.ToString("dd/MM/yyyy")</td></tr>
  10:     <tr><td>@Html.LabelFor(m=>m.Department)</td><td>@Model.Department</td></tr>
  11: </table>

我需要在Global.asax中完成两件事情,即针对自定义UnityControllerFactory和路由的注册。如下的代码片断所示,在Application_Start方法中我们通过当前ControllerBuilder注册了一个UnityControllerFactory实例。 在RegisterRoutes方法中我们注册两个路由,前者针对Detail操作(URL模版包含员工的ID和姓名),后者针对Index操作。

   1: public class MvcApplication : System.Web.HttpApplication
   2: {
   3:     //其他成员
   4:     public static void RegisterRoutes(RouteCollection routes)
   5:     {
   6:         routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
   7:         routes.MapRoute(
   8:             name: "Detail",
   9:             url: "{controller}/{action}/{name}/{id}",
  10:             defaults: new { controller = "Employee" }
  11:         );
  12:         routes.MapRoute(
  13:             name: "Default",
  14:             url: "{controller}/{action}",
  15:             defaults: new { controller = "Employee", action = "Index"}
  16:         );
  17:     }
  18:  
  19:     protected void Application_Start()
  20:     {
  21:         //其他操作
  22:         RegisterRoutes(RouteTable.Routes);
  23:         ControllerBuilder.Current.SetControllerFactory(new UnityControllerFactory());
  24:     }
  25: }

EmployeeController仅仅依赖于IEmployeeRepository接口,它通过基于该类型的依赖属性Repository返回员工信息,我们需要通过注册为之设置一个具体的匹配类型,而这个类型自然就是前面我们定义的EmployeeRepository。如下所示的正是Unity相关的类型注册配置。到此为止,整个实例的编程和配置工作既已完成(忽略了针对样式的设置),运行该程序就可以得到如上图所示的效果。

   1: <configuration>
   2:   <configSections>
   3:     <section name="unity" 
   4:          type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, 
   5:                Microsoft.Practices.Unity.Configuration"/>
   6:      </configSections>
   7:   <unity>
   8:     <containers>
   9:       <container>
  10:         <register type="Artech.Mvc.IEmployeeRepository, Artech.Mvc.MvcApp" 
  11:                   mapTo="Artech.Mvc.EmployeeRepository, Artech.Mvc.MvcApp"/>
  12:       </container>
  13:     </containers>
  14:   </unity>
  15: </configuration>

 

除了通过自定义ControllerFactory的方式引入IoC之外,在使用默认DefaultControllerFactory情况下也可以通过一些扩展使基于IoC的Controller激活成为可能。主要的方式就是自定义ControllerActivator和 DependencyResolver。

四、ControllerActivator V.S. DependencyResolver

如下面的代码片断所示,DefaultControllerFactory具有两个构造函数重载,其中一个具有一个类型为IControllerActivator接口的参数,我们将实现了该接口得类型统称为ControllerActivator。

   1: public class DefaultControllerFactory : IControllerFactory
   2: {
   3:     //其他成员
   4:     public DefaultControllerFactory();
   5:     public DefaultControllerFactory(IControllerActivator controllerActivator);   
   6: }

顾名思义,ControllerActivator就是Controller的“激活器”,Controller的激活实现在唯一的Create方法中。如下面的代码所示,该方法具有两个参数(requestContext和controllerType),分别代表当前请求上下文和解析出来的目标Controller的类型。

   1: public interface IControllerActivator
   2: {
   3:     IController Create(RequestContext requestContext, Type controllerType);
   4: }

在默认的情况下(调用DefaultControllerFactory默认构造函数或者指定的参数为Null),Controller激活系统 会默认使用一个类型为DefaultControllerActivator的对象。如下面的代码片断所示,DefaultControllerActivator是一个实现了IControllerActivator私有类型而已,我们不能直接通过编程的方式使用它。

   1: private class DefaultControllerActivator : IControllerActivator
   2: {
   3:     public DefaultControllerActivator();
   4:     public DefaultControllerActivator(IDependencyResolver resolver);
   5:      public IController Create(RequestContext requestContext,  Type controllerType);
   6: }

DefaultControllerActivator的构造函数具有一个类型为IDependencyResolver的参数,这是一个重要的接口,我们将实现了该接口的类型统称为DependencyResolver。。如下面的代码片断所示,IDependencyResolver接口具有两个方法GetService和GetServices,用于根据指定的类型获取单个或者多个实例。实际上DefaultControllerActivator就是通过调用GetService方法获取具体的Controller对象的

   1: public interface IDependencyResolver
   2: {
   3:     object GetService(Type serviceType);
   4:     IEnumerable<object> GetServices(Type serviceType);
   5: }

如果在构造DefaultControllerActivator对象的时候传入的参数为Null,那么Controller激活系统会使用通过DependencyResolver的静态只读属性Current表示DependencyResolver。需要提醒的是,DependencyResolver类型没有实现IDependencyResolver接口,而是对一个实现了IDependencyResolver接口类型对象的封装。

   1: public class DependencyResolver
   2: {
   3: private static DependencyResolver _instance;
   4:  
   5:     public void InnerSetResolver(object commonServiceLocator);
   6:     public void InnerSetResolver(IDependencyResolver resolver);
   7:     public void InnerSetResolver(Func<Type, object> getService, Func<Type, IEnumerable<object>> getServices);
   8:     
   9:     public static void SetResolver(object commonServiceLocator);
  10:     public static void SetResolver(IDependencyResolver resolver);
  11:     public static void SetResolver(Func<Type, object> getService, Func<Type, IEnumerable<object>> getServices);
  12:  
  13:     public static IDependencyResolver Current { get; }
  14:     public IDependencyResolver InnerCurrent { get; }
  15: }

这个被封装的DependencyResolver(指实现了接口IDependencyResolver的某个类型的类型,不是指DependencyResolver类型的对象,对于后者我会采用“DependencyResolver类型对象”的说法)通过只读属性InnerCurrent表示,而三个InnerSetResolver方法重载用于初始化改属性。静态字段_instance表示当前的DependencyResolver类型对象,静态只读属性Current则表示该对象内部封装的DependencyResolver对象,而它通过三个静态的SetResolver进行初始化。

如果我们不曾通过调用DependencyResolver的静态方法SetResolver通过Current属性表示的当前DependencyResolver进行显示设置,该属性默认返回一个DefaultDependencyResolver对象。如下面的代码片断所示,DefaultDependencyResolver是一个实现了IDependencyResolver接口的私有类型,在实现的GetService方法中,它直接通过根据指定的类型以反射的形式创建相应的对象并返回,所以前面我们说DefaultControllerFactory根据解析出来的Controller类型以反射的形式创建对应的实例在这里得到了印证。至于GetServices方法则返回一个空对象集合。

   1: private class DefaultDependencyResolver : IDependencyResolver
   2: {
   3:     public object GetService(Type serviceType)    
   4:     {
   5:         if (serviceType.IsInterface || serviceType.IsAbstract)
   6:         {
   7:             return null;
   8:         }
   9:         try
  10:         {
  11:             return Activator.CreateInstance(serviceType);
  12:         }
  13:         catch
  14:         {
  15:             return null;
  16:         }
  17:     }
  18:  
  19:     public IEnumerable<object> GetServices(Type serviceType)
  20:     {
  21:         return Enumerable.Empty<object>();
  22:     }
  23: }

上面介绍的类型DefaultControllerFactory、IControllerActivator、DefaultControllerActivator、IDependencyResolver、DefaultDependencyResolver和DependencyResolver之前的关系基本上可以通过如下图所示的类图来体现。

image

五、通过自定义ControllerActivator实现IoC

如果我们基于一个ControllerActivator对象来创建一个DefaultControllerFactory,它会最终被用于Controller对象的激活,那么我们可以自定义ControllerActivator的方式将IoC引入Controller激活系统。我们接下来自定义的ControllerActivtor基于另一个IoC框架Ninject,较之Unity,Ninject是一个更加轻量级也更适合ASP.NET MVC的IoC框架。我们将自定义的ControllerActivator起名为NinjectControllerActivator,全部定义如下。[源代码从这里下载]

   1: public class NinjectControllerActivator: IControllerActivator
   2: {
   3:     public IKernel Kernel { get; private set; }
   4:     public NinjectControllerActivator()
   5:     {
   6:         this.Kernel = new StandardKernel();
   7:         AddBindings();    
   8:     }    
   9:     public IController Create(RequestContext requestContext, Type controllerType)
  10:     {
  11:         return (IController)this.Kernel.TryGet(controllerType) as IController;
  12:     }
  13:     private void AddBindings()
  14:     {
  15:         this.Kernel.Bind<IEmployeeRepository>().To<EmployeeRepository>();
  16:     }
  17: }

我们使用的还是上面演示的关于员工管理的例子。NinjectControllerActivator的只读属性Kernel在这里用于类型注册和基于类型的实例提供,具体来说它是在构造函数中初始化的StandardKernel对象。同样在构造函数中,我们通过该Kernel实现了作为Model接口的IEmployeeRepository类型和Model实现的EmployeeRepository类型之间的映射。在Create方法中,我们通过Kernel的TryGet方法根据指定的类型获取相应的Controller对象。

现在我们无须再使用自定义的ControllerFactory,只需要注册一个基于我们自定义的NinjectControllerActivator的DefaultControllerFactory即可。定义在Global.asax中与ControllerFactory注册相关的代码如下所示。

   1: public class MvcApplication : System.Web.HttpApplication
   2: {
   3:     //其他成员
   4:     protected void Application_Start()
   5:     {
   6:         //其他操作
   7:         NinjectControllerActivator controllerActivator =  new NinjectControllerActivator();
   8:         DefaultControllerFactory controllerFactory  = new DefaultControllerFactory(controllerActivator);
   9:         ControllerBuilder.Current.SetControllerFactory(controllerFactory);
  10:     }
  11: }

六、通过自定义DependencyResoolver实现IoC

通过前面的介绍我们知道,当我们调用构造函数创建一个DefaultControllerFactory的时候,如果调用的时候默认无参构造函数,后者将作为参数的ControllerActivator对象设置为Null,那么默认请求用于激活Controller实例的是通过DependencyResoolver类型的静态属性Current表示的DependencyResoolver对象。换言之,我们可以通过自定义DependencyResoolver的方式来实现基于IoC的Controller激活。

同样是采用Ninject,我们定义了一个具有如下定义的NinjectDependencyResolver。与上面定义的NinjectControllerActivator类似,NinjectDependencyResolver具有一个IKernel类型的只读属性Kernel,该属性在构造函数中被初始化。同样是在构造函数中,我们通过该Kernel完成了IEmployeeRepository接口和EmployeeRepository类型的注册。对于实现的GetService和GetServices方法,我们直接调用Kernel的TryGet和GetAll返回指定类型的实例和实例列表。[源代码从这里下载]

   1: public class NinjectDependencyResolver : IDependencyResolver
   2: {
   3:     public IKernel Kernel { get; private set; }
   4:     public NinjectDependencyResolver()
   5:     {
   6:         this.Kernel = new StandardKernel();
   7:         AddBindings();
   8:     }
   9:     private void AddBindings()
  10:     {
  11:         this.Kernel.Bind<IEmployeeRepository>().To<EmployeeRepository>();
  12:     }
  13:  
  14:     public object GetService(Type serviceType)
  15:     {
  16:         return this.Kernel.TryGet(serviceType);
  17:     }
  18:  
  19:     public IEnumerable<object> GetServices(Type serviceType)
  20:     {
  21:         return this.Kernel.GetAll(serviceType);
  22:     }
  23: }

由于默认情况下通过无参构造函数创建的DefaultConrtollerFactory会被使用,所以我们无须进行ControllerFactory的注册。我们只需要创建一个自定义的NinjectDependencyResolver对象并将其作为当前的DependencyResolver即可,定义在Global.asax设置当前DependencyResolver的代码如下所示。

   1: public class MvcApplication : System.Web.HttpApplication
   2: {
   3:     //其他成员
   4:     protected void Application_Start()
   5:     {
   6:         //其他操作
   7:         DependencyResolver.SetResolver( new NinjectDependencyResolver());
   8:     }
   9: }
 
http://www.cnblogs.com/artech/archive/2012/04/01/controller-activation-031.html

0 0