適用於: Microsoft® ASP.NET 摘要:瞭解爲 ASP.NET Web 頁面創建的事件模型,以及 Web 頁面轉變爲 HTML 過程當中的各個階段。ASP.NET HTTP 運行時負責管理對象管道,這些對象首先將請求的 URL 轉換成 Page 類的具體實例,而後再將這些實例轉換成純 HTML 文本。本文將探討那些做爲頁面生命週期標誌的事件,以及控件和頁面編寫者如何幹預並改變標準行爲。(本文包含一些指向英文站點的連接。) 目錄 簡介 真正的 Page 類 頁面的生命週期 執行的各個階段 小結 簡介 對由 Microsoft® Internet 信息服務 (IIS) 處理的 Microsoft® ASP.NET 頁面的每一個請求都會被移交到 ASP.NET HTTP 管道。HTTP 管道由一系列託管對象組成,這些託管對象按順序處理請求,並將 URL 轉換爲純 HTML 文本。HTTP 管道的入口是 HttpRuntime 類。ASP.NET 結構爲輔助進程中的每一個 AppDomain 建立一個此類的實例。(請注意,輔助進程爲每一個當前正在運行的 ASP.NET 應用程序維護一個特定的 AppDomain。) HttpRuntime 類從內部池中獲取 HttpApplication 對象,並安排此對象來處理請求。HTTP 應用程序管理器完成的主要任務就是找到將真正處理請求的類。當請求 .aspx 資源時,處理程序就是頁面處理程序,即從 Page 繼承的類的實例。資源類型和處理程序類型之間的關聯關係存儲在應用程序的配置文件中。更確切地說,默認的映射集是在 machine.config 文件的 <httpHandlers> 部分定義的。可是,應用程序能夠在本地的 web.config 文件中自定義本身的 HTTP 處理程序列表。如下這一行代碼就是用來爲 .aspx 資源定義 HTTP 處理程序的。 <add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory"/>
擴展名能夠與處理程序類相關聯,而且更可能是與處理程序工廠類相關聯。在全部狀況下,負責處理請求的 HttpApplication 對象都會得到一個實現 IHttpHandler 接口的對象。若是根據 HTTP 處理程序來解析關聯的資源/類,則返回的類將直接實現接口。若是資源被綁定處處理程序工廠,則還須要額外的步驟。處理程序工廠類實現 IHttpHandlerFactory 接口,此接口的 GetHandler 方法將返回一個基於 IHttpHandler 的對象。 HTTP 運行時是如何結束這個循環並處理頁面請求的?ProcessRequest 方法在 IHttpHandler 接口中很是重要。經過對錶明被請求頁面的對象調用此方法,ASP.NET 結構會啓動將生成瀏覽器輸出的進程。 真正的 Page 類 特定頁面的 HTTP 處理程序類型取決於 URL。首次調用 URL 時,將構建一個新的類,這個類被動態編譯爲一個程序集。檢查 .aspx 資源的分析進程的結果是類的源代碼。該類被定義爲命名空間 ASP 的組成部分,而且被賦予了一個模擬原始 URL 的名稱。例如,若是 URL 的終點是 page.aspx,則類的名稱就是 ASP.Page_aspx。不過,類的名稱能夠經過編程方式來控制,方法是在 @Page 指令中設置 ClassName 屬性。 HTTP 處理程序的基類是 Page。這個類定義了由全部頁面處理程序共享的方法和屬性的最小集合。Page 類實現 IHttpHandler 接口。 在不少狀況下,實際處理程序的基類並非 Page,而是其餘的類。例如,若是使用了代碼分離,就會出現這種狀況。代碼分離是一項開發技術,它能夠將頁面所需的代碼隔離到單獨的 C# 和 Microsoft Visual Basic® .NET 類中。頁面的代碼是一組事件處理程序和輔助方法,這些處理程序和方法真正決定了頁面的行爲。可使用 <script runat=server> 標記對此代碼進行內聯定義,或者將其放置在外部類(代碼分離類)中。代碼分離類是從 Page 繼承並使用額外的方法的類,被指定用做 HTTP 處理程序的基類。 還有一種狀況,HTTP 處理程序也不是基於 Page 的,即在應用程序配置文件的 <pages> 部分中,包含了 PageBaseType 屬性的從新定義。 <pages PageBaseType="Classes.MyPage, mypage" />
PageBaseType 屬性指明包含頁面處理程序的基類的類型和程序集。從 Page 導出的這個類能夠自動賦予處理程序擴展的自定義方法和屬性集。 頁面的生命週期 徹底識別 HTTP 頁面處理程序類後,ASP.NET 運行時將調用處理程序的 ProcessRequest 方法來處理請求。一般狀況下,無需更改此方法的實現,由於它是由 Page 類提供的。 此實現將從調用爲頁面構建控件樹的 FrameworkInitialize 方法開始。FrameworkInitialize 方法是 TemplateControl 類(Page 自己今後類導出)的一個受保護的虛擬成員。全部爲 .aspx 資源動態生成的處理程序都將覆蓋 FrameworkInitialize。在此方法中,構建了頁面的整個控件樹。 接下來,ProcessRequest 使頁面經歷了各個階段:初始化、加載視圖狀態信息和回發數據、加載頁面的用戶代碼以及執行回發服務器端事件。以後,頁面進入顯示模式:收集更新的視圖狀態,生成 HTML 代碼並隨後將代碼發送到輸出控制檯。最後,卸載頁面,並認爲請求處理完畢。 在各個階段中,頁面會觸發少數幾個事件,這些事件能夠由 Web 控件和用戶定義的代碼截取並進行處理。其中的一些事件是嵌入式控件專用的,所以沒法在 .aspx 代碼級進行處理。 要處理特定事件的頁面應該明確註冊一個適合的處理程序。不過,爲了向後兼容早期的 Visual Basic 編程風格,ASP.NET 也支持隱式事件掛鉤的形式。默認狀況下,頁面會嘗試將特定的方法名稱與事件相匹配,若是實現匹配,則認爲此方法就是匹配事件的處理程序。ASP.NET 提供了六種方法名稱的特定識別,它們是 Page_Init、Page_Load、Page_DataBind、Page_PreRender 和 Page_Unload。這些方法被認爲是由 Page 類提供的相應事件的處理程序。HTTP 運行時會自動將這些方法綁定到頁面事件,這樣,開發人員就沒必要再編寫所需的粘接代碼了。例如,若是命名爲 Page_Load 的方法綁定到頁面的 Load 事件,則可省去如下代碼。 this.Load += new EventHandler(this.Page_Load);
對特定名稱的自動識別是由 @Page 指令的 AutoEventWireup 屬性控制的。若是該屬性設置爲 false,則要處理事件的全部應用程序都須要明確鏈接到頁面事件。不使用自動綁定事件的頁面性能會稍好一些,由於不須要額外匹配名稱與事件。請注意,全部 Microsoft Visual Studio® .NET 項目都是在禁用 AutoEventWireup 屬性的狀況下建立的。可是,該屬性的默認設置是 true,即 Page_Load等方法會被識別,並被綁定到相關聯的事件。 下表中按順序列出了頁面的執行包括的幾個階段,執行的標誌是一些應用程序級的事件和/或受保護並可覆蓋的方法。 表 1:ASP.NET 頁面生命中的關鍵事件
階段 |
頁面事件 |
可覆蓋的方法 |
頁面初始化 |
Init |
|
加載視圖狀態 |
|
LoadViewState |
處理回發數據 |
|
任意實現 IPostBackDataHandler 接口的控件中的 LoadPostData 方法 |
加載頁面 |
Load |
|
回發更改通知 |
|
任意實現 IPostBackDataHandler 接口的控件中的 RaisePostDataChangedEvent 方法 |
處理回發事件 |
由控件定義的任意回發事件 |
任意實現 IPostBackDataHandler 接口的控件中的 RaisePostBackEvent 方法 |
頁面顯示前階段 |
PreRender |
|
保存視圖狀態 |
|
SaveViewState |
顯示頁面 |
|
Render |
卸載頁面 |
Unload |
|
以上所列的階段中有些在頁面級是不可見的,而且僅對服務器控件的編寫者和要建立從 Page 導出的類的開發人員有意義。Init、Load、PreRender、Unload,再加上由嵌入式控件定義的全部回發事件,就構成了向外發送頁面的各個階段標記。 執行的各個階段 頁面生命週期中的第一個階段是初始化。這個階段的標誌是 Init 事件。在成功建立頁面的控件樹後,將對應用程序觸發此事件。換句話說,當 Init 事件發生時,.aspx 源文件中靜態聲明的全部控件都已實例化並採用各自的默認值。控件能夠截取 Init 事件以初始化在傳入的 Web 請求的生命週期內所需的全部設置。例如,這時控件能夠加載外部模板文件或設置事件的處理程序。請注意,這時視圖狀態信息尚不可用。 初始化以後,頁面框架將加載頁面的視圖狀態。視圖狀態是名稱/值對的集合,在此集合中,控件和頁面自己存儲了對全部 Web 請求都必須始終有效的所有信息。視圖狀態表明瞭頁面的調用上下文。一般,它包含上次在服務器上處理頁面時控件的狀態。首次在會話中請求頁面時,視圖狀態爲空。默認狀況下,視圖狀態存儲在靜默添加到頁面的隱藏字段中,該字段的名稱是 __VIEWSTATE。經過覆蓋 LoadViewState 方法(Control 類的受保護、可覆蓋方法),組件開發人員能夠控制視圖狀態的存儲方式以及視圖狀態的內容映射到內部狀態的方式。 有些方法(如 LoadPageStateFromPersistenceMedium 以及其對應的 SavePageStateToPersistenceMedium),能夠用來將視圖狀態加載並保存到其餘存儲介質(例如會話、數據庫或服務器端文件)中。與 LoadViewState 不一樣,上述方法只能在從 Page 導出的類中使用。 存儲視圖狀態以後,頁面樹中控件的狀態與頁面最後一次顯示在瀏覽器中的狀態相同。下一步是更新它們的狀態以加入客戶端的更改。處理回發數據階段使控件有機會更新其狀態,從而準確反映客戶端相應的 HTML 元素的狀態。例如,服務器的 TextBox 控件對應的 HTML 元素是 <input type=text>。在回發數據階段,TextBox 控件將檢索 <input> 標記的當前值,並使用該值來刷新本身內部的狀態。每一個控件都要從回發的數據中提取值並更新本身的部分屬性。TextBox 控件將更新它的 Text 屬性,而 CheckBox 控件將刷新它的 Checked 屬性。服務器控件和 HTML 元素的對應關係能夠經過兩者的 ID 找到。 在處理回發數據階段的最後,頁面中的全部控件的狀態都將使用客戶端輸入的更改來更新前一狀態。這時,將對頁面觸發 Load 事件。 頁面中可能會有一些控件,當其某個敏感屬性在兩個不一樣的請求中被修改時,須要完成特定的任務。例如,若是 TextBox 控件的文本在客戶端被修改,則此控件將觸發 TextChanged 事件。每一個控件在其一個或多個屬性被修改成客戶端輸入的值時均可以決定觸發相應的事件。對於這些更改對其很是關鍵的控件,控件實現 IPostBackDataHandler 接口,此接口的 LoadPostData 方法是在 Load 事件後當即調用的。經過對 LoadPostData 方法進行編碼,控件將驗證自上次請求後是否發生了關鍵更改,並觸發本身的更改事件。 頁面生命週期中的關鍵事件是被調用以執行服務器端代碼的事件,此代碼與客戶端觸發的事件相關聯。當用戶單擊按鈕時,將回發頁面。回發值的集合中包括啓動整個操做的按鈕的 ID。若是控件實現 IPostBackEventHandler 接口(如按鈕和連接按鈕),頁面框架將調用 RaisePostBackEvent 方法。此方法的行爲取決於控件的類型。就按鈕和連接按鈕而言,此方法將查找 Click 事件處理程序並運行相關的委託。 處理完回發事件以後,頁面就能夠顯示了。這個階段的標誌是 PreRender 事件。控件能夠利用這段時間來執行那些須要在保存視圖狀態和顯示輸出的前一刻執行的更新操做。下一個狀態是 SaveViewState,在此狀態中,全部控件和頁面自己都將更新本身 ViewState 集合的內容。而後,將獲得序列化、散列、Base64 編碼的視圖狀態,並且此視圖狀態與隱藏字段 __VIEWSTATE 相關聯。 經過覆蓋 Render 方法能夠改變各個控件的顯示機制。此方法接受 HTML 書寫器對象,並使用此對象來積累全部要爲控件生成的 HTML 文本。Page 類的 Render 方法的默認實現包括對全部成員控件的遞歸調用。對於每一個控件,頁面都將調用 Render 方法,並緩存 HTML 輸出。 頁面生命中的最後一個標誌是 Unload 事件,在頁面對象消除以前發生。在此事件中,您應該釋放全部可能佔用的關鍵資源(例如文件、圖形對象、數據庫鏈接等)。 在此事件以後,也就是最後,瀏覽器接收 HTTP 響應數據包並顯示頁面。 小結 ASP.NET 頁面對象模型因其事件機制而顯得格外新穎獨特。Web 頁面由控件組成,這些控件既能夠產生豐富的基於 HTML 的用戶界面,又能夠經過事件與用戶交互。之前,在 Web 應用程序的上下文中設置事件模型是件有挑戰性的工做。可咱們驚奇的看到,客戶端生成的事件能夠由服務器端的代碼來解決,並且只進行一些相應的修改後,此過程仍能夠輸出相同的 HTML 頁面。 掌握這個模型對於瞭解頁面生命週期的各個階段,以及頁面對象如何被 HTTP 運行時實例化並使用是很是重要的。 關於做者 Dino Esposito 是一位來自意大利羅馬的培訓教師和顧問。做爲 Wintellect 團隊的成員,Dino 專門研究 ASP.NET 和 ADO.NET,主要在歐洲和美國從事教學和諮詢工做。此外,Dino 還負責管理 Wintellect 的 ADO.NET 課件,併爲 MSDN 期刊的「Cutting Edge」專欄撰寫文章。要與他聯繫,請向 dinoe@wintellect.com 發送電子郵件。 |