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

ASP.NET MVC深度接触:ASP.NET MVC请求生命周期(转)

2010-06-08 20:36 357 查看

【翻译】ASP.NET MVC深度接触:ASP.NET MVC请求生命周期

Author:朱晔

Date:2008-03-25

http://msdn.microsoft.com/zh-cn/dd350174.aspx

ASP.NET MVC 深度接触:ASP.NET MVC 请求生命周期

翻译自: ASP.NET MVC In-Depth: The Life of an ASP.NETMVC Request

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

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

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

两个警告

首先,要给大家两个警告。

第一个警告:我在ASP.NET MVC Preview2 对外发布之后写了此文。ASP.NET MVC 框架在Beta 前还会变化很大。因此,我在此文中描述的任何东西都会在几个月后不再有效。因此,如果你在2008 年5 月之后读此文的话,不要相信任何你读到的东西。

第二,此文不是ASP.NET MVC 的概览。我只是枯草地介绍了ASP.NET MVC 请求的生命周期。好了,别说我没有提醒你。

生命周期步骤概览

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

1 、步骤1 ——创建RouteTable

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

2 、步骤2 ——UrlRoutingModule 拦截请求

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

3 、步骤3 ——执行MvcHandler

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

4 、步骤4 ——执行控制器

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

5 、步骤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;
using System.Web.Mvc;
using System.Web.Routing;

namespace TestMVCArch
{
public class GlobalApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
// Note: Change the URL to "{controller}.mvc/{action}/{id}" to enable
// automatic support on IIS6 and IIS7 classic mode

routes.Add(new Route("{controller}/{action}/{id}", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new { action = "Index", id = "" }),
});

routes.Add(new Route("Default.aspx", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new { controller = "Home", action = "Index", id = "" }),
});
}

protected void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}
}
}

应用程序的路由表由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 生命周期在设计的时候包含了很多可扩展点。我们可以自定义通过插入自定义类或覆盖既有类来自定义框架的行为。下面是这些扩展点的概要:

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

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

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

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

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

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

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

如果你发现任何我遗漏的扩展点,请为留下你的评论(留在原作者文章下),我会更新此文。

结束语

本文的目的是描述ASP.NET MVC 请求从开始到结束的整个生命周期。我们研究了处理ASP.NET MVC 请求的5 个过程:创建RouteTable ,UrlRoutingModule 拦截请求,生成控制器,执行行为以及呈现视图。最后,我介绍了ASP.NET MVC 框架的可扩展点。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: