每當(dāng)請(qǐng)求IIS容納的ASP.NET頁(yè)時(shí),總是要把請(qǐng)求轉(zhuǎn)交給了ASP.NET HTTP 管道。HTTP管道是一組被控對(duì)象,這些對(duì)象按順序處理請(qǐng)求并且把這些請(qǐng)求轉(zhuǎn)換成一般HTML文本。HTTP管道的入口是HttpRuntime 類。ASP.NET的底層結(jié)構(gòu)為每一個(gè)應(yīng)用程序域 ( AppDomain )的工作進(jìn)程建立了一個(gè)這個(gè)類(HttpRuntime)的實(shí)例(注意,一個(gè)工作進(jìn)程只能支持一個(gè)正在運(yùn)行的ASP.NET應(yīng)用域)。
HttpRuntime 類從內(nèi)部程序池中選擇一個(gè) HttpApplcation 對(duì)象,并且在接收到請(qǐng)求的時(shí)候使它工作。Http應(yīng)用管理程序的主要工作是尋找這樣的類使之能夠處理請(qǐng)求。例如:當(dāng)請(qǐng)求一個(gè).aspx資源時(shí),處理句柄就是一個(gè)從Page繼承類的實(shí)例。請(qǐng)求資源的類型和相關(guān)處理句柄的關(guān)系映射表被保存在應(yīng)用程序的配置文件里。更確切的說,這個(gè)映射表就定義在machie.config里的
一個(gè)擴(kuò)展可以和一個(gè)句柄類聯(lián)系起來,更一般說,是和一個(gè)句柄工廠類相聯(lián)系。在所有情況下,負(fù)責(zé)處理請(qǐng)求的HttpApplication對(duì)象得到一個(gè)從IHttpHandler接口具體實(shí)現(xiàn)的對(duì)象。如果是根據(jù)HTTP句柄來處理資源和相關(guān)處理類的關(guān)系,則返回類是直接實(shí)現(xiàn)相關(guān)的接口的;如果資源是綁定到一個(gè)句柄工廠的話,將必須經(jīng)過另外一個(gè)階段:具體實(shí)現(xiàn)IHttpHandlerFactory接口的句柄工廠類的GetHandler方法將返回一個(gè)基于IHttpHandler的對(duì)象。
Http運(yùn)行時(shí)怎么結(jié)束一個(gè)周期或關(guān)閉一個(gè)頁(yè)面請(qǐng)求的進(jìn)程呢?IHttpHandler接口的ProcessRequest方法擁有這個(gè)功能。調(diào)用代表被請(qǐng)求頁(yè)面的對(duì)象的該方法,ASP.NET底層結(jié)構(gòu)打開一個(gè)進(jìn)程來為瀏覽器產(chǎn)生輸出。
Page類
一個(gè)頁(yè)面的HTTP處理句柄的類型取決于URL。當(dāng)這個(gè)URL被首次訪問,一個(gè)新的類將被構(gòu)建并動(dòng)態(tài)的編譯成一個(gè)程序集。一個(gè)分析aspx文件的進(jìn)程從aspx文件中分離出這個(gè)類的代碼。在默認(rèn)情況下,這個(gè)類被加入到一個(gè)叫做asp的名字空間里,并且把URL作為這個(gè)類的類名。例如,如果請(qǐng)求的URL是page.aspx,則這個(gè)類就是ASP.Page_aspx。這個(gè)類名,可以通過設(shè)置@Page預(yù)處理指令的ClassName屬性來修改。
HTTP句柄的基礎(chǔ)類是Page類。這個(gè)類定義了一組最小方法和屬性集,這些方法和屬性被所有的頁(yè)面處理句柄所共享。Page類具體實(shí)現(xiàn)了IHttpHandler接口。
在另外一種和上述相對(duì)應(yīng)的情況中,實(shí)際處理頁(yè)面的句柄的基礎(chǔ)類并不是Page類,而是一個(gè)別的類。當(dāng)使用后代碼模式時(shí),這個(gè)情況就發(fā)生了。后代碼是一種將C#或VB.NET代碼和頁(yè)面分離的技術(shù)。頁(yè)面代碼是一組事件處理句柄和其他一些方法的集合,這些方法定義了頁(yè)面的各種行為。這些代碼可以以內(nèi)聯(lián)形式用<script runat=server>標(biāo)簽定義,或者你可以用外部類形式來寫——這就是后代碼模式。后代碼類是從Page類繼承的,但是具體化或者重新定義了一些其他的方法。在指定了頁(yè)面的后代碼類后,這個(gè)后代碼類就作為HTTP處理句柄。
在其他的情況下,如果應(yīng)用程序的配置文件中重定義了
PageBaseType屬性指明了包含頁(yè)面處理句柄父類的類型和程序集。來自Page類的這些類自動(dòng)賦予一些通?;驍U(kuò)展的方法和屬性的集合給處理句柄?! ?
頁(yè)面生命周期
一旦HTTP頁(yè)面處理句柄被明確的定義了,ASP.NET運(yùn)行時(shí)調(diào)用處理句柄的ProcessRequest方法來處理請(qǐng)求。通常,沒有必要改變Page類提供的執(zhí)行方法。
頁(yè)面執(zhí)行是從FrameworkInitialize方法開始的,這個(gè)方法為頁(yè)面構(gòu)建控件樹。該方法是TemplageControl類的受保護(hù)并且是虛方法。任何為aspx資源動(dòng)態(tài)生成的句柄覆蓋了該方法。在這個(gè)方法里,頁(yè)面的所有控件樹都被構(gòu)建了。
接下來,ProcessRequest方法使頁(yè)面經(jīng)歷了不同的幾個(gè)階段:初始化、加載視圖狀態(tài)信息、回傳數(shù)據(jù)、加載頁(yè)面代碼和執(zhí)行回傳的服務(wù)器事件。在這之后,頁(yè)面轉(zhuǎn)換到了顯示模式:收集被更新的視圖狀態(tài);產(chǎn)生HTML代碼,并且傳送到控制臺(tái)。最后,頁(yè)面卸載,請(qǐng)求的全部服務(wù)結(jié)束了。
在各個(gè)不同階段里,頁(yè)面處理了與web控件相關(guān)、程序員代碼能夠干預(yù)并解決一定問題的事件。其間一些事件是專門為那些內(nèi)嵌控件和不能在.aspx代碼級(jí)別處理的控件而設(shè)計(jì)的。
一個(gè)頁(yè)面要解決這樣的事件,它能明確的注冊(cè)成為合適的句柄。但是,為了和原有的Visual Basic編程模式有后向兼容性,ASP.NET也支持了隱含事件的形式。在默認(rèn)情況下,頁(yè)面會(huì)尋找和事件相關(guān)的方法名;如果找到和事件相匹配的方法,這個(gè)方法就被認(rèn)為是這種事件的處理程序。ASP.NET提供了六種專門的方法名,他們是 Page_Init , Page_Load , Page_DataBind , Page_PreRender 和 Page_Unload 。這些方法這些方法在Page類中已經(jīng)被定義過,他們是相應(yīng)事件的處理程序。HTTP運(yùn)行時(shí)將自動(dòng)的將這些方法綁定到相關(guān)的頁(yè)面事件,而不需要程序員去編寫把事件和方法聯(lián)系起來的代碼。舉個(gè)例子來說,在下面的代碼中, Page_Load方法和頁(yè)面的加載事件相關(guān)聯(lián):
this.Load + = new EventHandler(this.Page_Load);
這種自動(dòng)識(shí)別是被 @Page 預(yù)指令的AutoEventWireup 屬性控制的。如果這個(gè)屬性被置false ,應(yīng)用程序必須顯式聲明和事件相關(guān)的方法。不自動(dòng)關(guān)聯(lián)頁(yè)面事件代碼的頁(yè)面執(zhí)行起來會(huì)快一些,是因?yàn)樗麄儾恍枰谄ヅ渖献鲞^多的工作。在Visual Studio.NET 工程里可以把這個(gè)屬性關(guān)閉掉。但是,默認(rèn)設(shè)置是true,這意味著Page_Load方法被自動(dòng)識(shí)別并被關(guān)聯(lián)到相關(guān)的事件。
頁(yè)面執(zhí)行包含了下表中按順序列出的幾個(gè)階段,他們被標(biāo)志成為應(yīng)用程序級(jí)別的事件,同時(shí)也可能是一些受保護(hù)、重定義的方法:
階段
頁(yè)面事件
可重定義的方法
頁(yè)面初始化
Init
視圖狀態(tài)加載
LoadViewState
回傳數(shù)據(jù)處理
控件里實(shí)現(xiàn)了IPostBackDataHandler接口的LoadPostData方法
頁(yè)面加載
Load
回傳數(shù)據(jù)變化檢查
控件里實(shí)現(xiàn)了IPostBackDataHandler接口的RaisePostDataChangedEvent方法
回傳事件處理
控件里定義的回傳事件
控件里實(shí)現(xiàn)了IPostBackEventHandler接口的RaisePostBackEvent方法
頁(yè)面預(yù)返回階段
PreRender
頁(yè)面返回階段
Render
頁(yè)面卸載階段
Unload
上表中列出的階段有的在頁(yè)面級(jí)別是不可見的,他們只是在服務(wù)器控件的作者編寫繼承于Page的類時(shí)會(huì)使用到。Init , Load , PreRender , Unload,再加上定義在內(nèi)嵌控件中的回傳處理事件,他們構(gòu)成了頁(yè)面的整個(gè)生命周期。
各個(gè)階段的執(zhí)行
頁(yè)面生命周期的第一階段是初始化。這個(gè)階段被Init事件所描述,這個(gè)事件在控件樹被構(gòu)建出來后執(zhí)行。換句話說,當(dāng)Init事件發(fā)生時(shí),所有在.aspx文件中靜態(tài)聲明的控件被實(shí)例化并被賦予了默認(rèn)值。在Init事件中可以初始化任何的在頁(yè)面生命周期里需要的設(shè)置。例如:在這個(gè)階段,控件可以加載外部的摸版文件或者是為事件建立處理句柄。需要注意的是,任何的視圖狀態(tài)信息在這個(gè)階段里是不能用的。
緊接著初始化結(jié)束后,頁(yè)面構(gòu)架為頁(yè)面加載視圖狀態(tài)。視圖狀態(tài)是 名稱/值 對(duì)的集合,控件或頁(yè)面在這里保存的數(shù)據(jù)在整個(gè)web請(qǐng)求過程中必須是穩(wěn)固的。視圖狀態(tài)代表著頁(yè)面的上下文。典型的,它保存著頁(yè)面上次在服務(wù)器上被執(zhí)行時(shí)控件的狀態(tài)。視圖狀態(tài)在會(huì)話開始的第一個(gè)頁(yè)面請(qǐng)求時(shí)是空的。在默認(rèn)情況下,試圖狀態(tài)被保存在一個(gè)隱藏域里,這個(gè)隱藏域是被自動(dòng)添加到頁(yè)面里的。這個(gè)隱藏域的名稱是 __VIEWSTATE。如果覆蓋了LoadViewState方法——在Control類里被聲明為受保護(hù)的方法——組件開發(fā)者可以控制視圖狀態(tài)的保存和它是如何和內(nèi)部狀態(tài)形成映射。
象LoadPageStateFormPersistenceMedium這樣的方法和與其相對(duì)應(yīng)的SavePageStateToPersistenceMedium方法可以用來加載或者保存視圖狀態(tài)到其他的存儲(chǔ)中介里,例如:會(huì)話、數(shù)據(jù)庫(kù)或者是服務(wù)器上的文件。和LoadViewState方法不相同的是,上面提到的方法只能在Page的繼承類里使用。
一旦視圖狀態(tài)加載完畢了,頁(yè)面里的控件被賦予了和上一次發(fā)送到瀏覽器時(shí)一樣的狀態(tài)。下一個(gè)階段是將他們更新,使之與服務(wù)器端發(fā)生的變化相一致。在回傳數(shù)據(jù)處理階段,控件更新他們的狀態(tài),使之和客戶端的HTML元素的狀態(tài)相一致。例如,服務(wù)器控件TextBox有和它相對(duì)應(yīng)的HTML控件。在回傳數(shù)據(jù)階段,TextBox控件將得到標(biāo)簽的值,并且用他來更新他的內(nèi)部狀態(tài)。每一個(gè)控件都可以從回傳數(shù)據(jù)中取得自己數(shù)據(jù)的能力,并且把自己的狀態(tài)更新。TextBox控件將更新它的Text屬性,同樣的,CheckBox控件也會(huì)將他們的Checked屬性刷新。服務(wù)器控件和HTML元素的匹配是通過兩者的ID來進(jìn)行的。
在回傳數(shù)據(jù)處理的最后階段,所有的頁(yè)面控件反映了上一個(gè)被更新的狀態(tài),這些都是由于客戶端的輸入變化所引起的。接下來,Load事件將被頁(yè)面執(zhí)行。
有一些控件,在兩次請(qǐng)求中如果某些敏感屬性發(fā)生了變化,他們需要對(duì)此作出響應(yīng),并且完成一定的任務(wù)。例如,如果客戶端的textbox控件的文本發(fā)生變化,這個(gè)控件就激發(fā)了TextChanged事件。根據(jù)自客戶端的數(shù)據(jù),如果控件的一個(gè)或多個(gè)屬性發(fā)生了變化,每一個(gè)控件都可以精確的激發(fā)合適的事件來處理。這些控件實(shí)現(xiàn)了IPostBackDataHandler接口,這個(gè)接口中的LoadPostData方法在Load事件之后就被執(zhí)行了。通過重定義LoadPostData方法,控件可以驗(yàn)證兩次請(qǐng)求中發(fā)生的變化并且激起相關(guān)的事件處理程序。
在一個(gè)頁(yè)面周期中的關(guān)鍵事件是那些由客戶端事件激發(fā)在服務(wù)器執(zhí)行一段代碼的事件。例如,當(dāng)用戶點(diǎn)擊一個(gè)按鈕,頁(yè)面就需要回傳。這個(gè)事件的處理是從按鈕ID和值的收集開始的。如果控件是實(shí)現(xiàn)了IPostBackEventHandler接口(Button和LinkButton就是這樣的情況),頁(yè)面構(gòu)架將調(diào)用RaisePostBackEvent方法。這個(gè)方法的具體情況是取決于控件的類型的。在上面提到的Button和LinkButton控件,這個(gè)方法就將尋找Click事件處理程序?! ?
處理了回傳事件之后,頁(yè)面就準(zhǔn)備被發(fā)送出去了。這個(gè)階段是從PreRender事件開始的。這對(duì)于控件來說,那些需要在視圖信息被保存與結(jié)果被發(fā)送之前這段時(shí)間里執(zhí)行的動(dòng)作,這是一個(gè)很好的時(shí)機(jī)。下一步就是SaveViewState ,所有的空間和頁(yè)面本身就把視圖狀態(tài)的集合內(nèi)容保存起來。接下來,視圖狀態(tài)被串行化、哈希編碼、Base64 編碼,并且保存在__VIEWSTATE隱藏域里。
各個(gè)控件的發(fā)送機(jī)制可以通過重定義Render方法來改變。這個(gè)方法構(gòu)建了一個(gè)HTML writer對(duì)象,用它來為控件產(chǎn)生HTML代碼。對(duì)Page類里Render方法的默認(rèn)執(zhí)行包含了對(duì)所有成員控件的遞歸調(diào)用。頁(yè)面為每一個(gè)控件調(diào)用一次Render方法,并緩沖HTML輸出。
頁(yè)面生命周期的最后階段是卸載事件,這個(gè)事件在頁(yè)面對(duì)象消失前被激發(fā)。在這個(gè)事件里,你應(yīng)該把任何臨界資源釋放掉(例如:文件、圖形對(duì)象、數(shù)據(jù)庫(kù)連接)。
最后,瀏覽器接收到了HTTP響應(yīng),并且把頁(yè)面顯示出來。
總結(jié)
ASP.NET頁(yè)面對(duì)象模型是一個(gè)有特點(diǎn)的新穎的模型,因?yàn)樗腔谑录C(jī)制的。一個(gè)Web頁(yè)由一些控件組成,這些控件擁有豐富的基于HTML的用戶接口,同時(shí)通過事件和用戶進(jìn)行交互。在Web應(yīng)用程序上下文環(huán)境中構(gòu)建一個(gè)事件模型是富有挑戰(zhàn)性的。使客戶端產(chǎn)生的事件和服務(wù)器上的代碼關(guān)聯(lián)起來是令人驚異的,這個(gè)過程是輸出同HTML也一樣是可見的,只不過他們?cè)谛枰臅r(shí)候被恰當(dāng)?shù)男薷摹?
要了解頁(yè)面生命周期的各個(gè)階段、頁(yè)面對(duì)象是怎樣實(shí)例化并被HTTP運(yùn)行時(shí)所使用,掌握這個(gè)模型是很重要的。
</script>