目錄分析 ASPX 代碼分析 HTML 客戶端代碼視圖狀態(tài)字段回發(fā)機(jī)制分析類代碼軟件行業(yè)的一個(gè)趨勢是將許多編寫代碼的工作量轉(zhuǎn)移到基本平臺的基礎(chǔ)結(jié)構(gòu)。眾多開發(fā)平臺只是要求開發(fā)人員使用相對寬松的語法,在較高級別上對所需的信息進(jìn)行描述,而不是按照一組嚴(yán)格的語法規(guī)則進(jìn)行逐字節(jié)的硬編碼?,F(xiàn)在,開發(fā)人員經(jīng)常使用 XML 語言來描述所需的結(jié)果,通過編譯器或運(yùn)行時(shí)引擎對內(nèi)容進(jìn)行分析,并將其處理成傳統(tǒng)的可執(zhí)行代碼。
例如,Windows® Presentation Foundation(.NET Framework 3.0 的支柱之一)使用 XAML 作為基于 XML 的呈現(xiàn)語言,以描述表單用戶界面。Microsoft AJAX 庫(以前代碼名為 ASP.NET“Atlas”的系統(tǒng)的一部分)使用其 XML-Script 元語言將相同原則應(yīng)用于富文本網(wǎng)頁(盡管從技術(shù)上看,XML-Script 不屬于其核心發(fā)布內(nèi)容,而是作為非官方示例技術(shù)進(jìn)行共享)。XML-Script 是聲明性布局語言,它將 HTML 元素和腳本組合在一起,形成虛擬的客戶端控件。最終,XML-Script 為客戶端頁面引入了邏輯處理和功能。
使用聲明性語言創(chuàng)作網(wǎng)頁和表單有幾個(gè)優(yōu)點(diǎn)。通過采用此方式,服務(wù)器端組件可以更方便地生成頁面和表單,而不必生成實(shí)際的 Visual Basic®、C# 或 JavaScript 代碼。此外,對于諸如 Visual Studio® 這樣的創(chuàng)作工具,聲明性標(biāo)記就其本質(zhì)而言更容易進(jìn)行設(shè)計(jì)。從體系結(jié)構(gòu)角度來看,采用聲明標(biāo)記的方式,所指定的是頁面元素的行為,而不是這些元素如何實(shí)現(xiàn)這類行為。這樣,就可以創(chuàng)建更多的抽象層。
第一個(gè)利用這種模型的具體編程環(huán)境是 ASP.NET(從版本 1.0 開始)。正如大多數(shù) Web 開發(fā)人員現(xiàn)在所知的,ASP.NET 頁通常是在一、兩個(gè)文件中進(jìn)行編寫的:一個(gè) .aspx 標(biāo)記文件和一個(gè)可選的代碼隱藏文件。代碼隱藏文件中包含了以任何受支持的編程語言(通常是 Visual Basic 或 C#)所編寫的類文件。.aspx 標(biāo)記文件包含形成頁面結(jié)構(gòu)的 HTML 標(biāo)記、ASP.NET 控制標(biāo)記和文字(它還可以包含代碼)。此文本將在運(yùn)行時(shí)進(jìn)行分析,并轉(zhuǎn)換成頁類。這樣的頁類,在與代碼隱藏類和一些系統(tǒng)生成的代碼組合之后,共同形成可執(zhí)行代碼,以處理任何提交的數(shù)據(jù),并生成響應(yīng),然后將其發(fā)送回客戶端。
雖然這個(gè)總體模型為絕大多數(shù) ASP.NET 開發(fā)人員所知,但還是存在很多只有少部分開發(fā)人員有深入了解的“黑洞”。MSDN®、相關(guān)書籍和在線文章對頁面機(jī)制的各個(gè)方面進(jìn)行了解釋,但仍然缺少對頁面內(nèi)部機(jī)制進(jìn)行的全面而統(tǒng)一的介紹。如果看一看 ASP.NET 頁的 HTML 源代碼,就會(huì)發(fā)現(xiàn)很多您可能幾乎不了解的隱藏字段和自動(dòng)插入的 JavaScript 代碼塊。但是,正是在這些字段和代碼塊的支持下,網(wǎng)頁才能正常工作。在本專欄中,我將分析 ASP.NET 頁所生成的客戶端源代碼。我不單要討論如視圖狀態(tài)這類大家熟悉的隱藏字段,而且還會(huì)涉及到一些少有人知的隱藏字段,例如,控件狀態(tài)、事件驗(yàn)證、事件目標(biāo)和參數(shù),以及系統(tǒng)提供的腳本代碼。
我在此處討論的很多實(shí)現(xiàn)細(xì)節(jié)均是針對當(dāng)前的 ASP.NET 版本而言的。這些細(xì)節(jié)在將來的版本中會(huì)有所更改(相對于過去的版本已有了更改),因此您不應(yīng)當(dāng)構(gòu)建任何依賴于不成文細(xì)節(jié)的運(yùn)行代碼。
分析 ASPX 代碼
圖 1 顯示了一個(gè)雖然很小但可以運(yùn)行的 ASP.NET 頁。盡管它非常簡單,但這是一個(gè)很好示例,因?yàn)樗ㄕ鎸?shí)環(huán)境中的 ASP.NET 頁面的典型元素:輸入域、可點(diǎn)擊的回發(fā)元素以及只讀元素。
.aspx 頁包含三個(gè)服務(wù)器控件:用于捕獲數(shù)據(jù)的文本框、用于啟動(dòng)回發(fā)操作的提交按鈕、用于顯示只讀數(shù)據(jù)的標(biāo)簽。在 .aspx 文件頂部,Page 指令定義了單個(gè)頁面的一些全局屬性。讓我們看一看 Page 指令的最常用屬性,比如在
圖 1 中顯示的那些屬性。<%@ Page Language="C#"AutoEventWireup="true"CodeFile="Test.aspx.cs"Inherits="Test"%>
大多數(shù) Page 指令屬性對頁標(biāo)記(即,瀏覽器通過 HTTP 響應(yīng)接收的 HTML 代碼)的影響都有限。但是,大部分 Page 屬性都會(huì)影響由系統(tǒng)在 .aspx 標(biāo)記和代碼隱藏類的頂部構(gòu)建的動(dòng)態(tài)生成頁的代碼。Language 屬性指定在 Visual Studio 中創(chuàng)作代碼隱藏類所使用的語言。系統(tǒng)將使用相同語言生成動(dòng)態(tài)頁類,以處理瀏覽器對 .aspx 資源的請求。CodeFile 屬性指示存儲(chǔ)代碼隱藏類的源文件。Inherits 屬性指示在代碼文件中應(yīng)當(dāng)作為動(dòng)態(tài)生成的頁類的父類的代碼隱藏類的名稱。最后,AutoEventWireup 屬性指示是否應(yīng)當(dāng)使用默認(rèn)命名約定將處理代碼映射到 Page 事件。如果將 AutoEventWireup 設(shè)置為 True,則可以在代碼文件中添加 Page_Load 方法,以處理頁面的 Load 事件,并且它將自動(dòng)注冊到 Page 的 Load 事件。隱式命名約定指示事件處理程序?qū)⒉捎?Page_XXX 格式,其中,XXX 可以是在 Page 類中定義的任何公共事件的名稱。如果將 AutoEventWireup 設(shè)置為 false,則必須將 Page 類事件與它的處理程序進(jìn)行顯式綁定。您可以在專門設(shè)計(jì)的類構(gòu)造函數(shù)中執(zhí)行此操作:public partial class Test : System.Web.UI.Page{public Test(){this.Load += new EventHandler(Page_Load);}...}
Web 服務(wù)器收到對給定 .aspx 資源的 HTTP 請求時(shí),它會(huì)將請求轉(zhuǎn)發(fā)給 ASP.NET 工作進(jìn)程。該進(jìn)程中駐留有 CLR,在其內(nèi)部創(chuàng)建了一個(gè)運(yùn)行時(shí)環(huán)境來處理 ASP.NET 請求。ASP.NET HTTP 運(yùn)行時(shí)環(huán)境的最終目標(biāo)是處理請求,即獲得將嵌入 HTTP 響應(yīng)中的標(biāo)記(HTML、WML、XHTML 以及應(yīng)用程序應(yīng)當(dāng)返回的任何其他標(biāo)記)。負(fù)責(zé)返回請求標(biāo)記的是稱為 HTTP 處理程序的特殊系統(tǒng)組件。
HTTP 處理程序是實(shí)現(xiàn)了 IHttpHandler 接口的類的實(shí)例。ASP.NET Framework 提供了少量預(yù)定義的 HTTP 處理程序,以處理特定情況,或者用作處理其他或更多特定請求的基類。System.Web.UI.Page 類是 ASP.NET 中的一個(gè)最復(fù)雜的內(nèi)置 HTTP 處理程序。
每個(gè) ASP.NET 請求都會(huì)映射到一個(gè) HTTP 處理程序。假設(shè)客戶端瀏覽器對一個(gè)名為 test.aspx 的頁面發(fā)出請求。請求將傳遞給 ASP.NET,并由 HTTP 運(yùn)行時(shí)進(jìn)行處理。運(yùn)行時(shí)通過頁處理程序工廠確定由 HTTP 處理程序類來處理該請求。如果在 AppDomain 中尚未提供正確的處理程序,則會(huì)動(dòng)態(tài)地創(chuàng)建該處理程序,并將其存儲(chǔ)在 Web 服務(wù)器計(jì)算機(jī)的 ASP.NET 臨時(shí)文件夾中。對于名為 test.aspx 的頁,將以類的形式創(chuàng)建一個(gè)名為 ASP.text_aspx 的 HTTP 處理程序。
針對給定請求的 HTTP 處理程序類的動(dòng)態(tài)創(chuàng)建過程對于每個(gè)頁面只發(fā)生一次,即在應(yīng)用程序運(yùn)行期間內(nèi)該頁面第一次被請求時(shí)進(jìn)行創(chuàng)建(盡管來說,使用批編譯時(shí),只要應(yīng)用程序內(nèi)有一個(gè)頁面收到了第一次請求即可生成處理程序)。如果應(yīng)用程序重新啟動(dòng)或 Web 服務(wù)器上的頁面源文件發(fā)生了修改,則動(dòng)態(tài)創(chuàng)建的程序集將無效并被替換。圖 2 顯示了從基礎(chǔ) Page 類直到處理用戶請求的動(dòng)態(tài)生成類等頁類的層次結(jié)構(gòu)。
圖 2 Page 類的層次結(jié)構(gòu) (單擊該圖像獲得較小視圖)
圖 2 Page 類的層次結(jié)構(gòu) (單擊該圖像獲得較大視圖)
ASP.NET 運(yùn)行時(shí)通過分析相應(yīng) .aspx 文件的源代碼來創(chuàng)建動(dòng)態(tài)頁類的 Visual Basic 或 C# 源代碼。每個(gè)包含 runat="server" 的標(biāo)記都將映射到一個(gè)服務(wù)器控件實(shí)例。任何其他文本則映射到文字控件,并按原樣一字不差地發(fā)出。Register 指令(如果有)幫助解析指向非標(biāo)準(zhǔn)控件的標(biāo)記。返回到客戶端瀏覽器的標(biāo)記是通過將頁面中每個(gè)服務(wù)器控件所發(fā)出的標(biāo)記組合到一起而形成的。請注意,每個(gè)頁通常都會(huì)發(fā)出標(biāo)記,而且通常是 HTML 標(biāo)記。但是,這不是必需的,并且 ASP.NET 頁可以輸出它需要的任何數(shù)據(jù)。
分析 HTML 客戶端代碼
圖 3 顯示了
圖 1 中的示例頁的 HTML 輸出。在該 HTML 中,服務(wù)器端 .aspx 頁中看不到任何有 Page 指令的跡象。而是逐字復(fù)制 !DOCTYPE 指令。
圖 1 中的第一個(gè) runat="server" 塊是 <form> 標(biāo)記。這意味著 Page 和 <form> 之間的任何文本都將按原樣發(fā)出。在服務(wù)器上動(dòng)態(tài)創(chuàng)建的頁類的源代碼中,此文本將轉(zhuǎn)換成 LiteralControl 類的一個(gè)實(shí)例。<form> 標(biāo)記類似以下方式發(fā)出:<form name="form1" method="post" action="Test.aspx" id="form1">
<form runat="server" …> 標(biāo)記將轉(zhuǎn)換為 HtmlForm 類的實(shí)例。該控件類沒有相應(yīng)的屬性可用于設(shè)置輸出標(biāo)記中的 action 屬性。action 屬性被硬編碼到當(dāng)前頁的 URL 中。此行為是基于 ASP.NET 平臺基礎(chǔ)的。請注意,ID 屬性同一個(gè)與 name 屬性值相同的值形成一對。
<asp:textbox> 標(biāo)記轉(zhuǎn)換為 HTML 中的 <input type="text"> 元素。在這里,將添加 name 屬性,以便與原來的 ID 屬性匹配。請注意,如果省略 ID 屬性,則可能會(huì)收到 Visual Studio 2005 發(fā)出的警告,但 ASP.NET 仍將成功編譯該頁。如果缺少 ID 屬性,則會(huì)生成隨機(jī)字符串,并將其綁定到 name 屬性。<asp:Button> 標(biāo)記轉(zhuǎn)換為 <input type="submit"> 按鈕。<asp:Label> 標(biāo)記將在客戶端瀏覽器上轉(zhuǎn)換為 HTML 的 <span> 標(biāo)記。
在大多數(shù)情況下(雖然不是全部),帶有 runat="server" 屬性的每個(gè)標(biāo)記都將生成一個(gè)對應(yīng)的 HTML 標(biāo)記塊。ID 字符串將保證兩個(gè)塊之間穩(wěn)定的匹配關(guān)系:一個(gè)在客戶端,另一個(gè)在服務(wù)器端。在
圖 3 中可以看到,兩個(gè)隱藏字段用于填充了 HTML 標(biāo)記:__VIEWSTATE 和 __EVENTVALIDATION。
視圖狀態(tài)字段
__VIEWSTATE 字段的內(nèi)容代表了頁面最后在服務(wù)器上處理時(shí)的狀態(tài)。盡管被發(fā)送到了客戶端,但視圖狀態(tài)并不包含客戶端應(yīng)當(dāng)使用的任何信息。存儲(chǔ)在視圖狀態(tài)中的信息只涉及服務(wù)器頁和它的一些子控件,并且由服務(wù)器獨(dú)占讀取、使用和修改。
通過采用此實(shí)現(xiàn)方式,視圖狀態(tài)可以不使用任何關(guān)鍵服務(wù)器資源,因此可以快速檢索和使用。另一方面,正是因?yàn)橐晥D狀態(tài)與頁面組合在一起,因此必然會(huì)使 HTTP 請求和響應(yīng)的大小增加幾千字節(jié)。注意,包含若干數(shù)據(jù)的實(shí)際頁面的視圖狀態(tài)大小很容易達(dá)到 20KB。而每次進(jìn)行上傳和下載時(shí)都要包括這個(gè)額外的負(fù)載量。視圖狀態(tài)是 ASP.NET 的最重要功能之一,因?yàn)樗梢曰谥T如 HTTP 這樣的無狀態(tài)協(xié)議實(shí)現(xiàn)狀態(tài)編程。雖然使用時(shí)不需要嚴(yán)格的條件,但視圖狀態(tài)很容易成為頁面的負(fù)擔(dān)。
通過重寫代碼文件類的兩個(gè)方法,可以將視圖狀態(tài)字段的內(nèi)容留在服務(wù)器上、存儲(chǔ)在數(shù)據(jù)庫、緩存或會(huì)話對象中。但請注意,將視圖狀態(tài)信息留在服務(wù)器上并非像一開始感覺的那樣是一個(gè)順理成章的解決辦法。實(shí)際上,ASP.NET 團(tuán)隊(duì)選擇基于頁的視圖狀態(tài)并不是偶然的。只要用戶沿著應(yīng)用程序中的鏈接從一頁導(dǎo)航到下一頁,基于服務(wù)器的視圖狀態(tài)確實(shí)是個(gè)好的選擇。請記住,ASP.NET 應(yīng)用程序的工作方式是在同一頁上進(jìn)行重復(fù)發(fā)布。但是,如果用戶單擊“后退”按鈕,情況會(huì)如何呢?為了安全起見,應(yīng)當(dāng)基于每個(gè)請求而不是基于每個(gè)頁來維護(hù)視圖狀態(tài)。而且,被跟蹤的請求鏈應(yīng)當(dāng)與用戶通過“后退”和“前進(jìn)”按鈕可以到達(dá)的請求相匹配。將視圖狀態(tài)存儲(chǔ)在客戶端可能不是一個(gè)完美的方案,但存儲(chǔ)在服務(wù)器上同樣也有其不足。對于您的應(yīng)用程序來說,更為可取的選擇取決于您對應(yīng)用程序的要求。
在 ASP.NET 2.0 中,__VIEWSTATE 隱藏字段包含兩種類型的信息:視圖狀態(tài)和控件狀態(tài)。開發(fā)人員可以完全禁用視圖狀態(tài),并以純粹的無狀態(tài)方式運(yùn)行其應(yīng)用程序。只要您使用內(nèi)置的控件和您自己編寫的控件,或者至少是您可以訪問其源代碼的控件,這就不是問題。如果使用了已啟用視圖狀態(tài)的自定義控件,情況會(huì)怎么樣呢?某些控件(通常是大量第三方和自定義的控件)需要跨回發(fā)持久保存私有信息。此信息不是公共的,并且不準(zhǔn)備對應(yīng)用程序級別公開,例如,下拉面板的折疊/展開狀態(tài)。此信息只能保存在視圖狀態(tài)。如果禁用視圖狀態(tài),則控件可能會(huì)意外地失去作用。
為了緩解這一問題,ASP.NET 2.0 引入了控件狀態(tài)的概念。每個(gè)服務(wù)器控件都可以將任何關(guān)鍵屬性打包到集合,并將它存儲(chǔ)到頁面的控件狀態(tài)中??丶顟B(tài)保存到 __VIEWSTATE 字段,但與傳統(tǒng)的視圖狀態(tài)不同,它不能被禁用,并且始終可用。開發(fā)人員通過 Page 類的一對新的可重寫方法 LoadControlState 和 SaveControlState 來管理控件狀態(tài)。但是,談到 ASP.NET 2.0 中的視圖狀態(tài),還應(yīng)當(dāng)注意到該版本采用了更為有效的新序列化算法,來使各個(gè)控件的狀態(tài)有效地存儲(chǔ)在隱藏字段中。因此,在大多數(shù)情況下,__VIEWSTATE 隱藏字段的總體大小是 ASP.NET 1.x 中的相應(yīng)字段大小的一半。
前面提到過,視圖狀態(tài)存儲(chǔ)在隱藏字段中,以便使它與特定的頁請求明確關(guān)聯(lián)。當(dāng)給定頁實(shí)例中的任何 HTML 元素回發(fā)時(shí),動(dòng)態(tài)生成的頁類開始在服務(wù)器上運(yùn)行,并使用存儲(chǔ)在視圖狀態(tài)中的數(shù)據(jù),來為頁面中的控件重新創(chuàng)建最后所能夠知道的正常狀態(tài)。如果視圖狀態(tài)在客戶端被篡改了,情況會(huì)怎么樣呢?這種情況可能發(fā)生嗎?默認(rèn)情況下,會(huì)使用 Base64 公式對視圖狀態(tài)編碼并進(jìn)行散列處理,所得到的散列值也與視圖狀態(tài)一起存儲(chǔ)。散列值是通過計(jì)算視圖狀態(tài)的內(nèi)容外加服務(wù)器密鑰得到的。一旦回發(fā)頁面,頁類中的代碼會(huì)將視圖狀態(tài)的內(nèi)容和散列值分離。下一步,它將基于檢索到的視圖狀態(tài)內(nèi)容和服務(wù)器密鑰重新計(jì)算散列值。如果兩個(gè)散列值不匹配,則引發(fā)安全異常(請參見圖 4)。
圖 4 不能在客戶端上更改頁面視圖 (單擊該圖像獲得較小視圖)
圖 4 不能在客戶端上更改頁面視圖 (單擊該圖像獲得較大視圖)
如果惡意用戶試圖發(fā)布已修改了視圖狀態(tài)的假請求,情況會(huì)怎么樣呢?惡意用戶需要知道服務(wù)器密鑰,才能為經(jīng)過修改的視圖狀態(tài)內(nèi)容生成可以在服務(wù)器上匹配的散列值。但是,服務(wù)器密鑰是僅由服務(wù)器信息組成的,并且不出現(xiàn)在視圖狀態(tài)字段中。附帶代碼中的 tweakviewstate.aspx 頁包含的腳本代碼可以修改視圖狀態(tài),并演示所發(fā)生的異常情況,如圖 4 所示。
盡管視圖狀態(tài)幾乎不能用于發(fā)動(dòng)攻擊,但它無法保證數(shù)據(jù)的機(jī)密性,除非使用加密。實(shí)際上,可以在客戶端對視圖狀態(tài)的內(nèi)容進(jìn)行解碼和檢查,但不可能成功修改該內(nèi)容以使經(jīng)過更改的頁狀態(tài)用于服務(wù)器環(huán)境。
__EVENTVALIDATION 隱藏字段是 ASP.NET 2.0 的新增安全措施。該功能可以阻止由潛在的惡意用戶從客戶端發(fā)送的未經(jīng)授權(quán)的請求。為了確保每個(gè)回發(fā)和回調(diào)事件來自于所期望的用戶界面元素,頁將在事件中添加額外的驗(yàn)證層。頁通常通過將請求的內(nèi)容與 __EVENTVALIDATION 字段中的信息進(jìn)行匹配,來驗(yàn)證未在客戶端添加額外的輸入域,并且該值是在服務(wù)器已知的列表中選擇的。頁將在生成期間創(chuàng)建事件驗(yàn)證字段,而這是最不可能獲取該信息的時(shí)刻。 像視圖狀態(tài)一樣,事件驗(yàn)證字段包含散列值以防止發(fā)生客戶端篡改。
控件使用 ClientScriptManager 對象的 RegisterEventForValidation 方法存儲(chǔ)自己的安全回發(fā)相關(guān)信息。每個(gè)控件還可能會(huì)注冊它自己的唯一 ID,但這種情況十分少見。列表控件還會(huì)存儲(chǔ)列表中的所有值。支持事件驗(yàn)證的服務(wù)器控件通常在其 IPostBackDataHandler 接口的實(shí)現(xiàn)中調(diào)用 ValidateEvent 方法。如果驗(yàn)證失敗,將引發(fā)安全異常。
可以基于每個(gè)頁啟用和禁用事件驗(yàn)證;每個(gè)控件類都通過 SupportsEventValidation 屬性來啟用事件驗(yàn)證。目前,還不能在特定控件實(shí)例上啟用或禁用事件驗(yàn)證。
事件驗(yàn)證是為了僅限輸入一組已知值而設(shè)置的防衛(wèi)屏障。它只是將安全防護(hù)提升到更高水平,但本身不會(huì)阻止腳本注入式的攻擊。
如果在啟用 AJAX 的應(yīng)用程序的環(huán)境中使用事件驗(yàn)證,則可能造成問題。在這類應(yīng)用程序中,某些客戶端工作可以臨時(shí)創(chuàng)建新的輸入元素,因而可能會(huì)由于出現(xiàn)未知元素而導(dǎo)致下一個(gè)回發(fā)失敗。最好的應(yīng)對方法是,一旦可能就在服務(wù)器上生成所有用戶界面,并使用級聯(lián)樣式表顯示屬性在客戶端上隱藏它。這樣,您要使用的任何用戶界面都將注冊到事件驗(yàn)證字段。如果編寫自定義控件,則應(yīng)當(dāng)用 SupportsEventValidation 屬性設(shè)置該控件,以啟用此功能。
回發(fā)機(jī)制
圖 1 中的 ASP.NET 頁會(huì)在用戶單擊按鈕時(shí)執(zhí)行回發(fā)操作。這是因?yàn)?<asp:Button> 標(biāo)記將轉(zhuǎn)換為 HTML 的提交 <input> 元素。單擊提交輸入字段時(shí),瀏覽器將觸發(fā) HTML 客戶端事件 onsubmit,然后根據(jù)所提交表單的內(nèi)容來準(zhǔn)備提交到服務(wù)器的新請求。所發(fā)送的 HTTP 請求包括一部分其他信息,用于計(jì)算按鈕 ID。
頁類將掃描 HTTP 請求的正文,以確定所發(fā)布的字段中是否有任何字段與 ASP.NET 頁中按鈕控件的 ID 匹配。如果找到匹配項(xiàng),將調(diào)用該按鈕控件以運(yùn)行與它的 Click 事件關(guān)聯(lián)的任何代碼。更準(zhǔn)確地說,頁類將檢查并確定所匹配的按鈕控件是否實(shí)現(xiàn)了 IPostBackEventHandler 接口。如果是,它將調(diào)用該接口的 RaisePostbackEvent 方法。對于按鈕控件,該方法將引發(fā)服務(wù)器端的 Click 事件。
到此為止,一切順利。但如果頁面中包含 LinkButton 控件,情況會(huì)怎么樣?
圖 5 顯示的 ASP.NET 頁的標(biāo)記與
圖 1 中的頁相同,只不過使用的是 LinkButton 而不是 Submit 按鈕。可以看到,標(biāo)記包括另外兩個(gè)隱藏字段(__EVENTTARGET 和 __EVENTARGUMENT)和一些 JavaScript 代碼。鏈接按鈕的 href 目標(biāo)綁定到 __doPostback 腳本函數(shù),這意味著一旦檢測到客戶端上有點(diǎn)擊該元素的操作,就將調(diào)用這個(gè)函數(shù)。通過生成 LinkButton 控件的代碼,__doPostback 函數(shù)在頁中發(fā)出。它用合適的信息填充 __EVENTTARGET 和 __EVENTARGUMENT 字段,然后通過腳本觸發(fā)回發(fā)。這種情況下,HTTP 回發(fā)請求的正文只包含頁中的輸入域,并且所回發(fā)的數(shù)據(jù)不引用 Submit 按鈕。
ASP.NET 如何識別負(fù)責(zé)處理回發(fā)的控件?如果在請求正文中引用的所有控件都未實(shí)現(xiàn) IPostBackEventHandler 接口,則頁類將查找 __EVENTTARGET 隱藏字段(如果有)。字段的內(nèi)容假定為導(dǎo)致回發(fā)的控件的 ID。如果此控件實(shí)現(xiàn)了 IPostBackEventHandler 接口,則調(diào)用 RaisePostbackEvent 方法。對于 LinkButton 控件,這將導(dǎo)致調(diào)用 Click 服務(wù)器事件。
分析類代碼
.aspx 標(biāo)記定義 ASP.NET 頁的布局,并確定成員控件的大小、樣式和位置。但除了可能包含某些客戶端腳本代碼和任何 Visual Basic 或 C# 內(nèi)嵌代碼以外,它不包含邏輯。初始化代碼、事件處理程序和任何幫助器例程通常在單獨(dú)的附帶文件中提供,這些文件叫做代碼隱藏文件:public partial class Test : System.Web.UI.Page{protected void Page_Load(object sender, EventArgs e){...}protected void Button1_Click(object sender, EventArgs e){...}}
代碼文件中的類直接或間接從 System.Web.UI.Page 繼承。代碼文件和標(biāo)記都表示必要的信息,但二者又截然不同。若要完全表示 ASP.NET 頁,它們必須組合在一起以形成頁類,在其中將代碼文件的邏輯和標(biāo)記文件的布局?jǐn)?shù)據(jù)合并在一起。代碼文件類已經(jīng)是頁類,但它缺少兩部分關(guān)鍵的信息:用于填充用戶界面的子服務(wù)器控件的列表,以及用于標(biāo)識各種服務(wù)器控件的類成員的聲明。
在 ASP.NET 1.x 中,頁面創(chuàng)建人員每次將控件拖到 Web 表單上時(shí),Visual Studio .NET 2003 都會(huì)在代碼文件中自動(dòng)添加新的行,以創(chuàng)建用于處理剛才拖動(dòng)的服務(wù)器控件的類成員。這是使所有內(nèi)容保持同步的非常好的步驟,但開發(fā)人員經(jīng)常會(huì)碰到由于缺少類成員或存在無效的類成員而導(dǎo)致的編譯錯(cuò)誤。
在 ASP.NET 2.0 中,這個(gè)問題已經(jīng)得到了妥當(dāng)?shù)慕鉀Q??梢暂斎氩糠诸?,即通過源代碼級、程序集受限且非面向?qū)ο蟮姆绞絹頂U(kuò)展類的行為。在 .NET Framework 2.0 中,類定義可以跨越兩個(gè)或更多個(gè)文件。每個(gè)文件都包含最終的類定義的一部分內(nèi)容,編譯器會(huì)考慮到合并各個(gè)部分定義,以形成單個(gè)統(tǒng)一的類。所有定義部分都必須有相同的簽名,并且最終的類定義必須保證語法正確。
下一步,將動(dòng)態(tài)生成第二個(gè)部分類,以列出所有控件成員。兩個(gè)部分類將在編譯時(shí)合并。系統(tǒng)將分析 .aspx 標(biāo)記文件,以創(chuàng)建臨時(shí) ASP.test_aspx 類,此類繼承自其最終版本的組合代碼文件。 如果 ASP.NET 頁未綁定到代碼文件,但包含它的內(nèi)嵌代碼,則動(dòng)態(tài)頁類將繼承 System.Web.UI.Page,并在它的正文中包括所有內(nèi)嵌代碼。
有關(guān)動(dòng)態(tài)頁的編譯機(jī)制還有很多內(nèi)容有待了解,這些內(nèi)容會(huì)在以后的專欄中介紹,本文權(quán)作拋磚引玉。