经过前面介绍了路由系统之后,我们知道任何一个请求在经过asp.net url路由系统的拦截之后,会生成以controller/action 名称为核心的路由数据。asp.net mvc 根据此解析出目标controller 的类型,并最终激活具体的controller实例处理当前请求。

   为了解释从请求到执行目标controller的过程,我们调式源码(具体怎么调试源码可以参照我这篇文章 ),在MvcHandler里面的IHttpAsyncHandler.BeginProcessRequest处设置断点

  1. IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
  2. {
  3.   return BeginProcessRequest(context, cb, extraData);
  4. }

大家都知道,asp.net运行机制是管道模型,asp.net mvc也同样遵循管道模型,查看MvcHandler代码,可以看出MvcHandler实现了接口IHttpHandler。此时我们继续深挖代码,找到下面代码片段

  1. protected internal virtual IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state)
  2. {
  3.   IController controller;
  4.   IControllerFactory factory;
  5.   ProcessRequestInit(httpContext, out controller, out factory);
  6.  
  7.   IAsyncController asyncController = controller as IAsyncController;
  8.   if (asyncController != null)
  9.   {
        //to do
  10.   }
  11.   else
  12.   {
        //to do
  13.   }
  14. }

此处代码便是创建了Controller,也正是这篇文章所要阐述的主题。

  此时,再进一步分析“BeginProcessRequest”这个方法,阅读前三行代码,我们大概可以得知Controller 是在这个方法中创建的。那么在这个方法中找到“ProcessRequestInit”此方法,其中后面两个参数“controller”,“factory”都是带有前缀“out”的,也就是说两参数是通过引用来传递值。那么我们暂且不谈“BeginProcessRequest”在获取“IController”实例 、“IControllerFactory”实例之后会做什么后续处理,直接调试进入方法“ProcessRequestInit”,看它里面到底发生了什么,我们将“ProcessRequestInit”的方法实现贴出来

  1. private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory)
  2. {
  3.   // If request validation has already been enabled, make it lazy. This allows attributes like [HttpPost] (which looks
  4.   // at Request.Form) to work correctly without triggering full validation.
  5.   // Tolerate null HttpContext for testing.
  6.   HttpContext currentContext = HttpContext.Current;
  7.   if (currentContext != null)
  8.   {
  9.   bool? isRequestValidationEnabled = ValidationUtility.IsValidationEnabled(currentContext);
  10.   if (isRequestValidationEnabled == true)
  11.   {
  12.   ValidationUtility.EnableDynamicValidation(currentContext);
  13.   }
  14.   }
  15.  
  16.   AddVersionHeader(httpContext);
  17.   RemoveOptionalRoutingParameters();
  18.  
  19.   // Get the controller type
  20.   string controllerName = RequestContext.RouteData.GetRequiredString("controller");
  21.  
  22.   // Instantiate the controller and call Execute
  23.   factory = ControllerBuilder.GetControllerFactory();
  24.   controller = factory.CreateController(RequestContext, controllerName);
  25.   if (controller == null)
  26.   {
  27.   throw new InvalidOperationException(
  28. String.Format(
  29. CultureInfo.CurrentCulture,
  30. MvcResources.ControllerBuilder_FactoryReturnedNull,
  31. factory.GetType(),
  32. controllerName));
  33.   }
  34. }

在这个方法里面,我们将注意力集中在这两句注释“Get the controller type”、“Instantiate the controller and call Execute”所对应的代码。

  首先,程序先获取到控制的名称。我们在阅读Mvc的源码的时候,可以发现获取控制器的名称有两种方式,至于为何没有统一写法,我也不知道,虽然结果都是一样的。

  1. RequestContext.RouteData.GetRequiredString("controller");
  2. (string)requestContext.RouteData.Values["controller"];

那么在获取到了控制器的名称之后,我们大胆猜想一下,获取控制器的实例是不是运用了反射了?

  然后,在获取到了控制器名称之后,程序就会执行实例化控制器并且调用。在这一步中涉及到了两个关键的类“ControllerBuilder”、实现IControllerFactory的类“DefaultControllerFactory”,此时我们继续调试下面那一行代码

  1. factory = ControllerBuilder.GetControllerFactory();

通过字面上应该可以猜出是通过“ControllerBuilder”获取控制器工厂。ControllerBuilder属性是定义在Mvchandler中,通过属性注入ControllerBuilder对象,此时调试进入了ControllerBuilder内部,我们看一下GetControllerFactory()的实现细节

  1. private static ControllerBuilder _instance = new ControllerBuilder();
  2. private Func<IControllerFactory> _factoryThunk = () => null;
  3. public ControllerBuilder()
  4. : this(null)
  5. {
  6. }
  7.  
  8. internal ControllerBuilder(IResolver<IControllerFactory> serviceResolver)
  9. {
  10.    _serviceResolver = serviceResolver ?? new SingleServiceResolver<IControllerFactory>(
  11. () => _factoryThunk(),
  12. new DefaultControllerFactory { ControllerBuilder = this },
  13. "ControllerBuilder.GetControllerFactory");
  14. }
  15. public IControllerFactory GetControllerFactory()
  16. {
  17.   return _serviceResolver.Current;
  18. }

此处只截取部分关键代码,查看“IResolver<T>”泛型接口,只有一个属性“T Current { get; }”,也就是返回当前T对象,那么对于方法“GetControllerFactory()”应该很快会领会到它是用来返回当前实现接口“IControllerFactory”的实例对象“DefaultControllerFactory ”,也就是说“DefaultControllerFactory ”是通过这种松散耦合的方式创建的。

  在MvcHandler中获取到了“factory”对象之后,紧接着就是通过当前上下文对象,控制器名称创建控制器的实例

  1. controller = factory.CreateController(RequestContext, controllerName);

我们调试代码进入“DefaultControllerFactory ”的“CreateController”方法里面,如下

  1. public virtual IController CreateController(RequestContext requestContext, string controllerName)
  2. {
  3.   if (requestContext == null)
  4.   {
  5.   throw new ArgumentNullException("requestContext");
  6.   }
  7.   if (String.IsNullOrEmpty(controllerName))
  8.   {
  9.   throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
  10.   }
  11.   Type controllerType = GetControllerType(requestContext, controllerName);
  12.   IController controller = GetControllerInstance(requestContext, controllerType);
  13.   return controller;
    }

这段代码里面,先是获取当前控制器名称对应的类型,然后通过控制器类型获取控制器实例对象。代码定位到方法“GetControllerType”里面,如下

  1. protected internal virtual Type GetControllerType(RequestContext requestContext, string controllerName)
  2. {
  3. if (String.IsNullOrEmpty(controllerName))
  4. {
  5. throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
  6. }
  7.  
  8. // first search in the current route's namespace collection
  9. object routeNamespacesObj;
  10. Type match;
  11. if (requestContext != null && requestContext.RouteData.DataTokens.TryGetValue("Namespaces", out routeNamespacesObj))
  12. {
  13. IEnumerable<string> routeNamespaces = routeNamespacesObj as IEnumerable<string>;
  14. if (routeNamespaces != null && routeNamespaces.Any())
  15. {
  16. HashSet<string> namespaceHash = new HashSet<string>(routeNamespaces, StringComparer.OrdinalIgnoreCase);
  17. match = GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, namespaceHash);
  18.  
  19. // the UseNamespaceFallback key might not exist, in which case its value is implicitly "true"
  20. if (match != null || false.Equals(requestContext.RouteData.DataTokens["UseNamespaceFallback"]))
  21. {
  22. // got a match or the route requested we stop looking
  23. return match;
  24. }
  25. }
  26. }
  27.  
  28. // then search in the application's default namespace collection
  29. if (ControllerBuilder.DefaultNamespaces.Count > )
  30. {
  31. HashSet<string> namespaceDefaults = new HashSet<string>(ControllerBuilder.DefaultNamespaces, StringComparer.OrdinalIgnoreCase);
  32. match = GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, namespaceDefaults);
  33. if (match != null)
  34. {
  35. return match;
  36. }
  37. }
  38.  
  39. // if all else fails, search every namespace
  40. return GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, null /* namespaces */);
  41. }

在这段代码里面,我们只重点关注通过名称空间匹配控制器类型,也是我们经常会遇到的错误,如图

这个在上一篇介绍路由的时候已经提到了。

  当已经获取到了控制器类型之后,程序就通过该类型获取控制的实例对象,此时调试代码进入方法“GetControllerInstance”,如下

  1. protected internal virtual IController GetControllerInstance(RequestContext requestContext, Type controllerType)
  2. {
  3. if (controllerType == null)
  4. {
  5. throw new HttpException(,
  6. String.Format(
  7. CultureInfo.CurrentCulture,
  8. MvcResources.DefaultControllerFactory_NoControllerFound,
  9. requestContext.HttpContext.Request.Path));
  10. }
  11. if (!typeof(IController).IsAssignableFrom(controllerType))
  12. {
  13. throw new ArgumentException(
  14. String.Format(
  15. CultureInfo.CurrentCulture,
  16. MvcResources.DefaultControllerFactory_TypeDoesNotSubclassControllerBase,
  17. controllerType),
  18. "controllerType");
  19. }
  20. return ControllerActivator.Create(requestContext, controllerType);
  21. }

此方法很简单,先是判断控制器类型是否为空对象,然后判断当前的控制器实例是否可以从指定 “IController”的实例分配,当两个条件都过了,此时通过内部的私有类“DefaultControllerActivator”来创建控制器实例了,如下

  1. public IController Create(RequestContext requestContext, Type controllerType)
  2. {
  3.   try
  4.   {
  5.   return (IController)(_resolverThunk().GetService(controllerType) ?? Activator.CreateInstance(controllerType));
  6.   }
  7.   catch (Exception ex)
  8.   {
  9.   throw new InvalidOperationException(
  10. String.Format(
  11. CultureInfo.CurrentCulture,
  12. MvcResources.DefaultControllerFactory_ErrorCreatingController,
  13. controllerType),
  14. ex);
  15.   }
  16. }

各位看官,看清楚了吧,控制器实例然来是这样被创建并激活的。

  

  

Asp.Net Mvc5 之Controller的更多相关文章

  1. ASP.NET MVC5学习笔记之Controller同步执行架构分析

    在开始之前,声明一下,由于ASP.NET MVC5正式发布了,后面的分析将基于ASP.NET MVC5最新的源代码.在前面的内容我们分析了怎样根据路由信息来确定Controller的类型,并最终生成C ...

  2. 二、 添加控制器Controller(ASP.NET MVC5 系列)

    MVC是Model-View-Controller的简写.MVC是一种开发良好架构,可测试,易维护应用程序的设计模式.据于MVC的应用程序应该包含: Models: 是呈现应用程序数据和使用验证逻辑给 ...

  3. ASP.NET MVC5基础-控制器(Controller)详解

    在上文ASP.NET MVC5基础 – MVC文件架构中我们简单了解了下控制器Controller的作用,本文我将详细介绍控制器Controller的使用方法. Controller的运行过程 上文我 ...

  4. ASP.NET MVC5 ModelBinder

    什么是ModelBinding ASP.NET MVC中,所有的请求最终都会到达某个Controller中的某个Action并由该Action负责具体的处理和响应.为了能够正确处理请求,Action的 ...

  5. ASP.NET MVC5 网站开发实践(二) Member区域 - 咨询管理的架构

    咨询.留言.投诉等功能是网站应具备的基本功能,可以加强管理员与用户的交流,在上次完成文章部分后,这次开始做Member区域的咨询功能(留言.投诉都是咨询).咨询跟文章非常相似,而且内容更少.更简单. ...

  6. ASP.NET MVC5 网站开发实践(二) Member区域 - 文章管理架构

    上次把member的用户部分完成,现在开始做文章管理部分.文章部分根据涉及显示现实文章列表,发布文章,修改文章,删除文章等功能.最终的实现目标是使用权限来控制用户是否能进行相应操作,管理员权限的会显示 ...

  7. ASP.NET MVC5 网站开发实践(一) - 框架(续) 模型、数据存储、业务逻辑

    上次搭建好了项目框架,但还是觉得不太对劲,后来才想起来没有对开发目标进行定位,这个小demo虽然不用做需求分析,但是要实现什么效果还得明确.后来想了一下就做个最简单的网站,目标定为小公司进行展示用的网 ...

  8. ASP.NET MVC5 网站开发实践(一) - 项目框架

    前几天算是开题了,关于怎么做自己想了很多,但毕竟没做过项目既不知道这些想法有无必要,也不知道能不能实现,不过邓爷爷说过"摸着石头过河"吧.这段时间看了一些博主的文章收获很大,特别是 ...

  9. asp.net mvc5 伪静态

    asp.net mvc5 伪静态 WebForm Mvc4和5通用 1.背景:老项目WebForm开发 需要 融合到新项目Mvc5开发 2.需求:Url地址TruckDetail.aspx?id=45 ...

随机推荐

  1. Android开发之技术文章索引

    Activity: 1.PreferenceActivity Fragment: 1.fragment中套用PagerSlidingTabStrip,切换底部时viewpager消失的解决 Widge ...

  2. Zookeeper命令

    常用命令 ZooKeeper 支持某些特定的四字命令字母与其的交互.它们大多是查询命令,用来获取 ZooKeeper 服务的当前状态及相关信息.用户在客户端可以通过 telnet 或 nc 向 Zoo ...

  3. fiddler代理

    对于前段开发人员,fiddler 是一个必不可少的调试神器.下载地址:http://www.telerik.com/download/fiddler. 有同学也许会碰到,chrome 下 fiddle ...

  4. Java 简单登录MVC

    构建一个简单的基于MVC模式的JavaWeb 零晨三点半了,刚刚几个兄弟一起出去吼歌,才回来,这应该是我大学第二次去K歌,第一次是大一吧,之后每次兄弟喊我,我都不想去,因为我还是很害怕去KTV,或许是 ...

  5. [VC6]ONMESSAGE()宏编译时出现"sytax error ;"错误时

    自定义消息时编译出错,经排查,在定义消息的头文件里 #define WM_XXX (WM_USER+1000); 最后多加了一个分号引起. 吐血.

  6. NoSql数据库使用半年后在设计上面的一些心得 (转)

    http://www.cnblogs.com/AllenDang/p/3507821.html NoSql数据库这个概念听闻许久了,也陆续看到很多公司和产品都在使用,优缺点似乎都被分析的清清楚楚.但我 ...

  7. mysql CMAKE 参数说明

    MySQL自5.5版本以后,就开始使用CMake编译工具了,因此,你在安装源文件中找不到configure文件是正常的.很多人下到了新版的MySQL,因为找不到configure文件,不知道该怎么继续 ...

  8. [Everyday Mathematics]20150103

    试求极限$$\bex \vlm{n} \sez{\int_1^{e^2}\sex{\frac{\ln x}{x}}^n\rd x}^\frac{1}{n}.\eex$$

  9. 枚举类型的单例模式(java)

    Inspired by Effective Java. Singleton模式是在编程实践中应用最广泛的几种设计模式之一.以前知道的,实现单例的方法有两种(下面的A.B).刚刚在读<Effect ...

  10. lightoj 1224

    很简单的Trie树,记录每个前缀的次数,dfs一次Trie即可 #include<set> #include<map> #include<list> #includ ...