为了搞清楚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
动态编译