Contents
IntroductionCode ModelCompilationFull Runtime Compilation (The Code Directory)Page LifecycleExtensibilityAdvanced Caching TechniquesPerformanceConclusionIntroduction對于專業(yè)的開發(fā)人員,最大的問題在于了解asp.net的內(nèi)部結(jié)構(gòu)有了何種變化。本文,將呈現(xiàn)這種變化以及幾個關(guān)鍵的問題。這篇文章對那些對性能比較苛求的開發(fā)人員以及優(yōu)秀的架構(gòu)師都將很有幫助。為了看懂文中的用例,您需要對asp.net以及vb/c#有所了解。若想有更深入的研究,可以參考附錄資料。
Code Modelasp.net 2.0內(nèi)部工作機制最明顯的變化依賴于一張asp.net頁面試如何產(chǎn)生的。這部分,我們將檢查code-behind模型的變化以及這種變化如何影響asp.net的發(fā)展。
Coding Models in ASP.NET 1.x
傳統(tǒng)的asp編碼方式,對于融合了大量(html) and (code)的頁面很難閱讀。為了解決這個問題,asp.net采用了code-behind模型。同時我們看到,這種模型使得開發(fā)團隊可以同時雙線作戰(zhàn),開發(fā)效率大大提高。
Figure 1:asp.net 1.x coding model
code-behind技術(shù)的最大困難在于如何讓code-behind文件與aspx頁面同步。從編程意義上講,aspx頁面繼承自一個code-behind文件。盡管如何,這兩個文件實際上被更加復雜的關(guān)系所關(guān)聯(lián)。
想對asp.net 1.x的code-behind技術(shù)有更多地了解,參閱msdn:Web Forms Code Model.
Inheritance Complexity
典型的asp.net開發(fā)是開發(fā)人員利用microsoft visual studio.net來拖放控件到aspx頁面中。visual studio會在后臺自動生成控件的相應代碼。每當有控件被加載近來,必須產(chǎn)生相應的后臺代碼。換句話說,盡管aspx葉面繼承自code-behind文件,實際上aspx頁面主導了code-behind文件的設計。
Compilation Complexity
第二個同步問題是文件被編譯的方式問題。所有的后臺文件,包括相關(guān)的類,被編譯成一個集合放置在web應用程序的/bin目錄下面,該編譯發(fā)生在應用程序被配置之前。另一方面,aspx頁面在第一次被請求時被runtime編譯成一個臨時集合。
當apx頁面發(fā)生變化,而后臺相應的code-behind沒有改變時,就會出現(xiàn)問題。開發(fā)人員可能對aspx頁緬中的某些屬性進行了修改,但是code-behind文件卻沒有被重新修改,而且應用程序也不會被重新配置。這時,應用程序就會因aspx頁面和code-behind失去鏈接而報錯。
Coding Models in ASP.NET 2.0
asp.net 2.0同時支持code-inline and code-behind.
asp.net 2.0通過修改code-behind文件的nature來處理inheritance和compliation這兩個問題。在ap.net 2.0中,code-behind文件不再是System.Web.UI.Page 的一個implementation ,而是一個新的partial class.該partial class僅包含user-define代碼,完全屏蔽掉visual studio自動生成的代碼。當一個鏈接了code-behind的aspx頁面被請求時。asp.net 2.0 runtime實際上將aspx頁面和該partial class編譯成一個單獨的類,而不是兩個類。
Figure 2. The code-behind model in asp.net 2.0
partial class采用一個新的關(guān)鍵字(vb中expands,c#中partial)來指示代碼編譯時要跟其它類文件和并。類似的,aspx頁面采用compliewith指示他與code-behind文件的連接。
Listing 1. A code-behind file in asp.net 1.x
namespace WebApplication1
...{
public class WebForm1 : System.Web.UI.Page
...{
protected System.Web.UI.WebControls.Label Label1;
private void Page_Load(object sender,
System.EventArgs e) ...{ }
Web Form Designer generated code#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
...{
InitializeComponent();
base.OnInit(e);
}
private void InitializeComponent()
...{
this.Load += new System.EventHandler(this.Page_Load);
}
#endregion
}
}
自動生成的代碼不僅定義了label,而且聲明了一個新的事件(page load),并顯式的標示為方法:page_load().
作為比較,同樣的asp,net頁面在2.0中簡潔多了。
Listing 2. A code-behind file in asp.net 2.0
namespace ASP ...{
public partial class MyPage : System.Web.UI.Page
...{
}
}
開發(fā)人員可以自動訪問label并可以為其添加事件,一個page_load事件可以被用來初始化label.
Listing 3. Adding events in the new code-behind file
namespace ASP {
public partial class Webform1_aspx : System.Web.UI.Page
{
void Page_Load(object sender, EventArgs e)
{
Label1.Text = "Hello ASP.NET 2.0";
}
}
}
事件的語法可以通過visual studio 2005被生成?,F(xiàn)在的code-behind文件更加簡潔并且跟其它自動生成的代碼沒有關(guān)系。asp.net自動將code-behind中的事件根apx中的控件聯(lián)系起來。換句話說,ASP.NET 運行庫現(xiàn)在自動執(zhí)行代碼生成,而過去這由 Visual Studio 完成。
兩步編譯方法的好處在于,它允許您將代碼隱藏類編譯成二進制,然后支持部署 .ASPX 文件,該文件作為以后修改的 html 源。您不能部署已編譯的代碼隱藏,以及稍后修改 .ASPX html 源,因為 .ASPX 源是編譯代碼隱藏所用的局部類型。
繼承的復雜性
這種新代碼隱藏模型大大降低了繼承的復雜性。因為 ASPX 頁不直接繼承代碼隱藏文件,所以代碼隱藏文件不再需要定義和支持 ASPX 頁上定義的所有控件。同樣,代碼隱藏文件可以自動訪問 ASPX 頁上的任何控件,而不需要 ASP.NET 1.x 中所需的聲明代碼。所有這些都是可能的,因為 ASP.NET 運行庫自動將所需要的聲明和事件連接代碼插入到最終的已編譯文件中。因為運行時承擔這樣的責任,所以代碼開發(fā)人員和 Web 開發(fā)人員都不需要為此擔心。
在設計期間,鏈接由 Visual Studio 維護。Visual Studio 環(huán)境利用 ASP.NET 運行庫編譯塊來確保代碼開發(fā)人員和 Web 開發(fā)人員可以同步工作。
編譯的復雜性
因為這種新的代碼隱藏文件與 ASPX 頁連接并在運行時編譯成一個完整類,所以不會出現(xiàn)編譯的復雜性。也就是說,代碼隱藏文件自動與 ASPX 頁同步。即使是使用這種新編譯模型,仍可能有不同步的代碼,但可以很快找到這種問題,因為產(chǎn)生的異常更為清楚。
返回頁首編譯
由于 ASP.NET 1.x 中引入了頁模型,ASP.NET Web 頁的編譯過程總是分成兩個階段。首先,代碼隱藏文件和其他支持類編譯到一個程序集中,然后在運行時編譯單獨的 ASPX 文件。雖然這種模型具有許多優(yōu)點,但它也有一些缺點。ASP.NET 2.0 提供基本模型的幾種替代模型,并根據(jù)您的特定需要提供更為廣泛的編譯選項。
ASP.NET 1.x中的編譯
ASP.NET1.x 中的主要編譯模型會導致一個應用程序程序集(包含所有的已編譯代碼隱藏文件和其他的源代碼)和一個為每個被請求的 ASPX 頁而創(chuàng)建的臨時程序集。在有些情況下,編譯器優(yōu)化(例如批處理)會引起將臨時 ASPX 頁編譯到同一個程序集中。在任一種情況下,每個 ASPX 頁都編譯到一個臨時程序集中,這樣它可以加載到 ASP.NET 運行庫。
圖 3. ASP.NET 1.x中的編譯
雖然這種模型有優(yōu)點,但它也有兩個主要缺點。首先,ASPX 頁必須要以人們可以閱讀的形式部署到 Web 站點。如果開發(fā)人員使用代碼內(nèi)聯(lián) 模型,這意味著,一些(或所有)的業(yè)務邏輯也可以部署在生產(chǎn)服務器上。雖然沒有將 IIS 和 ASP.NET 配置為公開原始的 ASPX 頁,但聰明的攻擊者仍可以通過任何攻擊(該攻擊打開到 Web 服務器的通道)來訪問這些文件。其次,第一次有人請求某 Web 頁時,響應速度將比正常速度慢一些,原因在于 ASP.NET 運行庫必須編譯 ASPX 頁。
這整個過程中,開發(fā)人員擁有的唯一控制權(quán)是決定是否批編譯 ASPX 頁。在 ASP.NET 1.x 中,可以通過修改 <compilation> 標記在 web.config 文件中配置批編譯。
清單 4. 配置批編譯
<compilation
batch="true|false"
batchTimeout="number of seconds"
maxBatchSize="maximum number of pages per batched compilation"
maxBatchGeneratedFileSize="maximum combined size (in KB) of the
generated source file per batched compilation"
</compilation>
批編譯用啟動時間換取減少第一次請求 Web 頁的加載時間。批編譯的另一個好處是所有的 ASPX 文件都編譯到一個臨時程序集中,而不是一頁一個臨時程序集。
ASP.NET 2.0 中的編譯
ASP.NET 2.0 為 Web 應用程序提供三種不同的編譯模型:
•
普通 (ASP.NET 1.x) — 在一個普通的 ASP.NET Web 應用程序中,代碼隱藏文件被編譯到一個程序集并存儲在 /bin 目錄中。根據(jù)要求編譯 Web 頁 (ASPX)。該模型對大多數(shù) Web 站點都運行得不錯。但是,編譯過程使得第一次請求 ASP.NET 頁時的速度比隨后的請求速度緩慢。ASP.NET 2.0 繼續(xù)支持這種編譯模型。
•
部署預編譯 — ASP.NET 2.0 的一種新功能,允許在部署前對項目進行完整編譯。在完整編譯中,所有的代碼隱藏文件、ASPX 頁面、HTML、圖形資源以及其他的后端代碼都被編譯到一個或多個可執(zhí)行程序集中,這取決于應用程序的大小和編譯設置。這些程序集包含所有的已編譯 Web 站點代碼,而資源文件和配置文件被復制,沒有做修改。這種編譯方法以犧牲修改部署后 Web 站點的能力為代價,提供了最好的性能和安全性。如果您使用高可見或高安全的 Web 站點,這種選項是最終部署的最好選擇。但是,如果您正在構(gòu)建一個運行局部 Intranet 的小站點,并且更改站點非常頻繁,那么完整預編譯可能有點過分。
•
ASP.NET 2.0 編譯模型也允許預編譯應用程序的所有代碼隱藏文件并且仍可以更新代碼。可以將代碼隱藏文件和原始的 .ASPX 文件(都是局部類)編譯到一個預編譯類中(頁面的基類)。如果選擇在運行時編輯 .ASPX 文件,只需重新編譯頁面即可。
•
完整的運行時編譯 — 在部署預編譯的另一個極端,ASP.NET 2.0 提供一種在運行時編譯整個應用程序的新機制。也就是說,可以將未編譯的代碼隱藏文件和其他相關(guān)的代碼放在 \app_code 目錄中,并讓 ASP.NET 2.0 創(chuàng)建并維護對程序集的引用,這些引用將在運行時根據(jù)這些文件生成。這種選項以在服務器上存儲未編譯代碼為代價,在更改 Web 站點內(nèi)容方面提供了最大的靈活性。
選擇最佳的編譯選項要由具體的情況和需要決定,但編譯模型要有靈活性。即使選擇使用 \app_code 目錄來存儲代碼隱藏文件,您仍可以使用完整的編譯方法來部署應用程序。
批編譯
在 ASP.NET 2.0 中,可以利用單個 URL 請求來批編譯任何應用程序。如同 ASP.NET 1.x 一樣,批編譯消除了第一次頁面請求的延時,但造成了更長的啟動周期。另外,批編譯還要求在部署前編譯代碼隱藏文件。
Web.config 批編譯設置在 ASP.NET 2.0 中仍起作用。批編譯的優(yōu)點是,第一個用戶可以立即使用頁面,而且在批編譯期間可以檢測到 ASPX 頁中的任何錯誤。但是,批編譯的確增加了應用程序啟動的延時,并且必須要內(nèi)置在 Web.config 文件中。應當注意,如果某個文件出現(xiàn)了問題,則該批將不會接收它。
部署預編譯
部署預編譯允許創(chuàng)建一個或多個程序集,這些程序集是 Web 站點的可執(zhí)行版本。所獲得的程序集包含 Web 站點的已編譯代碼。HTML 頁面、資源、配置文件和 ASPX 頁面被單獨復制。
部署預編譯要求使用一個稱為 ASPnet_compiler.exe 的命令行實用程序。該實用程序創(chuàng)建一個目標部署目錄,該目錄包含一個含有程序集的 /bin 目錄和各種 ASPX 頁的 stub 文件。該實用程序還用來在原地進行預編譯,類似于調(diào)用“魔術(shù)頁”的行為。stub 文件共享 ASPX 頁的名稱,但是包含調(diào)用已編譯程序集的簡單代碼。換句話說,ASPX 頁只是空殼而不是填滿的功能頁。
通過為部署預編譯 Web 站點,您可以獲得增強的安全性,因為只有進行反編譯程序集才能訪問您的代碼。為了增強保護,可以弄亂所得到的程序集,使您的 Web 應用程序更加安全。部署預編譯的主要缺點是,在部署前必須執(zhí)行這些步驟,并且在部署后不能更改 Web 站點。如果想進行更改,就必須重新編譯該 Web 站點并重新部署它。
對于大多數(shù)主要的 Web 應用程序,部署預編譯選項將是部署的首選機制,因為它減少了在 Web 服務器上部署的原始代碼數(shù)量,并提供了最佳的安全性。這個增加的進程可以內(nèi)置于通常的開發(fā)/測試/部署周期中,而工作效率并不會有多大損失。
返回頁首完整的運行時編譯(\app_code 目錄)
在目前描述的所有三種編譯方法中,在部署前必須要編譯所有的代碼文件(代碼隱藏類和支持類)。在 ASP.NET 2.0 中,您有代碼目錄。
\app_code 目錄是一個保存未編譯類的特殊目錄。在運行時,ASP.NET 運行庫將該目錄中的內(nèi)容編譯到一個程序集中,應用程序中的 ASPX 頁自動引用該程序集。換句話說,通過使用代碼目錄,可以避免為支持代碼創(chuàng)建和引用單獨的程序集。代碼目錄的優(yōu)點在于,不用完整編譯項目就可以部署,因此減少了不匹配的可能。缺點是,有可能在服務器上公開未編譯的代碼。
該選項最適合于不需要大量支持代碼(以代碼隱藏文件的形式或外部對象的形式)的 ASP.NET 應用程序。對于一個簡單的應用程序,與更為健壯的編譯方法相比,快速部署和測試系統(tǒng)的功能提供了幾個優(yōu)點。
返回頁首頁面生命周期
ASP.NET 2.0 在 ASP.NET 頁的生命周期中提供了兩個主要變化。第一,ASP.NET 2.0 公開新的事件來支持新功能,包括母版頁、個性化以及集成的移動設備支持。第二,ASP.NET 2.0 為 Web 窗體引入跨頁投遞。
新事件
與 ASP.NET 1.x 相比,ASP.NET 2.0 提供了一種粒度更細的頁面生命周期方法棧。這些添加的方法為 Web 開發(fā)人員提供了更高級的控制。可以通過 ASP.NET 頁上的 Page 對象來訪問這些事件。
表 1 顯示綜合方法列表。Method 列顯示實際事件方法名稱,Active 列表示該事件是否總處于活動狀態(tài)或者僅在 PostBack 操作期間是活動的。例如,新方法 TestDeviceFilter 可用來確定哪種設備篩選器就緒,并用這些信息來決定如何顯示頁面。另一方面,新方法 LoadControlState 只有在 PostBack 期間才激發(fā)??梢灾貙懺摲椒ǎńY(jié)合 SaveControlState)來在 PostBack 期間創(chuàng)建保存和還原控件狀態(tài)的備選序列化方案。
表 1. 頁面生命周期方法
方法 活動
Constructor
Always
Construct
Always
TestDeviceFilter
Always
AddParsedSubObject
Always
DeterminePostBackMode
Always
OnPreInit
Always
LoadPersonalizationData
Always
InitializeThemes
Always
OnInit
Always
ApplyControlSkin
Always
ApplyPersonalization
Always
OnInitComplete
Always
LoadPageStateFromPersistenceMedium
PostBack
LoadControlState
PostBack
LoadViewState
PostBack
ProcessPostData1
PostBack
OnPreLoad
Always
OnLoad
Always
ProcessPostData2
PostBack
RaiseChangedEvents
PostBack
RaisePostBackEvent
PostBack
OnLoadComplete
Always
OnPreRender
Always
OnPreRenderComplete
Always
SavePersonalizationData
Always
SaveControlState
Always
SaveViewState
Always
SavePageStateToPersistenceMedium
Always
Render
Always
OnUnload
Always
查看頁面生命周期的底層細節(jié),我們可以看到 ASP.NET 2.0 中提供的許多功能(例如主題和個性化)將在什么地方容易實現(xiàn)。例如,主題在 IntializeThemes 事件中處理,而個性化數(shù)據(jù)將在 LoadPersonalizationData 中加載并稍后用于 ApplyPersonalization 方法。請注意,就哪一個 UI 元素將決定 Web 應用程序的最終外觀和感覺而言,方法的順序非常重要。
跨頁投遞
頁面生命周期中的另一個主要變化涉及回發(fā)事件和 Web 窗體。在 ASP.NET 1.x 中,Web 窗體自動回發(fā)給它們的宿主頁。即,當用戶提交一個窗體時,窗體數(shù)據(jù)總是提交回包含該原始窗體的頁面。這種設計決策考慮了存儲控件狀態(tài)的容易性,但限制了開發(fā)人員執(zhí)行更復雜操作的能力。
在 ASP.NET 2.0 中,Web 窗體控件有一個新特性,讓開發(fā)人員決定在進行提交操作時將窗體數(shù)據(jù)發(fā)送到何處。在大多數(shù)情況下,要求 PostBack 機制,因此它仍是默認的機制。但是,如果開發(fā)人員想將數(shù)據(jù)投遞到不同的窗體,現(xiàn)在是可能的。
圖 4. PostBack 與跨頁投遞
例如,可以創(chuàng)建一個包含幾個不同窗體的多頁向?qū)АC總€窗體依次提交給下個頁面,直到用戶到達一個進行最后驗證的摘要頁。通過 PreviousPage 對象可以在當前的上下文中訪問來自最后一頁的數(shù)據(jù)。PreviousPage 對象存儲來自前頁的已驗證數(shù)據(jù),以在當前頁中使用。由于這個對象,跨頁投遞不會犧牲控件的持久性存儲。如果用戶需要依次倒退回一個窗體,那么就可以立即訪問該頁的數(shù)據(jù),而用戶不必重新輸入所有的數(shù)據(jù)。
返回頁首可擴展性
ASP.NET 最初被設計為一種開放式框架。即,可以擴展、修改或替換構(gòu)成 ASP.NET 的許多模塊和組件,以適應特定的要求。在 ASP.NET 2.0 中,這種框架的可擴展特性由新的 HTTPHandlers 和 HTTPModules 清楚地闡明,二者現(xiàn)在是該框架的一個標準部分。
請求管道
在 ASP.NET 中,請求從 Web 服務器通過 Internet 服務器應用程序編程接口 (ISAPI) 篩選器傳遞,并繼續(xù)傳遞給實際的 ASP.NET 運行庫。
圖 5. 請求管道
當 IIS 接收一個請求時,根據(jù) IIS 的設置將擴展映射到一個 ISAPI 篩選器。將 .ASPX、.asmx、.asd 和其他擴展映射到 ASPnet_isapi.dll,該 ASPnet_isapi.dll 只是一種啟動 ASP.NET 運行庫的 ISAPI 篩選器。一旦請求到達 ASP.NET 運行庫,它在 HTTPApplication 對象處啟動,該對象擔當 ASP.NET Web 應用程序的宿主。HTTPApplication 對象:
1.
讀取機器級和應用程序級的配置文件。
2.
通過一個或多個 HTTPModule 實例傳遞請求。每個 HTTPModule 提供一種服務,例如會話維護、身份驗證,或配置文件維護。這些模塊將請求傳遞回 HTTPApplication。
3.
根據(jù)謂詞和路徑將請求傳遞給 HTTPHandler。謂詞指請求中使用的 HTTP 謂詞(GET、POST、FTP,等等),而路徑指應用程序中的 URL。根據(jù)處理程序的配置方式,該請求可能作為一個 ASP.NET 頁(System.Web.UI.Page 為 IHTTPHandler 的一種實現(xiàn))加以處理,或者該請求可能觸發(fā)另一個操作,例如批編譯所有的 Web 頁(precomiplation.asd 觸發(fā) PrecompHandler)。
在 ASP.NET 2.0 中,該模型沒有變化,但是,添加了幾種新模塊和處理程序以提供其他的服務。與 ASP.NET 1.x 一樣,您可以擴展、替換或重新配置任何模塊或處理程序類,以提供自己的自定義功能。
新模塊
顯然,已經(jīng)添加了新 HTTPModules 以支持 ASP.NET 2.0 中提供的新服務。具體地說,具有默認模塊設置的 ASP.NET 應用程序?qū)橐韵履康亩砑拥男履K:
•
SessionID — 會話識別機制已經(jīng)從 ASP.NET 1.x 會話模塊分離,以提供對 cookie、URL 重寫以及會話 ID 生成的其他形式的更多控制。
•
角色管理 — 添加的這種新模塊用于提供基于角色的服務,以支持新用戶識別機制。該模塊有助于將 ASP.NET 應用程序和內(nèi)置在 .NET 框架中基于角色的安全性結(jié)合起來。
•
匿名識別 — 新的個性化功能支持匿名用戶。該模塊有助于跟蹤匿名用戶可以訪問的功能,以及跟蹤在請求之間維護這些功能的方式。
•
配置文件 — 該配置文件模塊連接新的配置文件服務,幫助為用戶提供特定的持久數(shù)據(jù)存儲。
除了這些新模塊,一些舊模塊的行為也進行了更改:例如,輸出緩存模塊現(xiàn)在支持新的緩存技術(shù),這將在本白皮書的稍后部分說明。
新處理程序
除了這些新模塊,ASP.NET 2.0 還引進了新的處理程序來支持應用程序配置工具以及其他的新功能,例如,批編譯請求。這些新處理程序中最重要的一點是包括處理 Web 站點管理請求的“.axd”系列。這些處理程序啟動內(nèi)部的管理工具,這些管理工具允許開發(fā)人員配置 ASP.NET 用戶和其他設置。管理處理程序包括:
•
Web 管理 — WebAdminHandler 是管理 Web 站點的主頁。該處理程序為管理 ASP.NET 2.0 Web 應用程序提供了起點。
•
跟蹤 — ASP.NET 1.xTraceHandler 已進行了改進,它是 ASP.NET 1.x 中唯一的“axd”處理程序。
•
Web 資源 — 由于有了新的管理工具和 WebResourcesHandler,現(xiàn)在可以在部署后配置 Web 資源。
•
緩存圖像 — CachedImageServiceHandler 支持緩存圖形組件。
•
計數(shù)器 — SiteCountersHandler 使用頁面計數(shù)器模塊為 ASP.NET 2.0 應用程序提供訪問統(tǒng)計信息。
•
預編譯 — 正如先前提到的一樣,可以使用 PrecompHandler 在一個 ASP.NET 應用程序中批編譯所有的 ASPX 頁。
•
Web 部件導出 — WebPartExportHandler 支持存儲和傳輸 Web 部件布局。Web 部件是一種新機制,用于個性化門戶樣式的 Web 應用程序的外觀和內(nèi)容。
與以前一樣,HTTPForbiddenHandler 連接到不應該返回的任何文件類型。在 ASP.NET 2.0 中,禁止的文件類型列表已擴展為包括母版頁、外觀文件和其他的開發(fā)人員新組件。
返回頁首高級緩存技術(shù)
提高 Web 應用程序性能的一種方法是在內(nèi)存中緩存靜態(tài)內(nèi)容。緩存內(nèi)容的返回速度始終比新提供內(nèi)容的速度快。但是,換來的是緩存的內(nèi)容可能會過時。ASP.NET 1.x 支持幾種緩存,包括:
•
頁面級別 — 每頁可以作為一個整體塊或根據(jù)用來訪問該頁的參數(shù)進行緩存。緩存頁在一段固定時間之后過期。
•
頁面片段 — 如果使用用戶控件(.ascx 文件)來構(gòu)建頁面,那么可以將用戶控件獨立于其他頁面內(nèi)容進行緩存。
•
編程緩存 — 由于有了緩存 API,開發(fā)人員還可以緩存對象。緩存 API 具有獨特的優(yōu)勢,提供一種在應該刷新緩存時創(chuàng)建不同類型依賴項的方式。
在 ASP.NET 2.0 中,頁面級別的緩存機制已經(jīng)擴展到了支持數(shù)據(jù)庫依賴項。利用數(shù)據(jù)庫緩存依賴項,緩存頁可綁定到 SQL Server 數(shù)據(jù)庫的一個特定表。當該表更改時,緩存自動過期。另外,開發(fā)人員現(xiàn)在可以使用緩存后替換來用刷新內(nèi)容替換部分緩存內(nèi)容。緩存后替換允許應用程序使用頁面級別緩存,即使部分頁面應當動態(tài)生成。
數(shù)據(jù)庫緩存無效
對于大多數(shù)數(shù)據(jù)驅(qū)動的 Web 站點,緩存是一個麻煩的問題,特別是在要求緩存以及需要更新數(shù)據(jù)時。在 ASP.NET 1.x 中,可通過輸入?yún)?shù)(查詢字符串或 POST 參數(shù))將頁面緩存一定長度的時間和組織頁面:
清單 5. ASP.NET 1.x輸出緩存指令
<%@ outputcache duration="3600" varybyparam="ProdID" %>
例如,清單 5 中的代碼根據(jù)變量 ProdID 將頁面在內(nèi)存中緩存一個小時。上例中出現(xiàn)的問題是,如果在別處更新了相關(guān)的業(yè)務數(shù)據(jù),應當如何處理。例如,考慮一個按產(chǎn)品 ID 緩存的產(chǎn)品目錄頁。如果在一個管理站點更新有關(guān)該產(chǎn)品的信息(例如,供應的數(shù)量或價格),就會將錯誤的數(shù)據(jù)緩存并顯示給客戶。對于以前版本的 ASP.NET,該問題的解決方案要求利用 Response.RemoveOutputCacheItem 手工地將該頁從緩存中刪除,或者等到 duration 時間到期并允許系統(tǒng)自動更新該頁。
ASP.NET 2.0 通過支持數(shù)據(jù)庫緩存依賴項解決了這個問題。當使用 SQL Server 7 和 2000 時可用表級通知,而 Microsoft SQL Server 將以更多的粒度級別來提供通知。例如,下列代碼緩存一個產(chǎn)品頁最多 1 個小時,但添加了第二個數(shù)據(jù)庫表依賴項。
清單 6. ASP.NET 2.0 數(shù)據(jù)庫緩存示例
<%@ outputcache duration="3600" varybyparam="ProdID" sqldependency="MyDatabase:Products" %>
利用 sqldependency 新屬性,如果對 Products 表有任何更改,那么緩存頁將到期。sqldependency 屬性必須引用一個 datasource,它在 web.config 文件中進行配置。datasource 識別數(shù)據(jù)庫連接和必需的參數(shù),以使依賴項通知工作。
自定義緩存依賴項
ASP.NET 2.0 配備一個 CacheDependency 實現(xiàn),即 SQLCacheDependency 類,它支持 Microsoft SQL Server。實現(xiàn)一個新的緩存依賴項將是一個復雜的過程,但是由于有了 ASP.NET 2.0 的可擴展特性,這種實現(xiàn)是可能的。換句話說,可以創(chuàng)建自己的 CacheDependency 類來為數(shù)據(jù)庫系統(tǒng)(例如 Oracle 或 Sybase)提供類似的功能。
緩存后替代
一些頁面元素保持動態(tài)而大多數(shù)頁將從緩存中受益,對于這種情況,ASP.NET 2.0 提供一種稱為緩存后替代的功能。緩存后替代用來通知 ASP.NET 運行庫緩存頁呈現(xiàn)給用戶之前應該重新評估該頁上的某個特定元素。
使用這種功能有兩種方法:
•
調(diào)用新方法 Response.writeSubstitution,將一個引用傳遞給替代回調(diào)函數(shù)。
•
給 Web 頁添加一個<asp:substitution> 控件并將 methodname 屬性設置為回調(diào)函數(shù)的名稱。
•
對于任一種選項,應當給頁面添加一個 @OutputCache 指令,指定依賴項的持續(xù)時間和位置。
實現(xiàn)緩存后替代
為了利用這種功能,可以創(chuàng)建能夠識別緩存后替代的控件。AdRotator 控件就是一個這樣的控件示例。清單 7 舉例說明了這樣一個頁:
•
檢索 Pubs 數(shù)據(jù)庫的作者表數(shù)據(jù)。
•
將數(shù)據(jù)綁定到 GridView 控件。
•
從一個 AdRotator 顯示地址。
•
在一個標簽控件中顯示創(chuàng)建該頁的時間。
該示例中還添加了一個 <asp:substitution>控件(清單中的粗體行)。該控件的 methodname 屬性設置為 uncachedUpdate(一種返回字符串輸出的方法 — 在這種情況下為當前時間)。無論緩存什么內(nèi)容,替代控件都將返回正確的時間。
清單 7. PostCache.ASPX 源代碼
<%@ Page language="C#" Codefile="PostCache.ASPX.cs"
AutoEventWireup="true" Inherits="WebApplication1.PostCache" %>
<%@ outputcache duration="30" varybyparam="none" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
<HEAD>
<title>WebForm1</title>
</HEAD>
<body MS_POSITIONING="GridLayout">
<form id="Form1" method="post" runat="server">
<DIV style="DISPLAY: inline;
Z-INDEX: 101; LEFT: 32px; WIDTH: 160px;
POSITION: absolute; TOP: 24px; HEIGHT: 8px"
align="right" ms_positioning="FlowLayout">
this page was created at:
</DIV>
<ASP:Label id="CreatedTime"
style="Z-INDEX: 102; LEFT: 200px; POSITION: absolute;
TOP: 24px" runat="server" Width="120px" Height="16px">
</ASP:Label>
<ASP:substitution id="UpdatedTime" methodname="uncachedUpdate"
style="Z-INDEX: 103; LEFT: 200px; POSITION: absolute;
TOP: 48px" runat="server" Width="112px" Height="11px">
</ASP:substitution>
<DIV style="DISPLAY: inline; Z-INDEX: 104; LEFT: 32px;
WIDTH: 160px; POSITION: absolute; TOP: 48px;
HEIGHT: 16px" align="right" ms_positioning="FlowLayout">
and last updated at:
</DIV>
<ASP:AdRotator id="Ads" style="Z-INDEX: 105; LEFT: 312px;
POSITION: absolute; TOP: 16px" runat="server"
Width="80px" Height="60px" AdvertisementFile="img/Ads.xml">
</ASP:AdRotator>
</form>
</body>
</HTML>
該頁的代碼隱藏文件包含支持 uncachedUpdate 方法的緩存后替代所必需的事件。請注意,Page_Load 方法報告加載該頁的時間,因此我們可以確定緩存發(fā)生的時間。
清單 8. PostCache.ASPX.cs
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
namespace WebApplication1 {
public class PostCache : System.Web.UI.Page {
protected System.Web.UI.WebControls.Label CreatedTime;
protected System.Web.UI.WebControls.Label UpdatedTime;
protected System.Web.UI.WebControls.AdRotator Ads;
private void InitializeComponent() {
this.Load += new System.EventHandler(this.Page_Load);
}
private void Page_Load(object sender, System.EventArgs e) {
CreatedTime.Text = DateTime.Now.ToShortTimeString();
}
protected String uncachedUpdate() {
return DateTime.Now.ToShortTimeString();
}
}
}
當前使用的緩存后替代
圖 6 顯示 PostCache 頁的輸出結(jié)果。首次運行應用程序,我們可以看到“創(chuàng)建頁”時間和“最后更新”時間是相同的。
圖 6. PostCache.ASPX 的輸出結(jié)果
連續(xù)調(diào)用同一個頁面,我們可以看到緩存后替代的效果。雖然頁面創(chuàng)建時間和圖像保持不變,但最后更新時間有變化。
圖 7. 第二次請求的緩存后輸出結(jié)果
由于緩存指令,創(chuàng)建時間和 adRotator 圖像都保持不變。該頁緩存了 30 秒。一旦時間到期,下次請求時創(chuàng)建時間和 adRotator 都將更新。但是,<asp:substitution> 控件(調(diào)用 uncachedUpdate() 方法)在每次請求頁面時都將更新,而不管其緩存狀態(tài)。
通過正確操作緩存后替代,開發(fā)人員可以通過只更新其頁面的動態(tài)內(nèi)容以顯著提高其 Web 應用程序性能。結(jié)合數(shù)據(jù)庫緩存無效和異步頁更新,用 ASP.NET 2.0 開發(fā)的 Web 應用程序?qū)⑾?Web 的傳統(tǒng)請求和響應體系結(jié)構(gòu)所施加的許多限制。
返回頁首性能
雖然在 ASP.NET 2.0 中更改了基礎(chǔ)結(jié)構(gòu)并增加了功能,但還有一個問題,ASP.NET 2.0 的執(zhí)行速度有多快?雖然沒有可用的性能衡量標準(因為 ASP.NET 2.0 仍在開發(fā)過程中),但是已花費了相當多的精力來確保ASP.NET 2.0 框架的各個方面的性能保持穩(wěn)定或有所提高。
改進的請求管道
每個開發(fā)人員都將看到性能得以提高的一個區(qū)域是在請求管道中。盡管添加了許多新的事件掛鉤,但是基本的 ASP.NET 請求棧的速度比在 ASP.NET 1.1 中的速度快。通過創(chuàng)建一個顯示“Hello World.”的簡單頁,可以評估提高的性能。因為該頁沒有高級功能,所以直接測試的是 HTTPHandler 和 HTTPModule 管道,以及將 ASP.NET 2.0 連接到 IIS 的 ISAPI 插件。無論使用的是哪一種版本的 IIS,應當可以看到性能得到了提高,因為這些代碼已經(jīng)針對更快的吞吐量進行了優(yōu)化。
利用 IIS 6.0 改進的內(nèi)存管理
一些 ASP.NET 2.0 中的性能改進只有在與 IIS 6.0 結(jié)合使用時才能體現(xiàn)。例如,在 IIS 6.0 中,在利用 100 個并發(fā)用戶通過幾個控件請求某個頁面的負載測試中,輔助進程的工作集被降低了大約 50%。這意味著,對于一個給定的服務器,操作系統(tǒng)使用的資源大約是以前所需資源的一半。
在設計的用來模仿中等復雜 ASP.NET 頁的某項測試中,與運行在 IIS 5.0 上的相同頁面相比,系統(tǒng)負載(內(nèi)存和 CPU 使用率)顯著下降。這種特定的性能改進是通過將響應緩沖區(qū)從托管內(nèi)存移到本機內(nèi)存完成的。通過消除將托管內(nèi)存固定到某個特定響應的必要性,ASP.NET 2.0 消除了資源瓶頸并對每個請求生成響應的速度更快。
其他的性能改進利用了 IIS 6.0 與 Windows 操作系統(tǒng)內(nèi)核的緊密集成。IIS 6.0 在內(nèi)核級別執(zhí)行它的一些緩存和緩沖功能,這為所有的 Web 應用程序(包括 ASP.NET)提高了性能。
其他改進
作為一名開發(fā)人員,您希望 ASP.NET 2.0 的運行速度與 ASP.NET 1.x 一樣快,或者比它還快。既然已經(jīng)構(gòu)建了核心功能,那么在 ASP.NET 2.0 的最后版本中一定能看到期望的其他性能改進。
返回頁首小結(jié)
ASP.NET 2.0 包含許多體系結(jié)構(gòu)升級,其設計是為了提高開發(fā)人員的工作效率。不但改進了代碼模型來減少沖突,而且還擴展了編譯過程以為編譯和部署 Web 應用程序提供更廣泛的選項。ASP.NET 框架的擴展性再次通過新的 HTTPModules 和 HTTPHandlers 顯示,它們支持建立在 ASP.NET 中的許多新功能,包括個性化、母版頁和管理站點。緩存已經(jīng)改進以允許數(shù)據(jù)庫依賴項和緩存后替代。從內(nèi)部來看,ASP.NET 2.0 包含了對它以前版本的顯著改進;這些新實現(xiàn)結(jié)合了許多開發(fā)人員驅(qū)動的實現(xiàn),同時沿用了業(yè)界的最佳做法。ASP.NET 2.0 提供一種世界一流的 Web 開發(fā)平臺,該平臺是為處理復雜的企業(yè) Web 應用程序開發(fā)而構(gòu)建的。