ASP.Net请求处理机制(5)【ASP.Net MVC请求处理流程】
2018-01-22 21:50
537 查看
一、开放的ASP.NET MVC代码
2009年,Microsoft推出了ASP.NET MVC,也将
ASP.NET MVC项目作为开源项目推送到了开源社区中,至今时间也过去快6年了,ASP.NET MVC已经到了5.0的版本阶段了。我们看到
ASP.NET MVC从一个不完整的小孩长成一个日渐成熟的巨人,我们可以从开源社区找到ASP.NET MVC的源码,相比之前我们需要
Reflector进行反编译查看,这次则轻松得多。
这里我们选择
ASP.NET MVC 4的源码作为分析对象
打开
ASP.NET MVC 4的源代码,你会看到如下解决方案:这里我们主要关注
System.Web.Mvc这个类库项目
二、从MvcHandler.ProcessRequest
开始
从Part 3中我们知道了在请求处理管道中的第7个事件生成了MvcHandler,在第11和第12个事件之间调用了
MvcHandler的
ProcessRequest方法开始了
ASP.NET MVC的处理响应之旅。那么,我们就从
MvcHandler的
ProcessRequest方法开始查看,一个
ASP.NET MVC页面是如何加载出来一个
HTML页的!
(1)Controller的激活
①借助HttpConetxtWrapper封装HttpContext
protected virtual void ProcessRequest(HttpContext httpContext) { HttpContextBase httpContextBase = new HttpContextWrapper(httpContext); ProcessRequest(httpContextBase); }
可以看出,这里通过了一个基于包装器(又称装饰者)模式实现的一个
HttpContextWrapper类对
HttpContext进行了一个封装,并调用重载的另一个
ProcessRequest方法进行继续处理。
②控制器工厂根据URL创建控制器
protected internal virtual void ProcessRequest(HttpContextBase httpContext) { IController controller; IControllerFactory factory; ProcessRequestInit(httpContext, out controller, out factory); try { controller.Execute(RequestContext); } finally { factory.ReleaseController(controller); } }
可以看出,这里通过调用
ProcessRequestInit方法将上下文对象传入进行处理,然后返回生成的控制器实例以及控制器工厂。因此,我们转入
ProcessRequestInit方法看看:
private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) { ...... string controllerName = RequestContext.RouteData.GetRequiredString("controller"); factory = ControllerBuilder.GetControllerFactory(); controller = factory.CreateController(RequestContext, controllerName); ...... }
在这个方法中,首先根据
RouteData路由数据取得要请求的
Controller名称,然后取得
ControllerFactory(控制器工厂)对象,通过
ControllerFactory来创建指定名称的控制器,最后将控制器作为out参数传递出去。
③调用控制器的Execute方法进入Action
具体实现了
IController接口的
Controller对象通过调用
Excute方法开始执行具体的
Action,那么
Action究竟又是怎样被触发的呢?
public interface IController { void Execute(RequestContext requestContext); }
(2)Action的触发
①从ControllerBase的Excute方法开始
public abstract class ControllerBase : IController { protected virtual void Execute(RequestContext requestContext) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } if (requestContext.HttpContext == null) { throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext"); } VerifyExecuteCalledOnce(); Initialize(requestContext); using (ScopeStorage.CreateTransientScope()) { ExecuteCore(); } } // 抽象方法-让Controller去具体实现 protected abstract void ExecuteCore(); }
首先,
Controller并没有实现
IController接口,而是
Controller的基类
ControllerBase实现了
IController接口;然后,
ControllerBase中定义了一个抽象方法
ExcuteCore,让其子类去具体执行,这里主要是让
Controller类对象执行这个方法。
②根据URL获取Action名称并准备触发Action
public abstract class Controller : ControllerBase, IActionFilter, IAuthenticationFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IAsyncManagerContainer { protected override void ExecuteCore() { PossiblyLoadTempData(); try { string actionName = GetActionName(RouteData); if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) { HandleUnknownAction(actionName); } } finally { PossiblySaveTempData(); } } }
首先,通过路由数据获取
Action名称,例如请求
URL为:
http://xxx.com/Home/Index,这里获取的
Action名称即为
Index。然后,通过
ActionInvoker.InvokeAction去执行具体的
Action。那么问题来了,这个
ActionInvoker又是啥东东?我们先看看这个接口的定义:
public interface IActionInvoker { bool InvokeAction(ControllerContext controllerContext, string actionName); }
通过查阅资料,我们发现原来是一个叫做
ControllerActionInvoker的类实现了
IActionInvoker接口,那么我们就去看看这个
ControllerActionInvoker类吧。
③获取Controller与Action的描述信息和过滤器信息
public virtual bool InvokeAction(ControllerContext controllerContext, string actionName) { ...... ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext); ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName); if (actionDescriptor != null) { FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor); ...... } ...... }
看到这里,也许会有人问什么是描述信息?那么看到我们在开发中经常给
Controller或者
Action添加的
Attribute信息也许就不会感到陌生了:例如我们给某个名为
Index的
Action添加了
[HttpPost]或者
[HttpGet]特性,在请求时需要通过
HTTP报文请求方式来区分这两个
Action。
那么,什么又是过滤器信息?首先,过滤器涉及到一个叫做
AOP(面向切面编程)的概念,我们可以通过前面的请求处理管道进行理解,虽然我们的
ASP.NET页面请求处理部分只是其中一小部分,但是在这部分执行之前还经历了许多事件,在这之后又经历了许多事件,而这些事件都是可以自定义逻辑的,它们都可以叫做过滤器。
ASP.NET MVC默认为我们提供了四种类型的过滤器(
Filter),如下图所示:
④获取参数信息并开始真正执行Action
:Filter->Action->Filter
public virtual bool InvokeAction(ControllerContext controllerContext, string actionName) { ...... IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor); ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters); ...... }
通过上面所获取的各种描述信息与过滤器信息找到
Action并获取所需的参数,然后调用
InvokeActionMethodWithFilters方法执行
Action。因此,再转到
InvokeActionMethodWithFilters方法看看:
protected virtual ActionExecutedContext InvokeActionMethodWithFilters(ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters) { ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters); Func<ActionExecutedContext> continuation = () => new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */) { Result = InvokeActionMethod(controllerContext, actionDescriptor, parameters) }; Func<ActionExecutedContext> thunk = filters.Reverse().Aggregate(continuation, (next, filter) => () => InvokeActionMethodFilter(filter, preContext, next)); return thunk(); }
在这个方法中,首先将上下文对象、描述信息、参数信息传入
InvokeActionMethod方法中,得到了一个
Result对象。这个
Result对象又是什么?转到定义一看,原来不就是我们在开发中经常返回的
ActionResult类型吗?
public ActionResult Result { get { return _result ?? EmptyResult.Instance; } set { _result = value; } }
那么,在
InvokeActionMethod方法中又是如何返回
Result的呢?再次转到定义看看:
protected virtual ActionResult InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters) { object returnValue = actionDescriptor.Execute(controllerContext, parameters); ActionResult result = CreateActionResult(controllerContext, actionDescriptor, returnValue); return result; }
在这个方法中,首先执行了指定的
Action,然后获得了一个
returnValue返回值,通过传入返回值创建具体类型的
ActionResult作为方法的返回值。这里需要注意的是,
ActionResult是一个抽象类,像什么
JsonResult、
EmptyResult、
ViewResult等都是其子类,而这里的
CreateActionResult就是要创建其具体子类的实例并返回。
现在将目光返回到
InvokeActionMethodWithFilters方法中,看到代码最后声明了一个委托
thunk,它是过滤器结合经过反转之后再合并之前声明的委托
continuation之后的一个新委托(它所持有的委托链顺序会协调一致),目的是为了完成
AOP的效果,比如首先要执行
Action执行之前的过滤器,才能执行
Action方法。
⑤ActionResult
闪亮登场:Filter->Result
public virtual bool InvokeAction(ControllerContext controllerContext, string actionName) { ...... InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, challengeContext.Result ?? postActionContext.Result); ...... }
现在回到
InvokeAction这个主方法中,刚刚执行完
Action之后将结果都保存在了
postActionContext中的
Result中,现在继续执行过滤器(比如:可以对刚刚的
Action结果进行一些处理),目的也是为了完成
AOP的效果,比如执行完
Action之后,必须要执行
Action结束后的过滤器业务逻辑方法。那么,这里又是进行了什么操作呢?转到
InvokeActionResultWithFilters方法中去看看:
private ResultExecutedContext InvokeActionResultFilterRecursive(IList<IResultFilter> filters, int filterIndex, ResultExecutingContext preContext, ControllerContext controllerContext, ActionResult actionResult) { ...... if (filterIndex > filters.Count - 1) { InvokeActionResult(controllerContext, actionResult); return new ResultExecutedContext(controllerContext, actionResult, canceled: false, exception: null); } IResultFilter filter = filters[filterIndex]; filter.OnResultExecuting(preContext); ...... int nextFilterIndex = filterIndex + 1; postContext = InvokeActionResultFilterRecursive(filters, nextFilterIndex, preContext, controllerContext, actionResult); ...... }
首先,判断过滤器执行的序号是否已经到了最后,如果不是,则继续递归执行本方法调用过滤器(这里对应的过滤器是
OnResultExecuting事件,即在
Result被生成时之前进行触发)。如果到了最后,则开始生成最终的
ActionResult。看看这个
InvokeActionResult方法,它是一个虚方法。
protected virtual void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult) { actionResult.ExecuteResult(controllerContext); }
(3)View的呈现
我们知道ActionResult是一个抽象类,那么这个
InvokeActionResult应该是由其子类来实现。于是,我们找到
ViewResult,但是其并未直接继承于
ActionResult,再找到其父类
ViewResultBase,它则继承了
ActionResult。于是,我们来查看它的
ExecuteResult方法:
①约定大于配置的缘故
public override void ExecuteResult(ControllerContext context) { ...... if (String.IsNullOrEmpty(ViewName)) { ViewName = context.RouteData.GetRequiredString("action"); } ...... }
我们在日常开发中,总是被告知约定大于配置,
View中的名字必须与
Controller中
Action的名字一致。在这了,我们知道了原因,可以看出,这里就是通过
URL来取得
ViewName然后去查找View的。
②找到ViewEngine视图引擎并获取ViewEngineResult
首先,我们了解一下什么是ViewEngine视图引擎:我们在
ASP.NET MVC开发中一般会有两个选择,一个是
aspx视图引擎,另一个是
ASP.NET MVC 3.0推出的
Razor视图引擎。
Razor视图引擎在减少代码冗余、增强代码可读性和
Visual Studio智能感知方面,都有着突出的优势。因此,
Razor一经推出就深受广大
ASP.Net开发者的喜爱。
public override void ExecuteResult(ControllerContext context) { ...... ViewEngineResult result = null; if (View == null) { result = FindView(context); View = result.View; } ...... }
这里通过
FindView方法获取到具体的
View对象,而
FindView又是
ViewResultBase的一个抽象方法。这时,我们需要到
ViewResult中去看看这个
FindView方法。
protected override ViewEngineResult FindView(ControllerContext context) { ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName); if (result.View != null) { return result; } ...... }
这里通过在
ViewEngineCollection视图引擎集合中调用
FindView方法返回一个
ViewEngineResult对象,而
View则作为属性存在于这个
ViewEngineResult对象之中。
③加载ViewData/TempData等数据生成ViewContext
protected override ViewEngineResult FindView(ControllerContext context) { ...... TextWriter writer = context.HttpContext.Response.Output; ViewContext viewContext = new ViewContext(context, View, ViewData, TempData, writer); ...... }
这里开始加载
ViewData、
TempData等数据生成
ViewContext,可以在
ViewContext的构造函数中看到如下代码:
public ViewContext(ControllerContext controllerContext, IView view, ViewDataDictionary viewData, TempDataDictionary tempData, TextWriter writer) : base(controllerContext) { if (controllerContext == null) { throw new ArgumentNullException("controllerContext"); } if (view == null) { throw new ArgumentNullException("view"); } if (viewData == null) { throw new ArgumentNullException("viewData"); } if (tempData == null) { throw new ArgumentNullException("tempData"); } if (writer == null) { throw new ArgumentNullException("writer"); } View = view; ViewData = viewData; Writer = writer; TempData = tempData; }
现在知道我们在
Action方法中定义的那些
ViewData或者
TempData是在哪里被存入上下文了吧?
④开始Render:HTML页面的呈现
protected override ViewEngineResult FindView(ControllerContext context) { ...... View.Render(viewContext, writer); ...... }
ViewContext上下文对象已生成好,
TextWriter已经拿到,现在就开始对
View进行正式的呈现了,也就是返回给浏览器端请求的
HTML。由于这里View对象是一个实现了
IView接口的类对象,于是我们找到
RazorView,但是它并未直接实现
IView接口,于是我们找到它的父类
BuildManagerCompiledView
public abstract class BuildManagerCompiledView : IView { public virtual void Render(ViewContext viewContext, TextWriter writer) { if (viewContext == null) { throw new ArgumentNullException("viewContext"); } object instance = null; Type type = BuildManager.GetCompiledType(ViewPath); if (type != null) { instance = ViewPageActivator.Create(_controllerContext, type); } if (instance == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentCulture, MvcResources.CshtmlView_ViewCouldNotBeCreated, ViewPath)); } RenderView(viewContext, writer, instance); } }
首先,通过
ViewPath获取
View的类型(
Type),这里也是通过
BuildManger来完成的,每个
cshtml都会被
asp.net编译成一个类。然后,通过反射生成了
View的具体实例。最后,通过
RendView方法进行下一步的呈现工作。
RenderView是一个抽象方法,具体实现是在
RazorView类或
WebFormView类中。
protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance) { if (writer == null) { throw new ArgumentNullException("writer"); } WebViewPage webViewPage = instance as WebViewPage; if (webViewPage == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentCulture, MvcResources.CshtmlView_WrongViewBase, ViewPath)); } webViewPage.OverridenLayoutPath = LayoutPath; webViewPage.VirtualPath = ViewPath; webViewPage.ViewContext = viewContext; webViewPage.ViewData = viewContext.ViewData; webViewPage.InitHelpers(); if (VirtualPathFactory != null) { webViewPage.VirtualPathFactory = VirtualPathFactory; } if (DisplayModeProvider != null) { webViewPage.DisplayModeProvider = DisplayModeProvider; } WebPageRenderingBase startPage = null; if (RunViewStartPages) { startPage = StartPageLookup(webViewPage, RazorViewEngine.ViewStartFileName, ViewStartFileExtensions); } webViewPage.ExecutePageHierarchy(new WebPageContext(context: viewContext.HttpContext, page: null, model: null), writer, startPage); }
在此方法中,首先将传递过来的实例转换成了一个
WebViewPage类的实例,然后将
ViewContext、
ViewData等数据赋给
WebViewPage实例作为属性,以便在
View中获取。然后,如果有开始页则先执行开始页。最后,将
HttpContext、
Page与
Model对象封装为一个
WebPageContext对象传入
ExecutePageHierarchy方法中进行执行页面的渲染。
首先,我们从字面上来看,
Hierarchy代表层次,那么方法名的意思大概是:根据层次执行页面。那么,什么是页面的层次?
在执行
ExecutePageHierachy这个方法来渲染
View时,这个方法里面要完成相当多的工作,主要是
ViewStart的执行,和
Layout的执行。这里的困难之处在于对于有
Layout的页面来说,
Layout的内容是先输出的,然后是
RenderBody内的内容,最后还是
Layout的内容。如果仅仅是这样的话,只要初始化一个
TextWriter,按部就班的往里面写东西就可以了,但是实际上,
Layout并不能首先执行,而应该是
View的代码先执行,这样的话
View就有可能进行必要的初始化,供
Layout使用。例如我们有如下的一个
View:
@{ ViewBag.Title = "Code in View"; Layout = "_LayoutPage1.cshtml"; }
这个Layout的内容如下:
@{ Layout = "~/Views/Shared/_Layout.cshtml"; ViewBag.ToView = "Data from Layout"; } <div> Data In View: @ViewBag.Title </div> <div> @RenderBody(); </div>
这样可以在页面显示
Code in View字样。 但是反过来,如果试图在
View中显示在
Layout里面的
"Data from Layout"则是行不通的,什么也不会被显示。所以
RenderBody是先于
Layout中其他代码执行的,这种
Layout的结构称为
Page Hierachy。
在这样的代码执行顺序下,还要实现文本输出的顺序,因此
asp.net mvc这里的实现中就使用了栈,这个栈是
OutputStack,里面压入了
TextWriter。注意到这只是一个页面的处理过程,一个页面之中还会有
Partial View和
Action等,这些的处理方式都是一样的,因此还需要一个栈来记录处理到了哪个(子)页面,因此还有一个栈,称之为
TemplateStack,里面压入的是
PageContext,
PageContext维护了
view的必要信息,比如
Model之类的,当然也包括上面提到的
OutputStack。有了上面的基本信息,下面看代码,先看入口点:
public void ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage) { PushContext(pageContext, writer); if (startPage != null) { if (startPage != this) { var startPageContext = Util.CreateNestedPageContext<object>(parentContext: pageContext, pageData: null, model: null, isLayoutPage: false); startPageContext.Page = startPage; startPage.PageContext = startPageContext; } startPage.ExecutePageHierarchy(); } else { ExecutePageHierarchy(); } PopContext(); }
这个方法中,第一步首先将
pageContext入栈:
PushContext
public void PushContext(WebPageContext pageContext, TextWriter writer) { _currentWriter = writer; PageContext = pageContext; pageContext.Page = this; InitializePage(); // Create a temporary writer _tempWriter = new StringWriter(CultureInfo.InvariantCulture); // Render the page into it OutputStack.Push(_tempWriter); SectionWritersStack.Push(new Dictionary<string, SectionWriter>(StringComparer.OrdinalIgnoreCase)); // If the body is defined in the ViewData, remove it and store it on the instance // so that it won't affect rendering of partial pages when they call VerifyRenderedBodyOrSections if (PageContext.BodyAction != null) { _body = PageContext.BodyAction; PageContext.BodyAction = null; } }
第二步判断是否存在
ViewStart文件,如果有,就执行
startPage.ExecutePageHierachy()。如果不存在,则直接执行
ExecutePageHierachy()。
public override void ExecutePageHierarchy() { ...... TemplateStack.Push(Context, this); try { // Execute the developer-written code of the WebPage Execute(); } finally { TemplateStack.Pop(Context); } }
这个方法就是将
context压栈,然后执行相应的
view的代码,然后出栈。有了这些出入栈的操作,可以保证
View的代码,也就是
Execute的时候的
writer是正确的。
Execute中的方法除去
PartialView,
Action之类的,最终调用的是
WebPageBase中的
WriteLiteral方法:
public override void WriteLiteral(object value) { Output.Write(value); }
这里的Output属性是:
public TextWriter Output { get { return OutputStack.Peek(); } }
在调用了
Excute方法后,页面上的
HTML内容基本输出完毕,至此
View就渲染完毕了。
第三步,
pageContext出栈,主要是栈中的元素的清理工作。
三、一图胜千言,总体上概览
相关文章推荐
- ASP.Net请求处理机制初步探索之旅 - Part 5 ASP.Net MVC请求处理流程
- ASP.Net请求处理机制初步探索之旅 - Part 5 ASP.Net MVC请求处理流程
- ASP.Net请求处理机制初步探索之旅 - Part 5 ASP.Net MVC请求处理流程
- ASP.NET页面运行机制以及请求处理流程
- ASP.NET页面运行机制以及请求处理流程
- ASP.NET页面运行机制以及请求处理流程
- ASP.NET MVC的请求处理流程
- ASP.NET页面运行机制以及请求处理流程
- ASP.NET页面运行机制以及请求处理流程
- ASP.NET MVC : 请求处理流程 (Request-Handling Pipeline)
- ASP_NET_MVC3_请求处理流程(2) MVC源码分析
- ASP.NET页面运行机制以及请求处理流程
- asp.net mvc 请求处理流程,记录一下。
- ASP_NET_MVC3_请求处理流程(1) MVC请求入口
- ASP.NET MVC 请求流程
- asp.net mvc处理流程图解
- ASP.Net请求处理机制初步探索之旅(4):WebForm页面生命周期
- ASP.NET WebForms底层请求处理机制初探
- Asp.Net 构架(Http请求处理流程)1
- Asp.Net构架(Http请求处理流程)