您的位置:首页 > 编程语言 > ASP

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
出栈,主要是栈中的元素的清理工作。

三、一图胜千言,总体上概览

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: