[ASP.NET MVC]视图是如何呈现的

为了搞清楚ASP.NET MVC的请求过程,我们计划从结果追踪到源头。使用VS2012创建一个空白的ASP.NET MVC项目

[ASP.NET MVC]视图是如何呈现的_第1张图片

然后创建一个HelloController

[ASP.NET MVC]视图是如何呈现的_第2张图片

创建一个HelloView。在Views文件夹下创建一个Hello的文件夹,然后创建一个名为Index的View

[ASP.NET MVC]视图是如何呈现的_第3张图片

然后再view中输入hello asp.net mvc4

点击App_Start下的RouteConfig.cs,更改Default的路由的Controller为Hello

OK,点击调适按钮,你可以得到如下结果:
[ASP.NET MVC]视图是如何呈现的_第4张图片

 

OK.我们就从这里出发,开始分析用户的请求是如何被处理的。首先我们再来看HelloController的代码

[ASP.NET MVC]视图是如何呈现的_第5张图片

恩,我们就从这里开始分析吧。

(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个属性在两个类之间的关系
[ASP.NET MVC]视图是如何呈现的_第6张图片

(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;

 

为了验证我们的推断,我们可以通过调试来验证。
[ASP.NET MVC]视图是如何呈现的_第7张图片

恩,这下明确了。没有问题。那么我们现在需要知道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中把最终结果呈现给用户。

这个过程相当复杂,设计众多的类。我们先来看一下概览图,我只列举了重要的类,

[ASP.NET MVC]视图是如何呈现的_第8张图片

 

这几个类的生命周期应该是这样子的:
[ASP.NET MVC]视图是如何呈现的_第9张图片

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方法,实际调用ActionResultExecuteResult方法。在当前的情况下(验证失败),此时的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。

[ASP.NET MVC]视图是如何呈现的_第10张图片

 

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的具体类型可能是ContentResultViewResult,或者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把结果通过对应的视图显示到用户的浏览器中

[ASP.NET MVC]视图是如何呈现的_第11张图片

FindView包含三个参数:context这个是ControllerContext;第二个是ViewName,它的值为ViewName = context.RouteData.GetRequiredString("action"),其实就是Action的值;第三个是MasterName,我们的例子中为空。

 

那么如何找到WebFormView呢?

我们在HomeController中创建一个List方法,并在View/Hello文件夹下创建List.aspx文件

publicActionResult List()

{

return View();

}

 

然后执行调适:
[ASP.NET MVC]视图是如何呈现的_第12张图片

 

由于当前默认的请求是/Hello/Index,因此MVC Framework会自动寻找
~/Views/Hello/Index.cshtml

~/Views/Hello/Index.vbhtml

~/Views/Shared/Index.cshtml

~/Views/Shared/Index.cshtml

实际上,Razor引擎不会真正的在硬盘上寻找上面的文件,因为,这些文件都已经编译成C#的类。所以Razor在编译后的类中寻找对应的视图。

 

 

 

Next

  1. How the controller is initialized?
  2. How the request comes to MvcHandler
  3. View, IView, ViewEngine, RazorViewEngile, RazorView

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

大多人应该都知道用户向IIS发送一个ASP.NET请求后,IIS处理请求并向用户返回对应的结果。也有人知道,当一个ASP.NET请求到达IIS后,进入CLR,然后由HttpApplication创建HttpContext并找到对应的HttpHandler处理请求。最后把结果返回到用户端。那么从进入CLR之后,一个ASP.NET请求的生命周期具体是怎么样的,要经历那些重要的对象呢? 本文主要介绍这两个方面。

ASP.NET Request Liftcycle

[ASP.NET MVC]视图是如何呈现的_第13张图片

 

 

[ASP.NET MVC]视图是如何呈现的_第14张图片

 

首先,我们来看看一个request在进入CLR之前,发生了什么?

在IIS6和IIS7中,所有的HTTP请求均由HTTP侦听器捕获。那么HTTP侦听器是什么,其实就是http.sys,它运行在内核级别。至于具体什么是内核模块,以及如何运行,超出本文的范畴。如果你有兴趣,请自己查阅MSDN.

HTTP侦听器把捕获到的HTTP请求放到对应的应用程序池的请求队列中。所谓应用程序池

 

HttpRuntime, HttpApplicationFactory, HttpApplication,HttpContext, HttpHandler, HttpModule

 

动态编译

你可能感兴趣的:([ASP.NET MVC]视图是如何呈现的)