做ASP.NET WebForm开发都知道,ASP.NET有复杂的生命周期,学习ASP.NET MVC就要深入理解它的生命周期。今天从CodePlex上下载了ASP.NET Preview 2 的源代码,还有两个程序集Routing与Abstractions并未发布,不过这两个程序集的类并不多,可以用NET反编译工具 Reflector解开来看看,可惜这两个程序集用的是VS2008使用.net 3.5开发的,用了c# 3.0的很多特性,Reflector反编译不完全。
ASP.NET MVC通过HttpModule(UrlRoutingModule)开始他的执行流程
代码如下:
里面还定义了一个RequestData,主要就是当前处理的HttpHandler和URL原始路径。来看看ASP.NET 的HttpApplication 管线会依次处理下面的请求:
-
对请求进行验证,将检查浏览器发送的信息,并确定其是否包含潜在恶意标记。
-
如果已在 Web.config 文件的 UrlMappingsSection 节中配置了任何 URL,则执行 URL 映射。
-
引发 BeginRequest 事件。
-
引发 AuthenticateRequest 事件。
-
引发 PostAuthenticateRequest 事件。
-
引发 AuthorizeRequest 事件。
-
引发 PostAuthorizeRequest 事件。
-
引发 ResolveRequestCache 事件。
-
引发 PostResolveRequestCache 事件。
-
根据所请求资源的文件扩展名(在应用程序的配置文件中映射),选择实现 IHttpHandler 的类,对请求进行处理。如果该请求针对从 Page 类派生的对象(页),并且需要对该页进行编译,则 ASP.NET 会在创建该页的实例之前对其进行编译。
-
引发 PostMapRequestHandler 事件。
-
引发 AcquireRequestState 事件。
-
引发 PostAcquireRequestState 事件。
-
引发 PreRequestHandlerExecute 事件。
-
为该请求调用合适的 IHttpHandler 类的 ProcessRequest 方法(或异步版 BeginProcessRequest)。例如,如果该请求针对某页,则当前的页实例将处理该请求。
-
引发 PostRequestHandlerExecute 事件。
-
引发 ReleaseRequestState 事件。
-
引发 PostReleaseRequestState 事件。
-
如果定义了 Filter 属性,则执行响应筛选。
-
引发 UpdateRequestCache 事件。
-
引发 PostUpdateRequestCache 事件。
-
引发 EndRequest 事件。
上述UrlRoutingModule订阅了两个 HttpApplication 事件,PostResolveRequestCache 要比 PostMapRequestHandler 更早执行,先看PostResolveRequestCache 事件,他首先从首先从 RouteCollection 中获取一个 RouteData 对象。看看RouteCollection 属性:
看到了来自RouteTable,这不正是在Global.asax.cs 中添加的 Route 集合:
上述代码来自[翻译]使用asp.net mvc再造一个digg 第一部分的kigg。回到上文,在获取 RoteCollection 之后,通过调用 GetRouteData(context) 返回一个 RouteData 对象,该对象内部包含了我们注册 Route 时的相关设置,包括下面所需要的 MvcRouteHandler。接下来,该方法将 routeData 和上下文一起打包成 RequestContext,这就是为相关处理准备的上下文环境。通过调用 IRouteHandler.GetHttpHandler() 方法,终于到达流程的关键IHttpHandler(MvcHandler)。在WebForm中我们知道每一个页面都是一个HttpHandler,Asp.net mvc也不例外。
先来看看MvcRouteHandler:
这里得到了MvcHandler:
OnApplicationPostMapRequestHandler 被执行。在 PostMapRequestHandler 中,它提取了前面预先准备好的上下文,并修改了 HttpContext.Handler,使得 MvcHandler 接管默认的WebForm的HttpHandler,才是执行ASP.NET MVC的流程。现在来继续看MvcHandler。
首先从 RouteData 中提取 Controller 的名字(这个名字是我们在 Global.asax.cs RegisterRoutes 中注册 Route 时提供的),然后获取 ControllerFactory,只不过这里面专门提供了一个 ControllerBuilder。
如果我们自定义MvcHandler,则需要好好的看看ControllerBuilder.Current:
(1) 通常情况下,返回一个默认的 DefaultControllerFactory 实例。
(2) 我们可以在 Application_Start 中通过 ControllerBuilder.Current.SetControllerFactory 方法来注册一个我们自定义的工厂。
(3) 核心代码: factory = (IControllerFactory)Activator.CreateInstance(controllerFactoryType),通过反射创建IControllerFactory;
回到MvcHandler的 ProcessRequest ,DefaultControllerFactory.CreateController(RequestContext, requiredString) 来返回 IController 实例。下面看看DefaultControllerFactory的代码:
通过反射来创建 Controller 实例,GetControllerType 里面做了些缓存处理,以此来避免频繁使用反射造成的性能问题。继续MvcHandler.ProcessRequest(),在得到控制器实例后,MvcHandler 开始了调用 Controller.Execute() 来进一步后续操作,同时对其上下文进一步封装,除了前面创建的 RequestContext,还加上了当前这个 Controller 对象的引用,类名叫ControllerContext。
继续看IController的默认实现类Controller:
获取 Action 的名字,然后开始执行 InvokeAction,如果找不到Action,则调用HandleUnknownAction,这是一个虚拟方法,可以在子类中重写,默认是抛出一个异常InvalidOperationException。
详细看看InvokeAction方式的执行:
它通过反射获取所有同名 Action 方法信息;其次,它过滤掉所有有 NonActionAttribute 和 IsSpecialName 标记的方法;第三,当同名有效 Action 被重载时它会抛出异常(提示Controller_MoreThanOneAction),继续调用InvokeActionMethod:
InvokeActionMethodFilters(methodInfo, () => methodInfo.Invoke(this, parameterValues));
这行代码将 Action 的调用作为一个委托,连同反射信息传递给 InvokeActionMethodFilters。
这个方法首先将默认的过滤器 ControllerActionFilter 加到列表,然后提取所有继承层次上基类的过滤器特性。最后将这些过滤器集合、过滤上下文,连同前一个方法传递进来的 Action 执行委托(continuation) 再次转交给了一个 ActionFilterExecutor 对象实例,并调用其 Execute 方法。
ExecuteRecursive使用了递归算法,通过迭代器 MoveNext() 方法提取一个过滤器对象,执行其 OnActionExecuting 方法。 如果该方法设置了 filterContext.Cancel = true,则放弃后续执行代码。这种机制为我们提供了更好的控制,例如用它来实现伪静态页。一层一层调用所有的 ActionFilterAttribute.OnActionExecuting 方法,直到 MoveNext() == false。 在最后一次递归调用时,由于 enumerator.MoveNext() == false, _continuation() 方法被执行。这个就是前面给传递过来的 Action 方法委托,Action 方法总算是执行了。 在 Action 委托执行完成后,递归调逐级往上回溯,直到最初那个方法堆栈。这样所有ActionFilterAttribute.OnActionExecuted 也被执行完成。
到此开始进入最后的视图呈现阶段,可以把数据呈现到视图上,Controller 提供了几个重载的 RenderView() 来完成这个工作。
将一路传递过来的相关 "数据" (上下文)ControllerContext 再次包装成ViewContext 。当然,这次依然会多出些东西,里面就有我们向视图传递的数据 —— viewData 和tempData。作为默认选择,MVC 创建 WebForm 视图引擎来展示结果。其他的视图引擎可以去看mvccontrib,这个项目就是整合:Castle Windsor 、StructureMap 、Spring.NET 等IoC框架以及视图引擎,包括Castle MonoRail所用的NVelocityView视图引擎、NHamlView视图引擎、XsltViewEngine视图引擎等等。
继续看这个 WebFormViewEngine:
首先会创建一个 WebFormViewLocator 对象来获取视图存放路径。
namespace System.Web.Mvc {
public class WebFormViewLocator : ViewLocator {
public WebFormViewLocator() {
ViewLocationFormats = new[] {
"~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Views/Shared/{0}.aspx",
"~/Views/Shared/{0}.ascx"
};
MasterLocationFormats = new[] {
"~/Views/{1}/{0}.master",
"~/Views/Shared/{0}.master"
};
}
}
}
在获取路径后,WebFormViewEngine 通过一个包装类 BuildManagerWrapper 间接调用 System.Web.Compilation.BuildManager 的静态方法 CreateInstanceFromVirtualPath() 将视图进行编译,并返回一个对象实例。(System.Web.Compilation.BuildManager BuildManager 类管理应用程序的程序集和页的编译过程),后面通过 as 转换结果来判断视图是 ViewPage 还是 ViewUserControl。ViewPage 继承自我们所熟悉的 System.Web.UI.Page,它的 RenderView() 方法也就是完成WebForm的 Page.ProcessRequest() 的处理而已,也就完成了对WebForm模型的置换。
下面看看ViewPage和ViewUserControl的代码:
namespace System.Web.Mvc {
using System.Web.UI;
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
public class ViewPage : Page, IViewDataContainer {
private string _masterLocation;
private object _viewData;
public string MasterLocation {
get {
return _masterLocation ?? String.Empty;
}
set {
_masterLocation = value;
}
}
public AjaxHelper Ajax {
get;
set;
}
public HtmlHelper Html {
get;
set;
}
public TempDataDictionary TempData {
get {
return ViewContext.TempData;
}
}
public UrlHelper Url {
get;
set;
}
public ViewContext ViewContext {
get;
private set;
}
public ViewData ViewData {
get {
return new ViewData(_viewData);
}
}
public HtmlTextWriter Writer {
get;
private set;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers")]
protected override void OnPreInit(EventArgs e) {
base.OnPreInit(e);
if (!String.IsNullOrEmpty(MasterLocation)) {
MasterPageFile = MasterLocation;
}
}
public virtual void RenderView(ViewContext viewContext) {
ViewContext = viewContext;
Ajax = new AjaxHelper(viewContext);
Html = new HtmlHelper(viewContext);
Url = new UrlHelper(viewContext);
ProcessRequest(HttpContext.Current);
}
protected override void Render(HtmlTextWriter writer) {
Writer = writer;
try {
base.Render(writer);
}
finally {
Writer = null;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
Justification = "There is already a ViewData property and it has a slightly different meaning.")]
protected internal virtual void SetViewData(object viewData) {
_viewData = viewData;
}
#region IViewDataContainer Members
object IViewDataContainer.ViewData {
get {
return _viewData;
}
}
#endregion
}
}
namespace System.Web.Mvc {
using System.ComponentModel;
using System.Globalization;
using System.Web.Resources;
using System.Web.UI;
using System.Web.Mvc.Resources;
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
public class ViewUserControl : UserControl, IViewDataContainer {
private string _viewDataKey;
private object _viewData;
public AjaxHelper Ajax {
get {
return ViewPage.Ajax;
}
}
public HtmlHelper Html {
get {
return ViewPage.Html;
}
}
public TempDataDictionary TempData {
get {
return ViewPage.TempData;
}
}
public UrlHelper Url {
get {
return ViewPage.Url;
}
}
public ViewData ViewData {
get {
EnsureViewData();
return new ViewData(_viewData);
}
}
public ViewContext ViewContext {
get {
return ViewPage.ViewContext;
}
}
[DefaultValue("")]
public string ViewDataKey {
get {
return _viewDataKey ?? String.Empty;
}
set {
_viewDataKey = value;
}
}
private ViewPage ViewPage {
get {
ViewPage viewPage = Page as ViewPage;
if (viewPage == null) {
throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, MvcResources.ViewUserControl_RequiresViewPage));
}
return viewPage;
}
}
public HtmlTextWriter Writer {
get {
return ViewPage.Writer;
}
}
private void EnsureViewData() {
// Get the ViewData for this ViewUserControl, optionally using the specified ViewDataKey
if (_viewData != null) {
return;
}
IViewDataContainer vdc = GetViewDataContainer(this);
if (vdc == null) {
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentUICulture,
MvcResources.ViewUserControl_RequiresViewDataProvider,
AppRelativeVirtualPath));
}
if (String.IsNullOrEmpty(ViewDataKey)) {
_viewData = vdc.ViewData;
}
else {
_viewData = DataBinder.Eval(vdc.ViewData, ViewDataKey);
}
}
private static IViewDataContainer GetViewDataContainer(Control control) {
// Walk up the control hierarchy until we find someone that implements IViewDataContainer
while (control != null) {
control = control.Parent;
IViewDataContainer vdc = control as IViewDataContainer;
if (vdc != null) {
return vdc;
}
}
return null;
}
public virtual void RenderView(ViewContext viewContext) {
// TODO: Remove this hack. Without it, the browser appears to always load cached output
viewContext.HttpContext.Response.Cache.SetExpires(DateTime.Now);
ViewUserControlContainerPage containerPage = new ViewUserControlContainerPage(this);
containerPage.RenderView(viewContext);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
Justification = "There is already a ViewData property and it has a slightly different meaning.")]
protected internal virtual void SetViewData(object viewData) {
_viewData = viewData;
}
#region IViewDataContainer Members
object IViewDataContainer.ViewData {
get {
EnsureViewData();
return _viewData;
}
}
#endregion
private sealed class ViewUserControlContainerPage : ViewPage {
public ViewUserControlContainerPage(ViewUserControl userControl) {
Controls.Add(userControl);
}
}
}
}
以直接在 Controller 中 RenderView 一个用户控件(ViewUserControl),asp.net mvc 会替我们创建了一个 "空白页" (ViewUserControlContainerPage )来装载这个控件RenderView(ViewUserControl) 有个限制,就是不能有 MasterPage。
private sealed class ViewUserControlContainerPage : ViewPage {
public ViewUserControlContainerPage(ViewUserControl userControl) {
Controls.Add(userControl);
}
}
我们从 UrlRoutingModule 开始,历经 MvcRouteHandler、MvcHandler、Controller、ActionFilterAttribute,直到最后的 ViewEngine、ViewPage.完成了整个ASP.NET MVC的生命周期探索。