[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视图引擎的整个执行过程做一个 ...
随机推荐
- 【springboot】【socket】spring boot整合socket,实现服务器端两种消息推送
参考地址:https://www.cnblogs.com/hhhshct/p/8849449.html
- linux-提示用户不在 sudoers文件中,此事将被报告。
在安装oracle创建用户后使用su – oracle命令进入执行相关修改信息(vi file命令)时提示无权限修改 “Can't open file for writing”或“operation ...
- iOS 录制视频MOV格式转MP4
使用UIImagePickerController系统控制器录制视频时,默认生成的格式是MOV,如果要转成MP4格式的,我们需要使用AVAssetExportSession; 支持转换的视频质量:低, ...
- Git操作简单入门及相关命令
说明:本文内容主要来自文末参考链接内容,此文仅作学习记录.如有转载,请到文末参考链接处. 1 基本概念理解 1.1 Git介绍 Git是分布式版本控制系统. 集中式VS分布式,SVN VS Git. ...
- sublime text3中文文件名显示为框框,怎么解决
点击Preferences选项——settings { "font_size": 20, "ignored_packages": [ ...
- 查看最新的Google地址
nslookup www.google.com 8.8.8.8
- swift4.0 对 afn 进行二次封装
先将 afn 用pod导入到 工程中 创建一个类 ZHttpTools 继承自 AFHTTPSessionManager 一般我们不希望网络请求同时有多个存在,所以我们将这个工具类 设计成单例 代 ...
- [MySQL]对于事务并发处理带来的问题,脏读、不可重复读、幻读的理解
一.缘由 众所周知MySQL从5.5.8开始,Innodb就是默认的存储引擎,Innodb最大的特点是:支持事务.支持行级锁. 既然支持事务,那么就会有处理并发事务带来的问题:更新丢失.脏读.不可重复 ...
- 基于HTML5全屏图文左右滑动切换特效
基于HTML5全屏图文左右滑动切换特效.这是一款基于jQuery+HTML5实现的全屏图文切换特效代码.效果图如下: 在线预览 源码下载 实现的代码. html代码: <main> & ...
- Navi.Soft31.微信WinForm框架(含下载地址)
1.概述 1.1应用场景 微信的出现,改变了我们日常生活.而微信公众平台的出现,使我们每个人都可能成为一个品牌,微商的普及.微店越来越多.越来越多的人,借助微信公众平台,实现了自己的梦想 在此背景环境 ...