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