一.MvcHandler——Controller——Action
先用反编译工具看看MvcHandler的内部:
先说明一下,MvcHandler是继承自IHttpHandler的,先看一下IHttpHandler这个接口:
顺藤摸瓜,进到MvcHandler:
再看ProcessRequestInit方法:
从上面看出,ProcessRequestInit方法是控制器实例的创建,上面是MvcHandler——Controller的实现过程。
下面介绍Controller——Action。回到RrocessRequest方法,接下来它执行了Execute方法
找Execute方法,但此方法不是在Controller类中,不是因为写错了,其实它在父类ControllerBase类中。看Execute方法:
ExecuteCore():
总结一下:
先找控制器和方法上面写的特性,如果存在这个特性,则调用其中的方法,如果调用的结果错了, 则跳到指定的页面或者相应的处理;如果通过了,就顺序执行特性,这就是AOP。画个图形就是这样的:
routedata里面是什么呢?
上面已经完成了MvcHandler——Controller——Action,下面介绍Action——View。
二.Action——View
ViewResult是怎样调用到了View文件夹并生成了一个页面呢?
ViewResult的父类是ViewResultBase,ViewResultBase的父类是ActionResult,所以上面的方法
public ViewResult Modules() 可以替换成 public ActionResult Modules()
用反编译工具看ActionResult
再看ViewResultBase以及它的ExecuteResult方法:
Render方法:实例化一个ViewContext,并渲染到output中去。
就是这样生成了一个cshtml页面吗?
写个方法测试一下:
1.先写控制器里面的方法ViewShow()
2.对应的ViewShow.cshtml页面
3.调用的后台ListViewAssemblies()方法
运行,显示结果为:
用反编译工具打开这个文件夹下面其中一个dll:
这说明了cshtml页面,在程序中它就是一个类,例如:
看到没?类文件对应了ViewShow.cshtml页面,而在ViewShow.cshtml上显示的html代码,都可以在类文件的Execute()方法中找到,莫非cshtml页面就是类文件通过Execute()方法,在后台拼装出来的?就像拼装字符串一样?
是的!在程序运行时,视图会被编译成一个类,而且一个文件夹就是一个dll,为什么说一个文件夹就是一个dll?
先访问Home/Index,文件夹下产生了一个新的dll。
用反编译工具打开这个dll:
所以,每一个文件夹被编译成一个dll,但不是一次性把文件夹全部生成dll,是访问哪个,就生成哪个dll,如果修改了cshtml的内容,dll文件是可以自动编译生成新的dll。
这个Model是干什么的?
从这里开始,我们知道前台的cshtml页面其实也是类文件通过拼装html生成的,以前我以为,我们的程序分前台和后台,后台将数据生成好,送到前台去显示。现在我才知道,其实在后台的控制器中,return View(),返回了一个View类,这个View类,就是按照路径来生成的,像_Page_Views_home_About_cshtml这样名字的类,并把viewData,model什么的,做了赋值,然后把View类里面的Execute方法执行,把html的东西,做了拼装,把初始化赋值的数据,生成了html,所以没有前台,都是后台折腾来折腾去。
js代码和后台一点关系都没有,js只对浏览器生效,后台将js代码当作一段字符串,拼装到cshtml页面上去,让浏览器执行。
三.View的扩展
很多情况下,我们要兼顾PC端和移动端,或者双语版本,怎么做呢?讨论一下一套后台两套界面的做法。
当前是什么客户端,就要返回什么视图,即只有一个控制器,但是要返回不同视图。现在我们在View下新建一套视图:
然后自定义引擎。
讲一下流程,首先把默认的引擎替换成我们自定义的CustomerViewEngine,当发起浏览器请求,会调用FindView方法,在CustomerViewEngine中查找视图,我们用SetEngine方法设置一下引擎,检查浏览器,如果是Chrome65,则保持默认模版,如果不是,则使用新模版,即带有Theme/Eleven路径的cshtml页面。
执行一下:
还可以判断是不是移动端,判断代码如下:
-
public static bool IsMobile(this HttpRequestBase request)
-
{
-
string userAgent = request.ServerVariables["HTTP_USER_AGENT"];
-
Regex b = new Regex(@"(android|bbd+|meego).+mobile|avantgo|bada/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)/|plucker|pocket|psp|series(4|6)0|symbian|treo|up.(browser|link)|vodafone|wap|windows ce|xda|xiino", RegexOptions.IgnoreCase | RegexOptions.Multiline);
-
Regex v = new Regex(@"1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-", RegexOptions.IgnoreCase | RegexOptions.Multiline);
-
return (b.IsMatch(userAgent) || v.IsMatch(userAgent.Substring(0, 4)));
-
}
四.MVC运行全生命周期:
1.一个MVC网站项目,在第一次启动时候,运行Application_Start,初始化整个网站。
2.注册路由。在内存中放置了一个容器,里面是一大堆key-value的东西,其中key就是一个url规则,value是一个MvcRouteHandler的东西,而MvcRouteHandler里面直接写死了一个MvcHandler,说的更加直观的话,就是网站启动的时候,我们指定了规则,只要满足这个规则的url,统一通过MvcHandler来处理。
3.网站响应请求,请求进入Application对象以后,会按照顺序执行22个事件,在MapRequestHandler之前呢,有一个PostResolveRequestCache,在这个事件上,我们注册了一个Module,就是UrlRoutingModule,它是干什么的呢?任何一个请求都要经过所有的Module,所以它看看请求是不是存在的物理文件,如果是,则不管,按照原始流程走,也就是WebForm方式。如果文件不存在,就拿着路由一个一个检查,匹配刚才的路由规则,找到第一个就返回了。拿到了规则(key)以后,也就找到了MvcRouteHandler(value),而MvcRouteHandler写死了一个MvcHandler,也意味着找到了一个HttpHandler,拿到了HttpHandler,就ProcessRequest一下,处理的过程是找到了控制器名称,找了一个ControllerFactory(控制器工厂),用控制器工厂创建一个控制器实例,同时去路由那里得到action(方法)名称,有了这两个东西,就可以想办法反射调用一下,怎样反射调用呢?在ActionInvoker里面,ActionInvoker又在哪里呢?控制器(controller)里面有个CreateActionInvoker方法,帮我们创建了ActionInvoker,拿着这个ActionInvoker又怎么去调用方法呢?它其实是一个 ControllerActionInvoker的类,里面有个InvokerAction的方法,怎样用它来调用action呢?首先检查特性,有没有权限特性,如果有则调用认证特性的OnAuthorization方法,如果这个过程中,把结果赋值了,就认为处理结束了,直接返回结果,如果没有结果,意味着权限是ok的,继续往下走,下面还有好多filter,异常filter,ResultFilter,讲它们编译成委托,叠加在一起顺序执行,这样就把特性和action都执行了一遍。action虽然执行了,但是只是执行了业务逻辑和数据库交互,最后还要生成页面,生成页面的过程是要把action执行了excute,把数据传给了一个和cshtml页面同名的类,执行这个类,把Html生成拼装成一个字符串,返回到response里面去,说白了,最终的请求也就是把cshtml里面的excute方法执行了一遍。
结合《ASP.NET MVC4 开发指南》一书第4章 Routing与ASP.NET MVC生命周期,得到一个这样顺序的生命周期:
Request ——Url Routing Module(管道模型中任何请求都要过Module,如果能请求到实体文件,看做是webform方式,访问实体文件)——Route(匹配路由规则)——MvcRoutingHandler(请求都是要让handler来处理)——MvcHandler(它是专门处理MVC的Handler,Handler都有一个方法ProccessRequest)——Controller Factory——Controller——Action(在routedata中有保存controller:action的一一对应信息,同时也找action上面的特性,完成AOP)——View(Action方法返回值为ActionResult,ActionResult的Execute方法有ViewEngineResult,就能找到View,并且用Render方法输出到output)——Response。