ASP.NET 页生命周期概述(转)
本文从两个粒度对Asp.net生命周期做了展示,一是通过记录页面事件的触发顺序看请求的处理流程,一是通过Reflector看Page类内部对请求处理的实现,为了清晰我清理掉了ETW相关的代码保留了一个简化却足可以说明问题的流程骨架;
本文覆盖以下内容:
- 页面事件的触发顺序展示
- 清理掉ETW代码后的,Page类内部对请求处理的实现
- MSDN关于Asp.net生命周期非常重要的四个表格
- 演示源代码下载
2using System.Configuration;
3using System.Data;
4using System.Web;
5using System.Web.Security;
6using System.Web.UI;
7using System.Web.UI.HtmlControls;
8using System.Web.UI.WebControls;
9using System.Web.UI.WebControls.WebParts;
10
11public partial class _Default : System.Web.UI.Page
12{
13 protected void Page_PreInit(object sender, EventArgs e)
14 {
15 Response.Write("Page_PreInit<br/>");
16 }
17 protected void Page_Init(object sender, EventArgs e)
18 {
19 Response.Write("Page_Init<br/>");
20
21 }
22 protected void Page_InitComplete(object sender, EventArgs e)
23 {
24 Response.Write("Page_InitComplete<br/>");
25
26 }
27 protected void Page_PreLoad(object sender, EventArgs e)
28 {
29 Response.Write("Page_PreLoad<br/>");
30
31 }
32 protected void Page_Load(object sender, EventArgs e)
33 {
34 Response.Write("Page_Load<br/>");
35
36 }
37 protected void Page_LoadComplete(object sender, EventArgs e)
38 {
39 Response.Write("Page_LoadComplete<br/>");
40
41 }
42 protected void Page_PreRender(object sender, EventArgs e)
43 {
44 Response.Write("Page_PreRender<br/>");
45
46 }
47 protected void Page_SaveStateComplete(object sender, EventArgs e)
48 {
49 Response.Write("Page_SaveStateComplete<br/>");
50
51 }
52
53
54 protected void Page_Unload(object sender, EventArgs e)
55 {
56 int i = 0;
57 i++;//这行代码是用来设置断点的,为什么不用Response.Write?你说呢?
58
59 }
60
61
62 protected void Button1_Click(object sender, EventArgs e)
63 {
64 Label1.Text = "ControlEvent";
65 Response.Write("Button事件触发!<br/>");
66 }
67}
68
69
70
运行结果:
Page_PreInit
Page_Init
Page_InitComplete
Page_PreLoad
Page_Load
Page_LoadComplete
Page_PreRender
Page_SaveStateComplete
点击页面的Button后的输出:
Page_PreInit
Page_Init
Page_InitComplete
Page_PreLoad
Page_Load
Button事件触发!
Page_LoadComplete
Page_PreRender
Page_SaveStateComplete
我们从一个更细的粒度,在Reflector中看Page对请求处理的代码:
2 {
3 try
4 {
5 HttpContext context = this.Context;
6 string str = null;
7 if (includeStagesBeforeAsyncPoint)
8 {
9 if (this.IsInAspCompatMode)
10 {
11 AspCompatApplicationStep.OnPageStartSessionObjects();
12 }
13 if (this.PageAdapter != null)
14 {
15 this._requestValueCollection = this.PageAdapter.DeterminePostBackMode();
16 }
17 else
18 {
19 this._requestValueCollection = this.DeterminePostBackMode();
20 }
21 string callbackControlID = string.Empty;
22 if (this.DetermineIsExportingWebPart())
23 {
24 if (!RuntimeConfig.GetAppConfig().WebParts.EnableExport)
25 {
26 throw new InvalidOperationException(SR.GetString("WebPartExportHandler_DisabledExportHandler"));
27 }
28 str = this.Request.QueryString["webPart"];
29 if (string.IsNullOrEmpty(str))
30 {
31 throw new InvalidOperationException(SR.GetString("WebPartExportHandler_InvalidArgument"));
32 }
33 if (string.Equals(this.Request.QueryString["scope"], "shared", StringComparison.OrdinalIgnoreCase))
34 {
35 this._pageFlags.Set(4);
36 }
37 string str3 = this.Request.QueryString["query"];
38 if (str3 == null)
39 {
40 str3 = string.Empty;
41 }
42 this.Request.QueryStringText = str3;
43 context.Trace.IsEnabled = false;
44 }
45 if (this._requestValueCollection != null)
46 {
47 if (this._requestValueCollection["__VIEWSTATEENCRYPTED"] != null)
48 {
49 this.ContainsEncryptedViewState = true;
50 }
51 callbackControlID = this._requestValueCollection["__CALLBACKID"];
52 if ((callbackControlID != null) && (this._request.HttpVerb == HttpVerb.POST))
53 {
54 this._isCallback = true;
55 }
56 else if (!this.IsCrossPagePostBack)
57 {
58 VirtualPath path = null;
59 if (this._requestValueCollection["__PREVIOUSPAGE"] != null)
60 {
61 try
62 {
63 path = VirtualPath.CreateNonRelativeAllowNull(DecryptString(this. _requestValueCollection["__PREVIOUSPAGE"]));
64 }
65 catch (CryptographicException)
66 {
67 this._pageFlags[8] = true;
68 }
69 if ((path != null) && (path != this.Request.CurrentExecutionFilePathObject))
70 {
71 this._pageFlags[8] = true;
72 this._previousPagePath = path;
73 }
74 }
75 }
76 }
77 if (this.MaintainScrollPositionOnPostBack)
78 {
79 this.LoadScrollPosition();
80 }
81
82 this.PerformPreInit();
83
84 this.InitRecursive(null);
85
86 this.OnInitComplete(EventArgs.Empty);
87
88 if (this.IsPostBack)
89 {
90 this.LoadAllState();
91
92 this.ProcessPostData(this._requestValueCollection, true);
93
94 }
95
96
97 this.OnPreLoad(EventArgs.Empty);
98
99 this.LoadRecursive();
100
101 if (this.IsPostBack)
102 {
103 this.ProcessPostData(this._leftoverPostData, false);
104
105 this.RaiseChangedEvents();
106
107 this.RaisePostBackEvent(this._requestValueCollection);
108
109 }
110
111 this.OnLoadComplete(EventArgs.Empty);
112
113 if (this.IsPostBack && this.IsCallback)
114 {
115 this.PrepareCallback(callbackControlID);
116 }
117 else if (!this.IsCrossPagePostBack)
118 {
119
120 this.PreRenderRecursiveInternal();
121 }
122 }
123 if ((this._asyncInfo == null) || this._asyncInfo.CallerIsBlocking)
124 {
125 this.ExecuteRegisteredAsyncTasks();
126 }
127 if (includeStagesAfterAsyncPoint)
128 {
129 if (this.IsCallback)
130 {
131 this.RenderCallback();
132 }
133 else if (!this.IsCrossPagePostBack)
134 {
135 this.PerformPreRenderComplete();
136
137 if (context.TraceIsEnabled)
138 {
139 this.BuildPageProfileTree(this.EnableViewState);
140 this.Trace.Write("aspx.page", "Begin SaveState");
141 }
142
143 this.SaveAllState();
144
145 this.OnSaveStateComplete(EventArgs.Empty);
146 if (str != null)
147 {
148 this.ExportWebPart(str);
149 }
150 else
151 {
152 this.RenderControl(this.CreateHtmlTextWriter(this.Response.Output));
153 }
154
155 this.CheckRemainingAsyncTasks(false);
156 }
157 }
158 }
159 catch (ThreadAbortException exception)
160 {
161 HttpApplication.CancelModuleException exceptionState = exception.ExceptionState as HttpApplication.CancelModuleException;
162 if (((!includeStagesBeforeAsyncPoint || !includeStagesAfterAsyncPoint) || ((this._context.Handler != this) || (this._context.ApplicationInstance == null))) || ((exceptionState == null) || exceptionState.Timeout))
163 {
164 this.CheckRemainingAsyncTasks(true);
165 throw;
166 }
167 this._context.ApplicationInstance.CompleteRequest();
168 Thread.ResetAbort();
169 }
170 catch (ConfigurationException)
171 {
172 throw;
173 }
174 catch (Exception exception3)
175 {
176 PerfCounters.IncrementCounter(AppPerfCounter.ERRORS_DURING_REQUEST);
177 PerfCounters.IncrementCounter(AppPerfCounter.ERRORS_TOTAL);
178 if (!this.HandleError(exception3))
179 {
180 throw;
181 }
182 }
183 }
184
185
186
187
188
{
this.OnPreInit(EventArgs.Empty);
this.InitializeThemes();//看到主题和模板页是什么时候加载了吧
this.ApplyMasterPage();
this._preInitWorkComplete = true;
}
ASP.NET 页运行时,此页将经历一个生命周期,在生命周期中将执行一系列处理步骤。这些步骤包括初始化、实例化控件、还原和维护状态、运行事件处理程序代码以及进行呈现。了解页生命周期非常重要,因为这样做您就能在生命周期的合适阶段编写代码,以达到预期效果。此外,如果您要开发自定义控件,就必须熟悉页生命周期,以便正确进行控件初始化,使用视图状态数据填充控件属性以及运行任何控件行为代码。(控件的生命周期基于页的生命周期,但是页引发的控件事件比单独的 ASP.NET 页中可用的事件多。)
常规页生命周期阶段
一般来说,页要经历下表概述的各个阶段。除了页生命周期阶段以外,在请求前后还存在应用程序阶段,但是这些阶段并不特定于页。有关更多信息,请参见 ASP.NET 应用程序生命周期概述。
阶段 |
说明 |
页请求 |
页请求发生在页生命周期开始之前。用户请求页时,ASP.NET 将确定是否需要分析和编译页(从而开始页的生命周期),或者是否可以在不运行页的情况下发送页的缓存版本以进行响应。 |
开始 |
在开始阶段,将设置页属性,如 Request 和 Response。在此阶段,页还将确定请求是回发请求还是新请求,并设置 IsPostBack 属性。此外,在开始阶段期间,还将设置页的 UICulture 属性。 |
页初始化 |
页初始化期间,可以使用页中的控件,并将设置每个控件的 UniqueID 属性。此外,任何主题都将应用于页。如果当前请求是回发请求,则回发数据尚未加载,并且控件属性值尚未还原为视图状态中的值。 |
加载 |
加载期间,如果当前请求是回发请求,则将使用从视图状态和控件状态恢复的信息加载控件属性。 |
验证 |
在验证期间,将调用所有验证程序控件的 Validate 方法,此方法将设置各个验证程序控件和页的 IsValid 属性。 |
回发事件处理 |
如果请求是回发请求,则将调用所有事件处理程序。 |
呈现 |
在呈现之前,会针对该页和所有控件保存视图状态。在呈现阶段中,页会针对每个控件调用 Render 方法,它会提供一个文本编写器,用于将控件的输出写入页的 Response 属性的 OutputStream 中。 |
卸载 |
完全呈现页并已将页发送至客户端、准备丢弃该页后,将调用卸载。此时,将卸载页属性(如 Response 和 Request)并执行清理。 |
生命周期事件
在页生命周期的每个阶段中,页将引发可运行您自己的代码进行处理的事件。对于控件事件,通过以声明方式使用属性(如 onclick)或以使用代码的方式,均可将事件处理程序绑定到事件。
页还支持自动事件连接,即,ASP.NET 将查找具有特定名称的方法,并在引发了特定事件时自动运行这些方法。如果 @ Page 指令的 AutoEventWireup 属性设置为 true(或者未定义该属性,因为该属性默认为 true),页事件将自动绑定至使用 Page_事件的命名约定的方法(如 Page_Load 和 Page_Init)。有关自动事件连接的更多信息,请参见 ASP.NET Web 服务器控件事件模型。
下表列出了最常用的页生命周期事件。除了列出的事件外还有其他事件;不过,大多数页处理方案不使用这些事件。而是主要由 ASP.NET 网页上的服务器控件使用,以初始化和呈现它们本身。如果要编写自己的 ASP.NET 服务器控件,则需要详细了解这些阶段。有关创建自定义控件的信息,请参见开发自定义 ASP.NET 服务器控件。
页事件 |
典型使用 |
||
使用该事件来执行下列操作: |
|||
在所有控件都已初始化且已应用所有外观设置后引发。使用该事件来读取或初始化控件属性。 |
|||
由 Page 对象引发。使用该事件来处理要求先完成所有初始化工作的任务。 |
|||
如果需要在 Load 事件之前对页或控件执行处理,请使用该事件。 在 Page 引发该事件后,它会为自身和所有控件加载视图状态,然后会处理 Request 实例包括的任何回发数据。 |
|||
Load |
Page 在 Page 上调用 OnLoad 事件方法,然后以递归方式对每个子控件执行相同操作,如此循环往复,直到加载完本页和所有控件为止。 使用 OnLoad 事件方法来设置控件中的属性并建立数据库连接。 |
||
控件事件 |
使用这些事件来处理特定控件事件,如 Button 控件的 Click 事件或 TextBox 控件的 TextChanged 事件。
|
||
对需要加载页上的所有其他控件的任务使用该事件。 |
|||
在该事件发生前: |
|||
在该事件发生前,已针对页和所有控件保存了 ViewState。将忽略此时对页或控件进行的任何更改。 使用该事件执行满足以下条件的任务:要求已经保存了视图状态,但未对控件进行任何更改。 |
|||
Render |
这不是事件;在处理的这个阶段,Page 对象会在每个控件上调用此方法。所有 ASP.NET Web 服务器控件都有一个用于写出发送给浏览器的控件标记的 Render 方法。 如果创建自定义控件,通常要重写此方法以输出控件的标记。不过,如果自定义控件只合并标准的 ASP.NET Web 服务器控件,不合并自定义标记,则不需要重写 Render 方法。有关更多信息,请参见开发自定义 ASP.NET 服务器控件。 用户控件(.ascx 文件)自动合并呈现,因此不需要在代码中显式呈现该控件。 |
||
该事件首先针对每个控件发生,继而针对该页发生。在控件中,使用该事件对特定控件执行最后清理,如关闭控件特定数据库连接。 对于页自身,使用该事件来执行最后清理工作,如:关闭打开的文件和数据库连接,或完成日志记录或其他请求特定任务。
|
其他的页生命周期注意事项
各个 ASP.NET 服务器控件都有自己的生命周期,该生命周期与页生命周期类似。例如,控件的 Init 和 Load 事件在相应的页事件期间发生。
虽然 Init 和 Load 都在每个控件上以递归方式发生,但它们的发生顺序相反。每个子控件的 Init 事件(还有 Unload 事件)在为其容器引发相应的事件之前发生(由下到上)。但是,容器的 Load 事件是在其子控件的 Load 事件之前发生(由上到下)。
可以通过处理控件的事件(如 Button 控件的 Click 事件和 ListBox 控件的 SelectedIndexChanged 事件)来自定义控件的外观或内容。在某些情况下,可能也需处理控件的 DataBinding 或 DataBound 事件。有关更多信息,请参见各个控件的类参考主题以及开发自定义 ASP.NET 服务器控件。
当从 Page 类继承类时,除了可以处理由页引发的事件以外,还可以重写页的基类中的方法。例如,可以重写页的 InitializeCulture 方法,以便动态设置区域性信息。注意,在使用 Page_事件语法创建事件处理程序时,将隐式调用基实现,因此无需在方法中调用它。例如,无论是否创建 Page_Load 方法,始终都会调用页基类的 OnLoad 方法。但是,如果使用 override 关键字(在 Visual Basic 中为 Overrides)重写页的 OnLoad 方法,则必须显式调用基方法。例如,如果在页中重写 OnLoad 方法,则必须调用 base.Load(在 Visual Basic 中为 MyBase.Load)以运行基实现。
添加的控件的追赶事件
如果控件是在运行时动态创建的,或者是以声明方式在数据绑定控件的模板中创建的,它们的事件最初与页上的其他控件的事件并不同步。例如,对于运行时添加的控件,Init 和 Load 事件在页生命周期中的发生时间可能要比以声明方式创建的控件的相同事件晚得多。因此,从实例化那一刻起,动态添加的控件的事件就一直是在模板中的控件的事件之后发生,直到赶上该控件加入 Controls 集合时所对应事件为止。
一般来说,除非存在嵌套数据绑定控件,否则,您不必担心这种情况。如果子控件已执行数据绑定,但其容器控件尚未执行数据绑定,则子控件中的数据与其容器控件中的数据可能不同步。如果子控件中的数据根据容器控件中的数据绑定值执行了处理,这种情况则尤其显著。
例如,假定有一个 GridView,它的每一行显示一条公司记录,此外,有一个 ListBox 控件包含公司管理者列表。若要填充管理者列表,则需要将 ListBox 控件绑定到一个数据源控件(如 SqlDataSource),后者在查询中使用 CompanyID 来检索公司管理者数据。
如果以声明方式设置了 ListBox 控件的数据绑定属性(如 DataSourceID 和 DataMember),ListBox 控件将尝试在包含行的 DataBinding 事件期间绑定到其数据源。不过,行的 CompanyID 字段直到 GridView 控件的 RowDataBound 事件发生后才包含值。这种情况下,先绑定子控件(ListBox 控件),后绑定包含控件(GridView 控件),因此它们的数据绑定阶段并不同步。
若要避免此种情况,需要将 ListBox 控件的数据源控件与 ListBox 控件自身放在同一模板项中,并且不要以声明方式设置 ListBox 的数据绑定属性。而应在 RowDataBound 事件期间在运行时以编程方式设置它们,这样,到 CompanyID 信息可用时 ListBox 控件才会绑定到其数据。
有关更多信息,请参见使用数据源控件绑定到数据。
数据绑定控件的数据绑定事件
为了帮助您理解页生命周期与数据绑定事件之间的关系,下表列出了数据绑定控件(如 GridView、DetailsView 和 FormView 控件)中与数据相关的事件。
控件事件 |
典型使用 |
DataBinding |
该事件在包含控件(或 Page 对象)的 PreRender 事件之前由数据绑定控件引发,会标记控件到数据的绑定过程的起点。 如果需要,使用该事件以手动方式打开数据库连接。(数据源控件通常不需要如此操作。) |
RowCreated(仅限 GridView)或 ItemCreated(DataList、DetailsView、SiteMapPath、DataGrid、FormView 和 Repeater 控件) |
使用该事件来操作不依赖于数据绑定的内容。例如,在运行时,可以以编程方式向 GridView 控件中的页眉或页脚行添加格式。 |
RowDataBound(仅限 GridView)或 ItemDataBound(DataList、SiteMapPath、DataGrid 和 Repeater 控件) |
当该事件发生时,行或项中的数据可用,因此,可以在子数据源控件上格式化数据或设置 FilterExpression 属性,以便显示行或项中的相关数据。 |
DataBound |
该事件在数据绑定控件中标记数据绑定操作的结尾。在 GridView 控件中,会针对所有行和任何子控件完成数据绑定。 使用该事件格式化数据绑定内容,或在依赖来自当前控件的内容的值的其他控件中启动数据绑定。(有关详细信息,请参见本主题中前面的“添加的控件的追赶事件”。) |
登录控件事件
Login 控件可以使用 Web.config 文件中的设置来自动管理成员资格验证。不过,如果应用程序要求您自定义控件的工作方式,或者您要了解 Login 控件事件与页生命周期的关联方式,可以使用下表中列出的事件。
控件事件 |
典型使用 |
在回发期间,当页的 LoadComplete 事件发生后就会引发该事件。它标记登录过程的起点。 对必须在验证过程开始前发生的任务使用该事件。 |
|
该事件在 LoggingIn 事件之后引发。 使用该事件来重写或增强 Login 控件的默认验证行为。 |
|
该事件在验证用户名和密码后引发。 使用该事件来重定向到另一个页或动态设置控件中的文本。如果出现错误或验证失败,就不会发生该事件。 |
|
如果验证失败,将引发该事件。 使用该事件来设置控件中的问题解释文本或将用户定向到不同的页。 |
源文档 <http://msdn2.microsoft.com/zh-cn/library/ms178472(VS.80).aspx>
ASP.NET 应用程序生命周期概述
- 对请求进行验证,将检查浏览器发送的信息,并确定其是否包含潜在恶意标记。有关更多信息,请参见 ValidateRequest 和脚本侵入概述。
- 如果已在 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 事件。
- 添加、修改或删除应用程序的 Bin 文件夹中的程序集。
- 添加、修改或删除 App_GlobalResources 或 App_LocalResources 文件夹中的本地化资源。
- 添加、修改或删除应用程序的 Global.asax 文件。
- 添加、修改或删除 App_Code 目录中的源代码文件。
- 添加、修改或删除配置文件配置。
- 添加、修改或删除 App_WebReferences 目录中的 Web 服务引用。
- 添加、修改或删除应用程序的 Web.config 文件。
本主题概述应用程序生命周期,列出重要的生命周期事件,并描述如何编写适合应用程序生命周期的代码。在 ASP.NET 中,若要对 ASP.NET 应用程序进行初始化并使它处理请求,必须执行一些处理步骤。此外,ASP.NET 只是对浏览器发出的请求进行处理的 Web 服务器结构的一部分。了解应用程序生命周期非常重要,这样才能在适当的生命周期阶段编写代码,达到预期的效果。
应用程序生命周期概述
下表描述了 ASP.NET 应用程序生命周期的各个阶段。
阶段 |
说明 |
||||
用户从 Web 服务器请求应用程序资源。 |
ASP.NET 应用程序的生命周期以浏览器向 Web 服务器(对于 ASP.NET 应用程序,通常为 IIS)发送请求为起点。ASP.NET 是 Web 服务器下的 ISAPI 扩展。Web 服务器接收到请求时,会对所请求的文件的文件扩展名进行检查,确定应由哪个 ISAPI 扩展处理该请求,然后将该请求传递给合适的 ISAPI 扩展。ASP.NET 处理已映射到其上的文件扩展名,如 .aspx、.ascx、.ashx 和 .asmx。
|
||||
ASP.NET 接收对应用程序的第一个请求。 |
当 ASP.NET 接收到对应用程序中任何资源的第一个请求时,名为 ApplicationManager 的类会创建一个应用程序域。应用程序域为全局变量提供应用程序隔离,并允许单独卸载每个应用程序。在应用程序域中,将为名为 HostingEnvironment 的类创建一个实例,该实例提供对有关应用程序的信息(如存储该应用程序的文件夹的名称)的访问。 下面的关系图说明了这种关系: 如果需要,ASP.NET 还可对应用程序中的顶级项进行编译,其中包括 App_Code 文件夹中的应用程序代码。有关更多信息,请参见本主题后面的“编译生命周期”。 |
||||
为每个请求创建 ASP.NET 核心对象。 |
创建了应用程序域并对 HostingEnvironment 对象进行了实例化之后,ASP.NET 将创建并初始化核心对象,如 HttpContext、HttpRequest 和 HttpResponse。HttpContext 类包含特定于当前应用程序请求的对象,如 HttpRequest 和 HttpResponse 对象。HttpRequest 对象包含有关当前请求的信息,包括 Cookie 和浏览器信息。HttpResponse 对象包含发送到客户端的响应,包括所有呈现的输出和 Cookie。 |
||||
将 HttpApplication 对象分配给请求 |
初始化所有核心应用程序对象之后,将通过创建 HttpApplication 类的实例启动应用程序。如果应用程序具有 Global.asax 文件,则 ASP.NET 会创建 Global.asax 类(从 HttpApplication 类派生)的一个实例,并使用该派生类表示应用程序。
创建 HttpApplication 的实例时,将同时创建所有已配置的模块。例如,如果将应用程序这样配置,ASP.NET 就会创建一个 SessionStateModule 模块。创建了所有已配置的模块之后,将调用HttpApplication 类的 Init 方法。 下面的关系图说明了这种关系: |
||||
由 HttpApplication 管线处理请求。 |
在处理该请求时将由 HttpApplication 类执行以下事件。希望扩展 HttpApplication 类的开发人员尤其需要注意这些事件。 |
生命周期事件和 Global.asax 文件
在应用程序的生命周期期间,应用程序会引发可处理的事件并调用可重写的特定方法。若要处理应用程序事件或方法,可以在应用程序根目录中创建一个名为 Global.asax 的文件。
如果创建了 Global.asax 文件,ASP.NET 会将其编译为从 HttpApplication 类派生的类,然后使用该派生类表示应用程序。
HttpApplication 进程的一个实例每次只处理一个请求。由于在访问应用程序类中的非静态成员时不需要将其锁定,这样可以简化应用程序的事件处理过程。这样还可以将特定于请求的数据存储在应用程序类的非静态成员中。例如,可以在 Global.asax 文件中定义一个属性,然后为该属性赋一个特定于请求的值。
通过使用命名约定 Application_event(如 Application_BeginRequest),ASP.NET 可在 Global.asax 文件中将应用程序事件自动绑定到处理程序。这与将 ASP.NET 页方法自动绑定到事件(如页的 Page_Load 事件)的方法类似。有关详细信息,请参见 ASP.NET 页生命周期概述。
Application_Start 和 Application_End 方法是不表示 HttpApplication 事件的特殊方法。在应用程序域的生命周期期间,ASP.NET 仅调用这些方法一次,而不是对每个 HttpApplication 实例都调用一次。
下表列出在应用程序生命周期期间使用的一些事件和方法。实际远不止列出的这些事件,但这些事件是最常用的。
事件或方法 |
说明 |
Application_Start |
请求 ASP.NET 应用程序中第一个资源(如页)时调用。在应用程序的生命周期期间仅调用一次 Application_Start 方法。可以使用此方法执行启动任务,如将数据加载到缓存中以及初始化静态值。 在应用程序启动期间应仅设置静态数据。由于实例数据仅可由创建的 HttpApplication 类的第一个实例使用,所以请勿设置任何实例数据。 |
Application_ event |
在应用程序生命周期中的适当时候引发,请参见本主题前面的应用程序生命周期表中列出的内容。 Application_Error 可在应用程序生命周期的任何阶段引发。 由于请求会短路,因此 Application_EndRequest 是唯一能保证每次请求时都会引发的事件。例如,如果有两个模块处理 Application_BeginRequest 事件,第一个模块引发一个异常,则不会为第二个模块调用 Application_BeginRequest 事件。但是,会始终调用 Application_EndRequest 方法使应用程序清理资源。 |
HttpApplication.Init |
在创建了所有模块之后,对 HttpApplication 类的每个实例都调用一次。 |
在销毁应用程序实例之前调用。可使用此方法手动释放任何非托管资源。有关更多信息,请参见清理非托管资源。 |
|
Application_End |
在卸载应用程序之前对每个应用程序生命周期调用一次。 |
编译生命周期
在第一次对应用程序发出请求时,ASP.NET 按特定顺序编译应用程序项。要编译的第一批项称为顶级项。在第一次请求之后,仅当依赖项更改时才会重新编译顶级项。下表描述编译 ASP.NET 顶级项的顺序。
项 |
说明 |
App_GlobalResources |
编译应用程序的全局资源并生成资源程序集。应用程序的 Bin 文件夹中的任何程序集都链接到资源程序集。 |
App_WebResources |
创建并编译 Web 服务的代理类型。所生成的 Web 引用程序集将链接到资源程序集(如存在)。 |
Web.config 文件中定义的配置文件属性 |
如果应用程序的 Web.config 文件中定义了配置文件属性,则生成一个包含配置文件对象的程序集。 |
App_Code |
生成源代码文件并创建一个或更多个程序集。所有代码程序集和配置文件程序集都链接到资源和 Web 引用程序集(如果有)。 |
Global.asax |
编译应用程序对象并将其链接到所有先前产生的程序集。 |
在编译应用程序的顶级项之后,ASP.NET 将根据需要编译文件夹、页和其他项。下表描述编译 ASP.NET 文件夹和项的顺序。
项 |
说明 |
App_LocalResources |
如果包含被请求项的文件夹包含 App_LocalResources 文件夹,则编译本地资源文件夹的内容并将其链接到全局资源程序集。 |
各个网页(.aspx 文件)、用户控件(.ascx 文件)、HTTP 处理程序(.ashx 文件)和 HTTP 模块(.asmx 文件) |
根据需要编译并链接到本地资源程序集和顶级程序集。 |
主题、主控页、其他源文件 |
在编译引用页时编译那些页所引用的各个主题、主控页和其他源代码文件的外观文件。 |
编译后的程序集缓存在服务器上并在后续请求时被重用,并且只要源代码未更改,就会在应用程序重新启动之间得到保留。
由于应用程序在第一次请求时进行编译,所以对应用程序的初始请求所花的时间会明显长于后续请求。可以预编译应用程序以减少第一次请求所需的时间。有关更多信息,请参见如何:预编译 ASP.NET 网站。
Application Restarts(应用程序重新启动的次数)
修改 Web 应用程序的源代码将导致 ASP.NET 把源文件重新编译为程序集。当修改应用程序中的顶级项时,应用程序中引用顶级程序集的其他所有程序集也会被重新编译。
此外,修改、添加或删除应用程序的已知文件夹中的某些类型的文件将导致应用程序重新启动。下列操作将导致应用程序重新启动:
当应用程序需要重新启动时,ASP.NET 将在重新启动应用程序域和加载新的程序集之前,从现有应用程序域和旧的程序集中为所有挂起的请求提供服务。
HTTP 模块
ASP.NET 应用程序生命周期可通过 IHttpModule 类进行扩展。ASP.NET 包含若干实现 IHttpModule 的类,如 SessionStateModule 类。您还可以自行创建实现 IHttpModule 的类。
如果向应用程序添加模块,模块本身会引发事件。通过使用 modulename_eventname 约定,应用程序可以在 Global.asax 文件中预订这些事件。例如,若要处理 FormsAuthenticationModule 对象引发的 Authenticate 事件,可以创建一个名为 FormsAuthentication_Authenticate 的处理程序。
默认情况下,ASP.NET 中会启用 SessionStateModule 类。所有会话事件将自动命名为 Session_event,如 Session_Start。每次创建新会话时都会引发 Start 事件。有关更多信息,请参见会话状态概述。