ASP.Net请求处理机制初步探索之旅 - Part 4 WebForm页面生命周期
開篇:上一篇我們了解了所謂的請求處理管道,在眾多的事件中微軟開放了19個重要的事件給我們,我們可以注入一些自定義的業(yè)務(wù)邏輯實(shí)現(xiàn)應(yīng)用的個性化設(shè)計(jì)。本篇,我們來看看WebForm模式下的頁面生命周期。
(1)Part 1:前奏
(2)Part 2:核心
(3)Part 3:管道
(4)Part 4:WebForm頁面生命周期
(5)Part 5:MVC頁面聲命周期
一、ASP.Net Page的兩個重要部分
在前面對于請求處理管道的介紹中,我們已經(jīng)了解了一個ASP.NET WebForm頁面請求事件的整體流程。那么,在其中一個最重要的部分就是ASP.NET Page頁面,但是我們并沒有對其進(jìn)行詳細(xì)討論。因此,我們在此深入地了解一下ASP.NET頁面事件。
每一個ASP.NET Page頁都有2個部分:一個部分是在瀏覽器中進(jìn)行顯示的部分,它包含了HTML標(biāo)簽、viewstate形式的隱藏域 以及 在HTML input中的數(shù)據(jù)。當(dāng)這個頁面被提交到服務(wù)器時,這些HTML標(biāo)簽會被創(chuàng)建到ASP.NET控件,并且viewstate還會和表單數(shù)據(jù)綁定在一起。另一個部分是在xxx.cs文件中的進(jìn)行業(yè)務(wù)邏輯操作的部分,一旦你在后置代碼中得到所有的服務(wù)器控件,你可以執(zhí)行和寫入你自己的邏輯并呈現(xiàn)給客戶瀏覽器。
其中,后臺代碼類是前臺頁面類的父類,前臺頁面類則是后臺代碼類的子類。這一點(diǎn),可以通過查看每個aspx文件中的頭部,我們都會看到以下的一句代碼:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="FirstPage.aspx.cs" Inherits="WebFormDemo.FirstPage" %>其中CodeBehind這個屬性定義了此aspx頁面的專屬后臺代碼文件的名稱,而Inherits這個屬性則定義了此aspx頁面所要繼承的父類的名稱(這也可以簡單地說明,aspx頁面會單獨(dú)生成一個類,與后臺代碼類不重合在一起)。因此,aspx.cs就是aspx的后置處理代碼,負(fù)責(zé)處理aspx中<%%>和runat="server"的內(nèi)容。
現(xiàn)在這些HTML控件會作為ASP.NET控件存活在服務(wù)器上,ASP.NET會觸發(fā)一系列的事件,我們也可以在這些事件中注入自定義邏輯代碼。根據(jù)你想要執(zhí)行什么樣的任務(wù)/邏輯,我們需要將邏輯合理地放入這些事件之中。
TIP:大部分的開發(fā)者直接使用Page_Load來干所有的事情,但這并不是一個好的思路。因此,無論是填充控件、設(shè)置ViewState還是應(yīng)用主題等所有發(fā)生在頁面加載中的所有事情。因此,如果我們能夠在合適的事件中放入邏輯,那么毫無疑問我們代碼將會干凈很多。
二、ASP.Net Page的頁面事件流程
| 順序 | 事件名稱 | 控件初始化 | ViewState可用 | 表單數(shù)據(jù)可用 | 什么邏輯可以寫在這里? |
| 1 | Init | No | No | No | 注意:你可以通過使用ASP.NET請求對象訪問表單數(shù)據(jù)等,但不是通過服務(wù)器控件。 動態(tài)地創(chuàng)建控件,如果你一定要在運(yùn)行時創(chuàng)建;任何初始化設(shè)置;母版頁及其設(shè)置。在這部分中我們沒有獲得viewstate、提交的數(shù)據(jù)值及已經(jīng)初始化的控件。 |
| 2 | Load View State | Not guaranteed | Yes | Not guaranteed | 你可以訪問View State及任何同步邏輯,你希望viewstate被推到后臺代碼變量可以在這里完成。 |
| 3 | PostBackdata | Not guaranteed | Yes | Yes | 你可以訪問表單數(shù)據(jù)。任何邏輯,你希望表單數(shù)據(jù)被推到后臺代碼變量可以在這里完成。 |
| 4 | Load | Yes | Yes | Yes | 在這里你可以放入任何你想操作控件的邏輯,如從數(shù)據(jù)庫填充combox、對grid中的數(shù)據(jù)排序等。這個事件,我們可以訪問所有控件、viewstate、他們發(fā)送過來的值。 |
| 5 | Validate | Yes | Yes | Yes | 如果你的頁面有驗(yàn)證器或者你想為你的頁面執(zhí)行驗(yàn)證,那就在這里做吧。 |
| 6 | Event | Yes | Yes | Yes | 如果這是通過點(diǎn)擊按鈕或下拉列表的改變的一個回發(fā),相關(guān)的事件將被觸發(fā)。與事件相關(guān)的任何邏輯都可以在這里執(zhí)行。 PS:這個事件想必很多使用WebForm的開發(fā)人員都很常用吧,是否記得那些Button1_Click(Object sender,EventArgs e)? |
| 7 | Pre-render | Yes | Yes | Yes | 如果你想對UI對象做最終的修改,如改變屬性結(jié)構(gòu)或?qū)傩灾?#xff0c;在這些控件保存到ViewState之前。 |
| 8 | Save ViewState | Yes | Yes | Yes | 一旦對服務(wù)器控件的所有修改完成,將會保存控件數(shù)據(jù)到View State中。 |
| 9 | Render | Yes | Yes | Yes | 如果你想添加一些自定義HTML到輸出,可以在這里完成。 |
| 10 | Unload | Yes | Yes | Yes | 任何你想做的清理工作都可以在這里執(zhí)行。 |
三、反編譯探秘ASP.Net Page頁面生命周期
前面我們簡單地了解了一下ASP.NET Page的頁面事件,現(xiàn)在我們來通過Reflector反編譯一下一個demo程序集,來感受一下ASP.NET Page的頁面生命周期。
3.1 準(zhǔn)備一個ASP.NET項(xiàng)目
(1)假如我們有以下的名為Index的一個aspx頁面:
<html xmlns="http://www.w3.org/1999/xhtml"> <head id="headIndex" runat="server"><title>Index頁</title> </head> <body><form id="formIndex" runat="server"><div>哈哈,我是ASP.Net WebForm,下面看我的表演。<br /><%for (int i = 0; i < 5; i++){Response.Write("I am a webform page.<br/>");}%><br /><%= GetServerTime() %><br /><asp:TextBox ID="txtDateTime" runat="server"></asp:TextBox><asp:Button ID="btnGetTime"runat="server" Text="獲取時間" οnclick="btnGetTime_Click" /><br /><% GetDllInfo(); %></div></form> </body> </html>(2)Index所對應(yīng)的后臺代碼如下:
namespace PageLifeCycleDemo {public partial class Index : System.Web.UI.Page{protected void Page_Load(object sender, EventArgs e){}protected string GetServerTime(){string result = "服務(wù)器時間:" + DateTime.Now.ToString();return result;}protected void GetDllInfo(){Response.Write("頁面類名稱:" + this.GetType() + "<br/>");Response.Write("程序集地址:" + this.GetType().Assembly.Location + "<br/>");Response.Write("父類的名稱:" + this.GetType().BaseType + "<br/>");Response.Write("程序集地址:" + this.GetType().BaseType.Assembly.Location + "<br/>");}protected void btnGetTime_Click(object sender, EventArgs e){txtDateTime.Text = DateTime.Now.ToString();}} }這里,我們來重點(diǎn)關(guān)注一下這個方法:我們可以通過寫入以下代碼,然后在aspx中<% GetDllInfo(); %>調(diào)用,它顯示了我們這個ASP.NET項(xiàng)目所屬的程序集在哪個位置?
protected void GetDllInfo() {Response.Write("頁面類名稱:"+this.GetType() + "<br/>");Response.Write("程序集地址:"+this.GetType().Assembly.Location + "<br/>");Response.Write("父類的名稱:"+this.GetType().BaseType + "<br/>");Response.Write("程序集地址:"+this.GetType().BaseType.Assembly.Location + "<br/>"); }瀏覽頁面,會顯示以下結(jié)果:通過下圖可以看到,我們的Index這個頁面會生成一個ASP.index_aspx的類,其父類是Index。
3.2 反編譯生成的臨時程序集
①將DLL拖到Reflector中進(jìn)行查看源代碼
通過上面顯示的路徑找到dll,并拖到反編譯工具(ILSpy或者Reflector,前者開源免費(fèi),后者已經(jīng)收費(fèi),但天朝,你懂的。)進(jìn)行查看。通過下圖可以看出,頁面類aspx是后臺代碼類所綁定的子類,它的名稱是aspx文件名加上“_aspx”后綴。因此,這里也就解釋了為什么在aspx中要訪問的方法必須是public和protected的訪問修飾符才可以。
②一個大型Control:Page類
從上面可以看出,頁面類繼承自后置代碼類,而后置代碼類又繼承自Page類。我們從上一篇管道可以知道,在請求處理管道的第8個事件中創(chuàng)建了Page類對象,那么我們?nèi)タ纯碢age類。
Page類繼承自TemplateControl,顧名思義,Page類是否就是一個模板控件呢?再看看TemplateControl類:
果不其然,其父類是Control類,Page就是一個封裝過的大控件!那么,我們在Page中拖的那些runat="server"的服務(wù)器控件,又是保存在哪里的呢?
原來,在Control父類中,有一個Controls的屬性,它是一個控件的集合:Page中的所有控件,都會存在于這個集合中。
③頁面生命周期的入口:Page類的ProcessRequest方法
從上一篇請求處理管道中,我們知道在第11和第12個事件之間會調(diào)用Page類對象的ProcessRequest方法進(jìn)入頁面生命周期。那么我們來看看這個ProcessRequest方法:
從圖中可以看出,這個方法中首先通過調(diào)用頁面類對象(我們請求的頁面都是繼承于Page類的)重寫的FrameworkInitialize方法開始我們經(jīng)常聽到的構(gòu)造控件樹的過程。下面我們轉(zhuǎn)到index_aspx這個頁面類重寫的FrameworkInitialize方法中取看看是否是進(jìn)行了構(gòu)造頁面控件樹的操作:
④BuildControlTree:構(gòu)造頁面控件樹
看到這里,我們不由地想問,什么是頁面控件樹?在一個aspx頁面中,runat="server"的控件集合構(gòu)成了如下圖所示的一棵頁面控件樹,他們被一一實(shí)例化,并依據(jù)層級關(guān)系存儲到了controls集合中。
了解了什么是頁面控件樹,現(xiàn)在我們看看是如何來構(gòu)造這棵樹的,通過查看BuildControlTree方法,發(fā)現(xiàn)它調(diào)用了多個名為BuildControlX的方法,依次實(shí)例化我們頁面中所需的控件,并添加到控件集合中(這里其實(shí)是將這些服務(wù)器控件作為子控件添加到頁面(頁面本身就是一個大的控件)中,在樹形結(jié)構(gòu)中Page就是一個根節(jié)點(diǎn),而那些Page中的控件則是Page的孩子節(jié)點(diǎn))。
那么,這些BuildControlX(X代表數(shù)字)方法又在做些什么事呢?我們可以通過查看一個BuildControl方法,看看如何打造HtmlForm的:
可以看出,在構(gòu)造HtmlForm控件的過程中,不僅為其設(shè)置了ID(_ctrl.ID="formIndex"),還為其指定了渲染方法(通過設(shè)置委托_ctrl.SetRenderMethodDelegate())。又因?yàn)槲覀兺狭艘粋€TextBox和Button在其中,于是在實(shí)例化HtmlForm這個控件的途中,又去實(shí)例化TextBox和Button對象,并將其作為HtmlForm的子節(jié)點(diǎn),形成一個層級關(guān)系。
⑤確定IsPostBack:是否第一次請求該頁面
現(xiàn)在重新回到Page類的ProcessRequest方法中,在創(chuàng)建頁面控件樹完成之后,開始進(jìn)入一個ProcessRequestMain方法,這個方法則真正地開啟了頁面生命周期之門。
private void ProcessRequest(bool includeStagesBeforeAsyncPoint, bool includeStagesAfterAsyncPoint) {...... this.ProcessRequestMain(includeStagesBeforeAsyncPoint, includeStagesAfterAsyncPoint);...... }我們經(jīng)常在Page_Load方法中使用Page.IsPostBack屬性來判斷請求是否是回發(fā),那么它是在哪里設(shè)置的呢?原來,在ProcessRequestMain方法中:
⑥初始化操作:PreInit-->Init-->InitComplete
接下來就是初始化操作了,初始化操作分為了三個階段:預(yù)初始化、初始化(使用遞歸方式)、初始化完成。
private void ProcessRequestMain(bool includeStagesBeforeAsyncPoint, bool includeStagesAfterAsyncPoint){......this.PerformPreInit();......this.InitRecursive();......this.OnInitComplete();......}預(yù)初始化主要利用App_Themes目錄中的內(nèi)容進(jìn)行初始化主題,并應(yīng)用模板頁。
這里我們主要看看初始化操作,通過查看源代碼,可以看出,該方法通過遞歸調(diào)用子控件的初始化方法,完成了控件集合中所有控件的初始化操作。
internal virtual void InitRecursive(Control namingContainer){......int count = this._controls.Count;for (int i = 0; i < count; i++){Control control = this._controls[i];control.UpdateNamingContainer(namingContainer);if (((control._id == null) && (namingContainer != null)) && !control.flags[0x40]){control.GenerateAutomaticID();}control._page = this.Page;control.InitRecursive(namingContainer);}......}? 再看看初始化方法中都做了哪些初始化操作,細(xì)細(xì)一看,原來就是為其動態(tài)地生成一個ID(control.GenerateAutomaticID()),然后將該控件的page指針指向當(dāng)前Page頁等。PreLoad 預(yù)加載在 Load 事件之前對頁或控件執(zhí)行處理,
⑦加載操作:(LoadState-->ProcessPostData-->)PreLoad-->Load-->
(ProcessPostData-->RaiseChangedEvents-->RaisePostBackEvent-->)LoadComplete
- 首先看看(LoadState-->ProcessPostData)
初始化完成之后,ASP.NET會通過IsPostBack判斷是否是第一次請求,如果不是,那么首先會加載ViewState并對回發(fā)的數(shù)據(jù)進(jìn)行處理。
private void ProcessRequestMain(bool includeStagesBeforeAsyncPoint, bool includeStagesAfterAsyncPoint){if(this.IsPostBack){......this.LoadAllState();......this.ProcessPostData(this._requestValueCollection, true);......}}至于ViewState是什么?又不了解的朋友,可以瀏覽我的另一篇博文:ASP.NET WebForm溫故知新:ViewState,這里就不再贅述。這里LoadAllState方法主要是將隱藏域中的_VIEWSTATE通過解碼獲取控件的狀態(tài)與數(shù)據(jù)信息,而ProcessPostData方法則是進(jìn)行了兩個部分的操作:一是將剛剛獲取到的各個控件的狀態(tài)與數(shù)據(jù)信息填充到頁面控件樹中所對應(yīng)的各個控件中去,二是對比控件狀態(tài)是否發(fā)生了改變?比如被點(diǎn)擊了?被觸發(fā)了某個事件(例如TextChanged、SelectedIndexChanged等)?如有觸發(fā)事件,則把需要觸發(fā)事件的控件放到一個集合當(dāng)中去。
- 再來看看PreLoad-->Load
處理完ViewState后,就開始進(jìn)行正式地加載操作了,如下代碼所示:
private void ProcessRequestMain(bool includeStagesBeforeAsyncPoint, bool includeStagesAfterAsyncPoint){......this.OnPreLoad(EventArgs.Empty);......this.LoadRecursive();......}在正式加載過程中也分為了兩個部分,一個是PreLoad預(yù)加載,另外一個則是重頭戲Load加載(通過方法名可以推斷,該方法是通過遞歸方式調(diào)用加載的)。首先,調(diào)用了OnPreLoad方法進(jìn)行預(yù)加載操作,如果我們需要在 Load 事件之前對頁或控件(這時頁面控件樹已經(jīng)構(gòu)造完成)執(zhí)行處理,就可以使用該事件。通過查看源代碼,在PreLoad方法中會遍歷一個PreLoad事件集合(我們可以自定義注入我們想要的事件),然后依次執(zhí)行委托所持有的事件。
protected virtual void OnPreLoad(EventArgs e) {EventHandler handler = (EventHandler) base.Events[EventPreLoad];if (handler != null){handler(this, e);} }PreLoad之后就是重頭戲,也是我們最為熟悉的Load了,在調(diào)用LoadRecursive()方法進(jìn)入Load事件。
internal virtual void LoadRecursive() {if (this._controlState < ControlState.Loaded){if (this.AdapterInternal != null){this.AdapterInternal.OnLoad(EventArgs.Empty);}else{this.OnLoad(EventArgs.Empty);}}if (this._controls != null){string errorMsg = this._controls.SetCollectionReadOnly("Parent_collections_readonly");int count = this._controls.Count;for (int i = 0; i < count; i++){this._controls[i].LoadRecursive();}this._controls.SetCollectionReadOnly(errorMsg);}if (this._controlState < ControlState.Loaded){this._controlState = ControlState.Loaded;} }從上面可以看出:ASP.NET頁面首先調(diào)用自身的OnLoad方法以引發(fā)自身的Load事件,接著遞歸調(diào)用?Contorls 集合中各個控件的OnLoad方法以引發(fā)它們的Load事件。那么,我們在頁面后置代碼類中經(jīng)常使用的Page_Load事件方法是在哪里調(diào)用的呢?相信我們都有了答案,就在頁面自身的OnLoad方法中。
- 二次經(jīng)歷(ProcessPostData)
加載結(jié)束后,會經(jīng)歷第二次的處理回發(fā)數(shù)據(jù)的事件。那么,我們不禁會問,為何還要第二次進(jìn)行ProcessPostData方法的調(diào)用,我們剛剛不是都已經(jīng)對ViewState進(jìn)行了解碼并對應(yīng)到了對應(yīng)控件樹中的控件了嘛?這里,我們首先看看下面一段代碼:
protected void Page_Load(object sender, EventArgs e){if (IsPostBack){TextBox txtTest = new TextBox();txtTest.Text = "動態(tài)創(chuàng)建的TextBox";formIndex.Controls.Add(txtTest);}}假如我們要在Page_Load事件中動態(tài)地為Form添加一個TextBox控件,那么之前的頁面控件樹就發(fā)生了改變,所以,這里需要進(jìn)行第二次的ProcessPostData方法,現(xiàn)在豁然開朗了吧。
- 事件觸發(fā)(RaiseChangedEvents-->RaisePostBackEvent)
在第二次處理回發(fā)數(shù)據(jù)之后,會調(diào)用RaiseChangedEvents方法觸發(fā)控件狀態(tài)改變事件響應(yīng)方法,例如TextBox_TextChanged、DropDownList_SelectedIndexChanged事件(這些事件中不包括Button_Click這種回發(fā)事件)等。查看源代碼,通過遍歷狀態(tài)改變了的控件的集合(在第一次進(jìn)行ProcessPostData時會檢查控件的狀態(tài)是否發(fā)生了改變,如果改變了就添加到一個集合中)
internal void RaiseChangedEvents() {if (this._changedPostDataConsumers != null){for (int i = 0; i < this._changedPostDataConsumers.Count; i++){Control control = (Control) this._changedPostDataConsumers[i];if (control != null){IPostBackDataHandler postBackDataHandler = control.PostBackDataHandler;if (((control == null) || control.IsDescendentOf(this)) && ((control != null) && (control.PostBackDataHandler != null))){postBackDataHandler.RaisePostDataChangedEvent();}}}} }在處理完?duì)顟B(tài)改變事件響應(yīng)方法后,會調(diào)用RaisePostBackEvent方法觸發(fā)例如按鈕控件的回發(fā)事件,例如Button_Click回發(fā)事件。
private void RaisePostBackEvent(NameValueCollection postData) {if (this._registeredControlThatRequireRaiseEvent != null){this.RaisePostBackEvent(this._registeredControlThatRequireRaiseEvent, null);}else{string str = postData["__EVENTTARGET"];bool flag = !string.IsNullOrEmpty(str);if (flag || (this.AutoPostBackControl != null)){Control control = null;if (flag){control = this.FindControl(str);}if ((control != null) && (control.PostBackEventHandler != null)){string eventArgument = postData["__EVENTARGUMENT"];this.RaisePostBackEvent(control.PostBackEventHandler, eventArgument);}}else{this.Validate();}} }通過查看代碼,發(fā)現(xiàn)通過回傳的表單數(shù)據(jù)中根據(jù)__EVENTTARGET與__EVENTARGUMENT進(jìn)行事件的觸發(fā)。我們可以通過查看ASP.NET生成的前端HTML代碼看到這兩個參數(shù):下圖是一個設(shè)置為AutoPostBack的DropDownList控件,可以發(fā)現(xiàn)回發(fā)事件都是通過調(diào)用_doPostBack這個js代碼進(jìn)行表單的submit,而表單中最重要的兩個參數(shù)就是eventTarget和eventArgument。
通過瀏覽器提供的開發(fā)人員工具查看數(shù)據(jù)請求報文,可以看到除了提交form中的input外,還提交了ASP.Net WebForm預(yù)置的一些隱藏字段,而這些隱藏字段則是WebForm為我們提供便利的基礎(chǔ)。比如EventTarget則記錄剛剛提交給服務(wù)器的是哪個服務(wù)器控件。
事件觸發(fā)完成之后,加載操作就完成了,這時會調(diào)用OnLoadComplete方法進(jìn)行相關(guān)的事件,這里就不再贅述了。
- 頁面渲染 PreRender-->PreRenderComplete-->SaveState-->SaveStateComplete-->Render
這一階段就進(jìn)入了頁面生命周期的尾巴,開始最終頁面的渲染流程:
private void ProcessRequestMain(bool includeStagesBeforeAsyncPoint, bool includeStagesAfterAsyncPoint){......this.PreRenderRecursiveInternal();......this.PerformPreRenderComplete();......this.SaveAllState();......this.OnSaveStateComplete(EventArgs.Empty);......this.RenderControl(this.CreateHtmlTextWriter(this.Response.Output));......}這里我們主要看看PreRender、SaveState和Render三個事件。
既然已經(jīng)進(jìn)入了頁面渲染階段,為何還要有一個PreRender預(yù)呈現(xiàn)階段?通過查找資料,我們發(fā)現(xiàn)微軟這么設(shè)計(jì)是為了給開發(fā)者提供一個最后一次更改頁面控件狀態(tài)或數(shù)據(jù)的機(jī)會,也就說:你可以再在這里注入一個邏輯,最后一次改變控件值,或者統(tǒng)一地改變控件狀態(tài)為某個指定狀態(tài)。
然后就是SaveState,這個很好理解,也就說:剛剛給了你最后一次更改的機(jī)會結(jié)束后,我就要保存最終的ViewState了。這里需要注意的是:服務(wù)器在向?yàn)g覽器返回html之前,對ViewState中的內(nèi)容是進(jìn)行了Base64編碼的;
最后就是Render,進(jìn)行最終的頁面呈現(xiàn)了,換句話說:就是拼接形成HTML字符串。在這個階段,Page 對象會遍歷頁面控件樹并在每個控件上遞歸地調(diào)用此方法。所有 ASP.NET Web 服務(wù)器控件都有一個用于寫出發(fā)送給瀏覽器的控件標(biāo)記的 Render 方法。通過對源代碼進(jìn)行追蹤,可以看到以下代碼:
internal void RenderChildrenInternal(HtmlTextWriter writer, ICollection children) {if ((this.RareFields != null) && (this.RareFields.RenderMethod != null)){writer.BeginRender();this.RareFields.RenderMethod(writer, this);writer.EndRender();}else if (children != null){foreach (Control control in children){control.RenderControl(writer);}} }在Render過程中,會判斷當(dāng)前控件是否含有子控件集合,如果有,那么遍歷各個子控件的Render方法進(jìn)行HTML的渲染。可以想象,從頁面控件樹的根節(jié)點(diǎn)調(diào)用Render方法,會依次遞歸調(diào)用其所有子節(jié)點(diǎn)的Render方法,從而得到一個完整的HTML代碼。
那么,Render方法結(jié)束后,生成的HTML代碼保存到了哪里呢?原來,Render方法的輸出會寫入Page類對象的?Response?屬性的?OutputStream?中,這就是最終的輸出流作為響應(yīng)報文通過HTTP協(xié)議返回給瀏覽器端了。
- 頁面卸載 Unload
自此,狹義上的頁面生命周期就結(jié)束了,但廣義上的頁面聲明周期事件還未結(jié)束,還會經(jīng)歷一個UnLoad事件,該事件首先針對每個控件發(fā)生,繼而針對該頁發(fā)生。在控件中,使用該事件對特定控件執(zhí)行最后清理,如關(guān)閉控件特定數(shù)據(jù)庫連接。對于頁自身,使用該事件來執(zhí)行最后清理工作,如:關(guān)閉打開的文件和數(shù)據(jù)庫連接,或完成日志記錄或其他請求特定任務(wù)。總而言之,Unload就是進(jìn)行最后的清理工作,釋放資源。
總體概覽
一篇文章下來,已耗費(fèi)了好多時間,如果你覺得對你有用,那就麻煩點(diǎn)個推薦吧。如果你覺得本文很爛,那點(diǎn)個反對也是可以的。后面Part 5會探秘ASP.NET MVC的頁面生命流程,今天就此停筆,謝謝!
參考資料
(1)農(nóng)村出來的大學(xué)生,《ASP.NET網(wǎng)頁請求處理全過程(反編譯)》:http://www.cnblogs.com/poorpan/archive/2011/09/25/2190308.html
(2)我自己,《【翻譯】ASP.NET應(yīng)用程序和頁面聲明周期》:http://www.cnblogs.com/edisonchou/p/3958305.html
(3)Shivprasad koirala,《ASP.NET Application and Page Life Cycle》:http://www.codeproject.com/Articles/73728/ASP-NET-Application-and-Page-Life-Cycle
(4)碧血軒,《ASP.NET頁面生命周期》:http://www.cnblogs.com/xhwy/archive/2012/05/20/2510178.html
(5)木宛城主,《ASP.NET那點(diǎn)不為人知的事兒》:http://www.cnblogs.com/OceanEyes/archive/2012/08/13/aspnetEssential-1.html
(6)千年老妖,《ASP.NET頁面生命周期》:http://www.cnblogs.com/hanwenhuazuibang/archive/2013/04/07/3003289.html
(7)MSDN,《Page事件》:http://msdn.microsoft.com/zh-cn/library/system.web.ui.page_events(v=vs.80).aspx
偶像的歌
?PS:背景音樂 from?張國榮?電影英雄本色中的插曲 《當(dāng)年情》
?
?
作者:周旭龍
出處:http://edisonchou.cnblogs.com/
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。
總結(jié)
以上是生活随笔為你收集整理的ASP.Net请求处理机制初步探索之旅 - Part 4 WebForm页面生命周期的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 4月人民币国际支付占有率再降!排名也降了
- 下一篇: [Sharepoint2007对象模型]