[ASP.NET MVC]视图是如何呈现的
为了搞清楚ASP.NET MVC的请求过程,我们计划从结果追踪到源头。使用VS2012创建一个空白的ASP.NET MVC项目
然后创建一个HelloController
创建一个HelloView。在Views文件夹下创建一个Hello的文件夹,然后创建一个名为Index的View
然后再view中输入hello asp.net mvc4
点击App_Start下的RouteConfig.cs,更改Default的路由的Controller为Hello
OK,点击调适按钮,你可以得到如下结果:
OK.我们就从这里出发,开始分析用户的请求是如何被处理的。首先我们再来看HelloController的代码
恩,我们就从这里开始分析吧。
(1)我们可以看到Index()返回的是一个View()。这个View()来自基类
protectedinternalViewResult View() { return View(viewName: null, masterName: null, model: null); } |
(2) 我们继续找到对应的方法:
protectedinternalvirtualViewResult View(string viewName, string masterName, object model) { if (model != null) { ViewData.Model = model; } returnnewViewResult { ViewName = viewName, MasterName = masterName, ViewData = ViewData, TempData = TempData, ViewEngineCollection = ViewEngineCollection }; } |
通过C#4.0的方式,创建了一个ViewResult对象。通过查看ViewResult类,我们可以发现其继承了ViewResultBase类,我们可以用UML来表示出上面5个属性在两个类之间的关系
(3) 再看第一步,传入的三个参数都为null,那么在创建ViewResult对象时,其他的几个属性ViewData,TempData和ViewEngineCollection的值是从哪里来的呢?
通过上面的图,我们知道,这三个属性均继承自ViewResultBase类,因此我们去分析ViewResultBase类。
ViewData |
get { if (_viewData == null) { _viewData = newViewDataDictionary(); } return _viewData; } set { _viewData = value; } |
TempData |
get { if (_tempData == null) { _tempData = newTempDataDictionary(); } return _tempData; } set { _tempData = value; } |
ViewEngineCollection |
get { return _viewEngineCollection ?? ViewEngines.Engines; } set { _viewEngineCollection = value; } |
到目前为止,我们并没有看到任何代码调用这些属性的Set方法,那么我们推断这三个属性的返回值分别是
ViewData |
newViewDataDictionary(); |
TempData |
newTempDataDictionary(); |
ViewEngineCollection |
ViewEngines.Engines; |
为了验证我们的推断,我们可以通过调试来验证。
恩,这下明确了。没有问题。那么我们现在需要知道ViewEngineCollection这个属性是如何赋值的。
(4) 我们查看ViewEngines这个类的代码
publicstaticclassViewEngines { privatestaticreadonlyViewEngineCollection _engines = newViewEngineCollection { newWebFormViewEngine(), newRazorViewEngine(), }; publicstaticViewEngineCollection Engines { get { return _engines; } } } |
原来如此,原来如此,ViewEngine是一个静态类。仅仅包含了一个静态的构造函数一个静态的属性。返回一个ViewEngineCollection。那么ASP.NET MVS怎么就知道该使用哪个ViewEngine呢?
在RouteConfig.cs文件中,我们注册了默认的Route
routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Hello", action = "Index", id = UrlParameter.Optional } ); |
因此,系统可以知道当前的Controller为HelloController,Action为Index。ASP.NET MVC Framework使用ControllerFactory创建Controller实例,然后通过ControllerActionInvoker通过反射的方式把Action转化为HelloController类Index方法的调用,最后调用ViewResultBase的ExecuteResult方法,把方法返回的结果传递给对应的View,并在该View中把最终结果呈现给用户。
这个过程相当复杂,设计众多的类。我们先来看一下概览图,我只列举了重要的类,
这几个类的生命周期应该是这样子的:
OK,下面我们来详细叙述一下整个过程,并加以代码分析
1)ControllerActionInvoker的InvokeAction被调用。那么它被谁调用呢,通过类图,我们知道,InvokeAction方法是实现了IActionVoker接口,我们通过调用关系图,可以知道该方法是Controller.cs的ExecuteCore方法调用
protectedoverridevoid ExecuteCore() { // If code in this method needs to be updated, please also check the BeginExecuteCore() and // EndExecuteCore() methods of AsyncController to see if that code also must be updated. PossiblyLoadTempData(); try { string actionName = RouteData.GetRequiredString("action"); if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) { HandleUnknownAction(actionName); } } finally { PossiblySaveTempData(); } } |
而Controller类继承了ControllerBase类,ControllerBase类的Execute方法调用了Controll的ExecuteCore方法
protectedvirtualvoid Execute(RequestContext requestContext) { if (requestContext == null) { thrownewArgumentNullException("requestContext"); } if (requestContext.HttpContext == null) { thrownewArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext"); } VerifyExecuteCalledOnce(); Initialize(requestContext); using (ScopeStorage.CreateTransientScope()) { ExecuteCore(); } } |
并且,ControllerBase的Execute方法实现了IController接口,此方法被MvcHandler类处理请求时调用
protectedinternalvirtualvoid ProcessRequest(HttpContextBase httpContext) { SecurityUtil.ProcessInApplicationTrust(() => { IController controller; IControllerFactory factory; ProcessRequestInit(httpContext, out controller, out factory); try { controller.Execute(RequestContext); } finally { factory.ReleaseController(controller); } }); } |
OK,我们就到这里吧,关于用户的请求如何到MvcHandler这里,我将另外写一篇文章来介绍。当然,园子里很多同学也已经写了很多,大家可以去查阅,比如:…..
2)下面,我们来看一下ControllerActionInvkoer类的InvokeAction方法具体做了哪些事情
publicvirtualbool InvokeAction(ControllerContext controllerContext, string actionName) { if (controllerContext == null) { thrownewArgumentNullException("controllerContext"); } if (String.IsNullOrEmpty(actionName)) { thrownewArgumentException(MvcResources.Common_NullOrEmpty, "actionName"); } ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext); ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName); if (actionDescriptor != null) { FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor); try { AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor); if (authContext.Result != null) { // the auth filter signaled that we should let it short-circuit the request InvokeActionResult(controllerContext, authContext.Result); } else { if (controllerContext.Controller.ValidateRequest) { ValidateRequest(controllerContext); } IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor); ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters); InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result); } } catch (ThreadAbortException) { // This type of exception occurs as a result of Response.Redirect(), but we special-case so that // the filters don't see this as an error. throw; } catch (Exception ex) { // something blew up, so execute the exception filters ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex); if (!exceptionContext.ExceptionHandled) { throw; } InvokeActionResult(controllerContext, exceptionContext.Result); } returntrue; } // notify controller that no method matched returnfalse; } |
最重要的代码,我用黄色highlights出来了。下面我们来分析一下这三行代码
2)A 检查Action是否有Authroization特性,如果有进行验证。验证的具体代码如下
if (AuthorizeCore(filterContext.HttpContext)) { HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache; cachePolicy.SetProxyMaxAge(newTimeSpan(0)); cachePolicy.AddValidationCallback(CacheValidateHandler, null/* data */); } else { HandleUnauthorizedRequest(filterContext); } |
如果验证成功,那么AuthorizationContext的Result属性为NULL,否则返回newHttpUnauthorizedResult();HttpUnauthorizedResult继承HttpStatusCodeResult,而HttpStatusCodeResult继承ActionResult。如果验证失败,那么调用InvokeActionResult方法。
protectedvirtualvoid InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult) { actionResult.ExecuteResult(controllerContext); } |
可见,InvokeActionResult方法,实际调用ActionResult的ExecuteResult方法。在当前的情况下(验证失败),此时的authContext.Result的具体类型是HttpUnauthorizedResult,它继承了HttpStatusCodeResult, 所以InvokeActionResult进入的是HttpStatusCodeResult类的ExecuteResult方法
publicoverridevoid ExecuteResult(ControllerContext context) { if (context == null) { thrownewArgumentNullException("context"); } context.HttpContext.Response.StatusCode = StatusCode; if (StatusDescription != null) { context.HttpContext.Response.StatusDescription = StatusDescription; } } |
设置完StatusCode和StatusDescription之后,将直接返回,不会寻找对应的View。
2)B 如果不涉及验证,或者验证成功。那么首先获取action的参数IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
比如我们当前的请求为/Hello/Index/2013
那么parameters的值为[id, 2013].请注意这个和你注册RouteData的格式相关联。如果你RouteData注册为为{controlller}/{action}/{no},那么parameters的值为[no,2013]
接着,获取postActionContext对象ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);该方法包括两个主要的步骤:其一为创建ActionExecutedContext,其二为调用与Action对应的Controller的方法,并将方法的结果保存在ActionResult之上。这个ActionResult的具体类型可能是ContentResult,ViewResult,或者JsonResult等等继承了ActionResult的各个子类。
为了模拟这个过程,我们创建如下两行代码模拟上面过程的执行结果:
// 返回ContentResult
ActionResult result1 = CreateActionResult(ControllerContext, actionDescriptor, "123");
// 返回ViewResult
ActionResult result2 = CreateActionResult(ControllerContext, actionDescriptor, View());
OK,如果是ContentResult,那么它的ExecuteResult方法如下
publicoverridevoid ExecuteResult(ControllerContext context) { if (context == null) { thrownewArgumentNullException("context"); } HttpResponseBase response = context.HttpContext.Response; if (!String.IsNullOrEmpty(ContentType)) { response.ContentType = ContentType; } if (ContentEncoding != null) { response.ContentEncoding = ContentEncoding; } if (Content != null) { response.Write(Content); } } |
可见,直接把内容输出到浏览器
如果是ViewResult,那么首先调用基类ViewResultBase的ExecuteResult方法
publicoverridevoid ExecuteResult(ControllerContext context) { if (context == null) { thrownewArgumentNullException("context"); } if (String.IsNullOrEmpty(ViewName)) { ViewName = context.RouteData.GetRequiredString("action"); } ViewEngineResult result = null; if (View == null) { result = FindView(context); View = result.View; } TextWriter writer = context.HttpContext.Response.Output; ViewContext viewContext = newViewContext(context, View, ViewData, TempData, writer); View.Render(viewContext, writer); if (result != null) { result.ViewEngine.ReleaseView(context, View); } } |
该方法调用ViewResult的FindView方法
protectedoverrideViewEngineResult FindView(ControllerContext context) { ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName); if (result.View != null) { return result; } // we need to generate an exception containing all the locations we searched StringBuilder locationsText = newStringBuilder(); foreach (string location in result.SearchedLocations) { locationsText.AppendLine(); locationsText.Append(location); } thrownewInvalidOperationException(String.Format(CultureInfo.CurrentCulture, MvcResources.Common_ViewNotFound, ViewName, locationsText)); } |
自此,Controller的执行结果与View建立关联,最后ASP.NET MVC Framework把结果通过对应的视图显示到用户的浏览器中
FindView包含三个参数:context这个是ControllerContext;第二个是ViewName,它的值为ViewName = context.RouteData.GetRequiredString("action"),其实就是Action的值;第三个是MasterName,我们的例子中为空。
那么如何找到WebFormView呢?
我们在HomeController中创建一个List方法,并在View/Hello文件夹下创建List.aspx文件
publicActionResult List() { return View(); } |
然后执行调适:
由于当前默认的请求是/Hello/Index,因此MVC Framework会自动寻找
~/Views/Hello/Index.cshtml
~/Views/Hello/Index.vbhtml
~/Views/Shared/Index.cshtml
~/Views/Shared/Index.cshtml
实际上,Razor引擎不会真正的在硬盘上寻找上面的文件,因为,这些文件都已经编译成C#的类。所以Razor在编译后的类中寻找对应的视图。
Next
- How the controller is initialized?
- How the request comes to MvcHandler
- View, IView, ViewEngine, RazorViewEngile, RazorView
大多人应该都知道用户向IIS发送一个ASP.NET请求后,IIS处理请求并向用户返回对应的结果。也有人知道,当一个ASP.NET请求到达IIS后,进入CLR,然后由HttpApplication创建HttpContext并找到对应的HttpHandler处理请求。最后把结果返回到用户端。那么从进入CLR之后,一个ASP.NET请求的生命周期具体是怎么样的,要经历那些重要的对象呢? 本文主要介绍这两个方面。
ASP.NET Request Liftcycle
首先,我们来看看一个request在进入CLR之前,发生了什么?
在IIS6和IIS7中,所有的HTTP请求均由HTTP侦听器捕获。那么HTTP侦听器是什么,其实就是http.sys,它运行在内核级别。至于具体什么是内核模块,以及如何运行,超出本文的范畴。如果你有兴趣,请自己查阅MSDN.
HTTP侦听器把捕获到的HTTP请求放到对应的应用程序池的请求队列中。所谓应用程序池
HttpRuntime, HttpApplicationFactory, HttpApplication,HttpContext, HttpHandler, HttpModule
动态编译
[ASP.NET MVC]视图是如何呈现的的更多相关文章
- [ASP.NET MVC]视图是如何呈现的 (续)
在上一篇文章中,我们知道了通过Controller执行ActionResult的Execute可以找到对应Controler对应的ViewEngine,然后在View中把Action的结果显示出来.那 ...
- ASP.NET MVC 视图(五)
ASP.NET MVC 视图(五) 前言 上篇讲解了视图中的分段概念.和分部视图的使用,本篇将会对Razor的基础语法简洁的说明一下,前面的很多篇幅中都有涉及到视图的调用,其中用了很多视图辅助器,也就 ...
- ASP.NET MVC 视图(四)
ASP.NET MVC 视图(四) 前言 上篇对于利用IoC框架对视图的实现进行依赖注入,最后还简单的介绍一下自定义的视图辅助器是怎么定义和使用的,对于Razor语法的细节和辅助器的使用下篇会说讲到, ...
- ASP.NET MVC 视图(一)
ASP.NET MVC 视图(一) 前言 从本篇开始就进入到了MVC中的视图部分,在前面的一些篇幅中或多或少的对视图和视图中的一些对象的运用进行了描述,不过毕竟不是视图篇幅说的不全面,本篇首先为大家讲 ...
- Asp.net MVC 视图引擎
Asp.net MVC视图引擎有两种: 1.ASPX View Engine 这个做过WebForm的人都清楚 设计目标:一个用于呈现Web Form页面的输出的视图引擎. 2.Razor View ...
- ASP.NET MVC 之Model的呈现
ASP.NET MVC 之Model的呈现(仅此一文系列三) 本文目的 我们来看一个小例子,在一个ASP.NET MVC项目中创建一个控制器Home,只有一个Index: public class H ...
- 【ASP.NET MVC系列】浅谈ASP.NET MVC 视图
ASP.NET MVC系列文章 [01]浅谈Google Chrome浏览器(理论篇) [02]浅谈Google Chrome浏览器(操作篇)(上) [03]浅谈Google Chrome浏览器(操作 ...
- ASP.NET MVC 视图(三)
ASP.NET MVC 视图(三) 前言 上篇对于Razor视图引擎和视图的类型做了大概的讲解,想必大家对视图的本身也有所了解,本篇将利用IoC框架对视图的实现进行依赖注入,在此过程过会让大家更了解的 ...
- ASP.NET MVC 视图(二)
ASP.NET MVC 视图(二) 前言 上篇中对于视图引擎只是做了简单的演示,对于真正的理解视图引擎的工作过程可能还有点模糊,本篇将会对由MVC框架提供给我们的Razor视图引擎的整个执行过程做一个 ...
随机推荐
- boolean和Boolean, char和Character , byte和Byte, short和Short, int和Integer , long和Long , float和Float, double和Double的区别 , String和StringBuffer的区别
Java提供两种不同的类型:引用类型和原始类型(内置类型).Int是java的原始数据类型,Integer是java为int提供的封装类. Java为每个原始数据类型提供了封装类. 其中原始数据类型封 ...
- linux 监控工具netdata
1. 背景 工作的关系,需要使用netdata将服务器信息实时.动态展示. 调研了netdata工具,记录一下,方便后续使用. 2. netdata介绍 2.1 netdata 能做什么? 可以参考: ...
- windows 2003 iis 360防黑加固后不能使用
最近在使用360的防黑加固加固2003系统,发现IIS居然不能够使用了,报401.1错误,查找解决方案如下: 1.我的电脑-〉属性-〉管理-〉本地用户和组,查看IUSER用户是否开启,如果未开启开启后 ...
- DHCP服务原理与搭建(Linux系统+路由器,二选一方案)
大家都知道上网的最基本前提是要在终端上设置IP.子网掩码.网关.DNS等地址信息,在家里或者在办公室很多时候打开电脑后发现就可以上网,并没有手动设置IP.掩码.DNS地址也能上网,这是什么原因呢?其实 ...
- ios mac 对照片进行JPEG压缩
ios mac 对照片进行JPEG压缩 1. 在iOS上可以使用 API UIImageJPEGRepresentation 对照片数据进行JPEG压缩: 我们知道iOS其实是MAC OS 的移植,那 ...
- Extjs的grid的单元格中加载超链接和按钮
效果: 户型图列显示的图片实际上就是一个超链接. 添加一个Button分2个步骤:1.在列头中定义超链接列或者Button列的HTML代码,也就是Render 2.添加该Button的事件处理函数.其 ...
- 使用vue.js路由踩到的一个坑Unknown custom element
在配合require.js使用vue路由的时候,遇到了路由组件报错: “vue.js:597 [Vue warn]: Unknown custom element: <router-link&g ...
- Qt编译错误“GL/gl.h:No such file or directory”的解决方法
备注:1)操作系统:Ubuntu-14.04或12.042)Linux用户:root3)Qt版本:qt-linux-opensource-5.2.0-x86 为了迎接Qt的新纪元(从诺基亚移居到芬兰公 ...
- python3用BeautifulSoup用limit来获取指定数量的a标签
# -*- coding:utf-8 -*- #python 2.7 #XiaoDeng #http://tieba.baidu.com/p/2460150866 #标签操作 from bs4 imp ...
- 在Asp.Net中操作PDF – iTextSharp - 操作图片
iTextSharp支持所有主流的图片格式,比如:jpg, tif, gif, bmp, png和wmf.在iTextSharp中使用Image.GetInstance()方法创建图片有很多种方式,或 ...