ASP.NET MVC 生命周期

本文的目的旨在详细描述ASP.NET MVC请求从开始到结束的每一个过程。我希望能理解在浏览器输入URL并敲击回车来请求一个ASP.NET MVC网站的页面之后发生的任何事情。

为什么需要关心这些?有两个原因。首先是因为ASP.NET MVC是一个扩展性非常强的框架。例如,我们可以插入不同的ViewEngine来控制网站内容呈现的方式。我们还可以定义控制器生成和分配到某个请求的方式。因为我想发掘任何ASP.NET MVC页面请求的扩展点,所以我要来探究请求过程中的一些步骤。

其次,如果你对测试驱动开发佷感兴趣,当为控制器写单元测试时,我们就必须理解控制器的依赖项。在写测试的时候,我们需要使用诸如Typemock Isolator或Rhino Mocks的Mock框架来模拟某些对象。如果不了解页面请求生命周期就不能进行有效的模拟。

生命周期步骤概览

当我们对ASP.NET MVC网站发出一个请求的时候,会发生5个主要步骤:

步骤1:创建RouteTable

当ASP.NET应用程序第一次启动的时候才会发生第一步。RouteTable把URL映射到Handler。

步骤2:UrlRoutingModule拦截请求

第二步在我们发起请求的时候发生。UrlRoutingModule拦截了每一个请求并且创建和执行合适的Handler。

步骤3:执行MvcHandler

MvcHandler创建了控制器,并且把控制器传入ControllerContext,然后执行控制器。

步骤4:执行控制器

控制器检测要执行的控制器方法,构建参数列表并且执行方法。

步骤5:调用RenderView方法

大多数情况下,控制器方法调用RenderView()来把内容呈现回浏览器。Controller.RenderView()方法把这个工作委托给某个ViewEngine来做。

现在让我们来详细研究每一个步骤:

步骤1:创建RouteTable

当我们请求普通ASP.NET应用程序页面的时候,对于每一个页面请求都会在磁盘上有这样一个页面。例如,如果我们请求一个叫做SomePage.aspx的页面,在WEB服务器上就会有一个叫做SomePage.aspx的页面。如果没有的话,会得到一个错误。

从技术角度说,ASP.NET页面代表一个类,并且不是普通类。ASP.NET页面是一个Handler。换句话说,ASP.NET页面实现了IhttpHandler接口并且有一个ProcessRequest()方法用于在请求页面的时候接受请求。ProcessRequest()方法负责生成内容并把它发回浏览器。

因此,普通ASP.NET应用程序的工作方式佷简单明了。我们请求页面,页面请求对应磁盘上的某个页面,这个页面执行ProcessRequest()方法并把内容发回浏览器。

ASP.NET MVC应用程序不是以这种方式工作的。当我们请求一个ASP.NET MVC应用程序的页面时,在磁盘上不存在对应请求的页面。而是,请求被路由转到一个叫做控制器的类上。控制器负责生成内容并把它发回浏览器。

当我们写普通ASP.NET应用程序的时候,会创建很多页面。在URL和页面之间总是一一对应进行映射。每一个页面请求对应相应的页面。

相反,当我们创建ASP.NET MVC应用程序的时候,创建的是一批控制器。使用控制器的优势是可以在URL和页面之间可以有多对一的映射。例如,所有如下的URL都可以映射到相同的控制器上。

http://MySite/Products/1
http://MySite/Products/2
http://MySite/Products/3

这些URL映射到一个控制器上,通过从URL中提取产品ID来显示正确的产品。这种控制器方式比传统的ASP.NET方式更灵活。控制器方式可以产品更显而易见的URL。

那么,某个页面请求是怎么路由到某个控制器上的呢?ASP.NET MVC应用程序有一个叫做路由表(Route Table)的东西。路由表映射某个URL到某个控制器上。

一个应用程序有一个并且只会有一个路由表。路由表在Global.asax文件中创建。清单1包含了在使用Visual Studio新建ASP.NET MVC Web应用程序时默认的Global.asax文件。

清单 1 – Global.asax

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

应用程序的路由表由RouteTable.Routes的静态属性表示。这个属性表示了路由对象的集合。在清单1列出的Global.asax文件中,我们在应用程序首次启动时为路由表增加两个路由对象(Application_Start()方法在第一次请求网站页面的时候被调用一次)。

路由对象负责把URL映射到Handler。在清单1中,我们创建了两个路由对象。这2个对象都把URL映射到MvcRouteHandler。第一个路由映射任何符合{controller}/{action}/{id}模式的URL到MvcRouteHandler。第二个路由映射某个URL Default.aspx到MvcRouteHandler。

顺便说一下,这种新的路由构架可以脱离ASP.NET MVC独立使用。Global.asax文件映射URL到MvcRouteHandler。然而,我们可以选择把URL路由到不同类型的Handler上。这里说的路由构架包含在一个叫做System.Web.Routing.dll的独立程序集中。我们可以脱离MVC使用路由。

步骤2:UrlRoutingModule拦截请求

当我们对ASP.NET MVC应用程序发起请求的时候,请求会被UrlRoutingModule HTTP Module拦截。HTTP Module是特殊类型的类,它参与每一次页面请求。例如,传统ASP.NET包含了FormsAuthenticationModule HTTP Module用来使用表单验证实现页面访问安全性。

UrlRoutingModule拦截请求后做的第一件事情就是包装当前的HttpContext为HttpContextWrapper2对象。HttpContextWrapper2类和派生自HttpContextBase的普通HttpContext类不同。创建的HttpContext的包装可以使使用诸如Typemock Isolator或Rhino Mocks的Mock对象框进行模拟变得更简单。

接着,Module把包装后的HttpContext传给在之前步骤中创建的RouteTable。HttpContext包含了URL、表单参数、查询字符串参数以及和当前请求关联的cookie。如果在当前请求和路由表中的路由对象之间能找到匹配,就会返回路由对象。

如果UrlRoutingModule成功获取了RouteData对象,Module然后就会创建表示当前HttpContext和RouteData的RouteContext对象。Module然后实例化基于RouteTable的新HttpHandler,并且把RouteContext传给Handler的构造函数。

对于ASP.NET MVC应用程序,从RouteTable返回的Handler总是MvcHandler(MvcRouteHandler返回MvcHandler)。只要UrlRoutingModule匹配当前请求到路由表中的路由,就会实例化带有当前RouteContext的MvcHandler。

Module进行的最后一步就是把MvcHandler设置为当前的HTPP Handler。ASP.NET应用程序自动调用当前HTTP Handler的ProcessRequest()方法然后转入下一步。

步骤3:执行MvcHandler

在之前的步骤中,表示某个RouteContext的MvcHandler被设置作为当前的HTTP Handler。ASP.NET应用程总是会发起一系列的事件,包括Star、BeginRequest、PostResolveRequestCache、 PostMapRequestHandler、PreRequestHandlerExecute和EndRequest事件(非常多的应用程序事件——对于完整列表,请查阅Visual Studio 2008文档中的HttpApplication类)。

之前内容中描述的所有东西都在PostResolveRequestCache和PostMapRequestHandler中发生。当前HTTP Handler的ProcessRequest()方法在PreRequestHandlerExecute事件之后被调用。

当之前内容中创建的MvcHandler对象的ProcessRequest()被调用的时候,会创建一个新的控制器。控制器由ControllerFactory创建。由于我们可以创建自己的ControllerFactory,所以这又是一个可扩展点。默认的ControllerFactory名字相当合适,叫做DefaultControllerFactory。

RequestContext以及控制器的名字被传入ControllerFactory.CreateController()方法来获得一个控制器。然后,从RequestContext和控制器构造ControllerContext对象。最后,调用控制器类的Execute()方法。在调用Execute()方法的时候会给方法传入ControllerContext。

步骤4:执行控制器

Execute()方法首先创建TempData对象(在Ruby On Rails中叫做Flash对象)。TempData可以用于保存下次请求必须的临时数据(TempData和会话状态差不多,不长期占用内存)。

接着,Execute()方法构建请求的参数列表。这些参数从请求参数中提取,将会被作为方法的参数。参数会被传入执行的控制器方法。

Execute()通过对控制器类进行反射来找到控制器的方法。控制器类是我们写的。Execute()方法找到了我们控制器类中的方法后就执行它。Execute()方法不会执行被装饰NonAction特性的方法。

至此,就进入了自己应用程序的代码。

步骤5:调用RenderView方法

通常,我们的控制器方法最后会调用RenderView()或RedirectToAction()方法。RenderView()方法负责把视图(页面)呈现给浏览器。

当我们调用控制器RenderView()方法的时候,调用会委托给当前ViewEngine的RenderView()方法。ViewEngine是另外一个扩展点。默认的ViewEngine是WebFormViewEngine。然而,我们可以使用诸如Nhaml的其它ViewEngine。

WebForm的ViewEngine.RenderView()方法创建了一个叫做ViewLocator的类来寻找视图。然后,它使用BuildManager来创建ViewPage类的实例。然后,如果页面有ViewData就会设置ViewData。最后,ViewPage 的RenderView()方法被调用。

ViewPage类从System.Web.UI.Page基类(和用于传统ASP.NET的页面一样)派生。RenderView()方法做的最后一个工作就是调用页面类的ProcessRequest()。调用视图的ProcessRequest()生成内容的方式和普通ASP.NET页面生成内容的方式一致。

可扩展点

ASP.NET MVC生命周期在设计的时候包含了很多可扩展点。我们可以自定义通过插入自定义类或覆盖既有类来自定义框架的行为。下面是这些扩展点的概要:

路由对象:当我们创建路由表的时候,调用RouteCollection.Add()方法来增加新的路由对象。Add()方法接受了RouteBase对象。我们可以通过派生RouteBase基类来实现自己的路由对象。

MvcRouteHandler :当创建MVC应用程序的时候,我们把URL映射到MvcRouteHandler对象上。然而,我们可以把URL映射到实现IRouteHandler接口的任何类上。路由类的构造函数接受任何实现IRouteHandler接口的对象。

MvcRouteHandler.GetHttpHandler() : MvcRouteHandler 类的GetHttpHandler()方法是virtual方法。默认情况下,MvcRouteHandler返回MvcHandler。如果愿意的话,我们可以覆盖GetHttpHandler()方法来返回不同的Handler。

ControllerFactory :我们可以通过System.Web.MVC.ControllerBuilder.Current.SetControllerFactory()方法指定一个自定义类来创建自定义的控制器工厂。控制器工厂负责为某个控制器名和RequestContext返回控制器。

控制器:我们可以通过实现Icontroller接口来实现自定义控制器。这个接口只有一个Execute(ControllerContext controllerContext)方法。

ViewEngine:我们可以为控制器指定自定义的ViewEngine。通过为公共的Controller.ViewEngine属性指定ViewEngine来把ViewEngine指定给控制器。ViewEngine必须实现IviewEngine接口,接口只有一个方法:RenderView(ViewContext viewContext)。

ViewLocator :ViewLocator把视图名映射到实际视图文件上。我们可以通过WebFormViewEngine.ViewLocator的属性来执行自定义的ViewLocator。

时间: 2024-11-05 06:56:46

ASP.NET MVC 生命周期的相关文章

Routing与ASP.NET MVC生命周期

一.Routing ASP.NET MVC的“网址路径”和“文档路径”的对应关系是通过“网址路由(Routing)”来定义的,我们可以从项目内的App_Start\RouteConfig.cs文档可以看到一个RegisterRoutes方法: public static void RegisterRoutes(RouteCollection routes) { route.IgnoreRoute("{resource}.axd/{*pathInfo}"); route.MapRoute

Asp.net MVC生命周期

Asp.net应用程序管道处理用户请求时特别强调"时机",对Asp.net生命周期的了解多少直接影响我们写页面和控件的效率.因此在2007年和2008年我在这个话题上各写了一篇文章: <日志不说谎--Asp.net的生命周期> <日志不说谎--Asp.net的生命周期 [结题]> <两个粒度看Asp.net生命周期> 对于Asp.net MVC,我对它的生命周期还是兴趣很浓,于是提出两个问题: 一个HTTP请求从IIS移交到Asp.net运行时,As

[收藏]Asp.net MVC生命周期

一个HTTP请求从IIS移交到Asp.net运行时,Asp.net MVC是在什么时机获得了控制权并对请求进行处理呢?处理过程又是怎样的? 以IIS7中asp.net应用程序生命周期为例,下图是来自MSDN的一张HTTP请求处理过程发生事件的简图,后面我列出了一个完整的事件列表.既然Asp.net Mvc还是以Asp.net运行时为基础那么它必然要在Asp.net应用程序的生命周期中对请求进行截获.第一反应当然是去web.config里面去翻翻,我们可以看到UrlRoutingModule的配置

asp.net-页面生命周期

WebForm生命周期 浏览器=>网站服务器=>IIS=>IsAPI=>ASP.NET WebForm=>初始化page=>加载视图和post数据=>Page Load 页面加载=>处理按钮事件=>保存数据到ViewState=>渲染页面成HTML返回给IIS=>返回给用户浏览器 viewstate优缺点 (1)优点: 耗费的服务器资源较少(与Application.Session相比).因为,视图状态数据都写入了客户端计算机中. 易于维护

MVC学习笔记---MVC生命周期

Asp.net应用程序管道处理用户请求时特别强调"时机",对Asp.net生命周期的了解多少直接影响我们写页面和控件的效率.因此在2007年和2008年我在这个话题上各写了一篇文章: <日志不说谎--Asp.net的生命周期> <日志不说谎--Asp.net的生命周期 [结题]> <两个粒度看Asp.net生命周期> 对于Asp.net MVC,我对它的生命周期还是兴趣很浓,于是提出两个问题: 一个HTTP请求从IIS移交到Asp.net运行时,As

ASP.NET 生命周期 – ASP.NET 应用生命周期(一)

概述 ASP.NET 平台定义了两个非常重要的生命周期.第一个是 应用生命周期  (application life cycle),用来追踪应用从启动的那一刻到终止的那一刻.另一个就是 请求生命周期 (request life cycle),它定义了 HTTP 请求在 ASP.NET 平台中首次接收到,到最终响应发出之间的路径. ASP.NET 应用生命周期 在 ASP.NET 中有两个时刻——应用启动的时刻和应用停止接收请求的时刻,这两个时刻定义了应用生命周期.ASP.NET 在应用启动和当应

【深入ASP.NET原理系列】--ASP.NET页面生命周期

前言 ASP.NET页面运行时候,页面将经历一个生命周期,在生命周期中将执行一系列的处理步骤.包括初始化.实例化控件.还原和维护状态.运行时间处理程序代码以及进行呈现.熟悉页面生命周期非常重要,这样我们才能在生命周期的合适阶段编写代码.如果我们能在写代码的时候想着我们现在是在做生命周期的哪一步那将是非常好的. 你可能会说我不清楚还不是一样写代码,反正每次都在Page_load里面写代码 然后页面就出来了我管那么多干什么.所谓知其然如果能知其所以然岂不是更吊?我个人认为做ASP.NET B/S开发

ASP.NET页面生命周期与控件生命周期

ASP.NET页面生命周期 (1)PreInit 预初始化(2)Init 初始化(3)InitComplete 初始化完成(4)PreLoad 预加载(5)Load 加载(6)LoadComplete 加载完成(7)PreRender 预输出(8)PreRenderComplete 预输出完成(9)Unload 卸载 ASP.NET控件生命周期 -- 实例化(Instantiate) 控件被页面或另一个控件通过调用它的构造器所实例化.这个步骤之后所列出的阶段,仅当控件加入控件树中才会发生. --

[转]ASP.net Application 生命周期事件

生命周期事件和 Global.asax 文件 在应用程序的生命周期期间,应用程序会引发可处理的事件并调用可重写的特定方法.若要处理应用程序事件或方法,可以在应用程序根目录中创建一个名为 Global.asax 的文件. 如果创建了 Global.asax 文件,ASP.NET 会将其编译为从 HttpApplication 类派生的类,然后使用该派生类表示应用程序. HttpApplication 进程的一个实例每次只处理一个请求.由于在访问应用程序类中的非静态成员时不需要将其锁定,这样可以简化