基于.NET的Web應(yīng)用框架構(gòu)建模式 |
[簡介] 本文對(duì)應(yīng)于Web表示模式集群,文章的前半部分重筆墨的描述了MVC模式的架構(gòu)、設(shè)計(jì)及其ASP.NET實(shí)現(xiàn),而在更加復(fù)雜的系統(tǒng)中,隨后提出了Page Controller(頁面控制器)和Front Controller(前端控制器)作為MVC實(shí)現(xiàn)的補(bǔ)充,最后,簡要介紹了Web表示模式集群的另外兩個(gè)模式:Intercepting Filter(篩選器)和Page Cache(頁面緩存)模式。
“體系結(jié)構(gòu)設(shè)計(jì)者的第一個(gè)作品往往比較簡練和干凈。他知道自己并不了解正在進(jìn)行的工作,因此他小心謹(jǐn)慎地設(shè)計(jì)它。在他設(shè)計(jì)第一個(gè)作品時(shí),會(huì)進(jìn)行多次修飾和潤色。這些會(huì)留到“下一次”使用……這第二個(gè)系統(tǒng)是他曾經(jīng)設(shè)計(jì)的最危險(xiǎn)的系統(tǒng)……一般趨勢(shì)是,在設(shè)計(jì)第二個(gè)系統(tǒng)時(shí),將會(huì)使用在第一個(gè)作品中被小心擱置在一邊的所有思路和修飾,從而導(dǎo)致設(shè)計(jì)過了頭。” —Frederick P. Brooks, Jr.發(fā)表于1972年的The Mythical Man Month(人月神話)。
Web上建立的第一個(gè)系統(tǒng)是簡單地鏈接在一起的靜態(tài)HTML頁面,以便在分散的小組之間共享文檔。隨著用戶的使用量增加,可響應(yīng)用戶輸入的動(dòng)態(tài)網(wǎng)頁日益普遍。早期的動(dòng)態(tài)頁面一般是以通用網(wǎng)關(guān)接口(CGI)腳本的形式編寫的。這些CGI腳本不僅包含用來確定在響應(yīng)用戶輸入時(shí)應(yīng)當(dāng)顯示什么內(nèi)容的業(yè)務(wù)邏輯,而且還會(huì)生成表示HTML。隨著對(duì)更復(fù)雜邏輯需求的增加,對(duì)更豐富、更生動(dòng)的表示形式的需求也隨之增加。這種增加了的復(fù)雜性給CGI編程模型帶來了挑戰(zhàn)。 不久,基于頁面的開發(fā)手段(如ASP和JSP)出現(xiàn)了。這些新方法允許開發(fā)人員將腳本直接嵌入到HTML面中,從而簡化了編程模型。當(dāng)這些嵌入的腳本應(yīng)用程序變得更復(fù)雜時(shí),開發(fā)人員希望在頁面級(jí)別將業(yè)務(wù)邏輯與表示邏輯分開。為適應(yīng)這一要求,隨之出現(xiàn)了具有幫助器對(duì)象和代碼隱藏頁面策略的標(biāo)記庫。然后,又出現(xiàn)了提供動(dòng)態(tài)配置站點(diǎn)導(dǎo)航和命令調(diào)度程序的精細(xì)框架,但所有這一切都是以增加復(fù)雜性為代價(jià)的。假設(shè)現(xiàn)在有大量的Web表示可選方案,如何為您的應(yīng)用程序選擇適當(dāng)?shù)腤eb表示設(shè)計(jì)策略?
是否真的有一個(gè)設(shè)計(jì)策略能夠適應(yīng)所有的情況?很不幸,在軟件設(shè)計(jì)中,消除過多的冗余和過度的復(fù)雜性是一個(gè)競爭性需求,很難能夠真正做到兩者之間的平衡。您可以從包含嵌入腳本的簡單頁面開始設(shè)計(jì)工作,但很快業(yè)務(wù)邏輯就會(huì)不斷重復(fù)出現(xiàn)在各個(gè)文件中,從而導(dǎo)致系統(tǒng)難以維護(hù)和擴(kuò)展。通過將該邏輯移到一組協(xié)作組件中,可以消除冗余,但是這樣做會(huì)增加解決方案的復(fù)雜性。另一方面,您的設(shè)計(jì)工作可以從設(shè)計(jì)用來提供標(biāo)記庫、動(dòng)態(tài)配置和命令調(diào)度程序的框架入手,可是這樣雖然能夠消除冗余代碼,但它會(huì)大大增加系統(tǒng)的復(fù)雜性,而這通常是不必要的。 而如何考慮各個(gè)方面的需求,提出一個(gè)最合適我們應(yīng)用的Web表示策略呢?這需要在復(fù)雜解決方案(支持將來可能發(fā)生變化的情形)和簡單解決方案(滿足目前的要求)之間做出抉擇,原則上適當(dāng)增加成本是可取的,而過多增加成本卻是不可取的。那么廢話少說,我們就從“簡單”開始吧。
MVC(模型—視圖—控制) 許多計(jì)算機(jī)系統(tǒng)的用途都是從數(shù)據(jù)存儲(chǔ)檢索數(shù)據(jù)并將其顯示給用戶。在用戶更改數(shù)據(jù)之后,系統(tǒng)再將更新內(nèi)容存儲(chǔ)到數(shù)據(jù)存儲(chǔ)中。因?yàn)殛P(guān)鍵的信息流發(fā)生在數(shù)據(jù)存儲(chǔ)和用戶界面之間,所以您可能傾向于將這兩部分綁在一起,以減少編碼量并提高應(yīng)用程序性能。但是,這種看起來自然而然的方法有一些大問題。一個(gè)問題是,用戶界面的更改往往比數(shù)據(jù)存儲(chǔ)系統(tǒng)的更改頻繁得多。將數(shù)據(jù)和用戶界面這兩部分耦合在一起帶來的另一個(gè)問題是,業(yè)務(wù)應(yīng)用程序往往會(huì)并入遠(yuǎn)不止數(shù)據(jù)傳輸功能的其他業(yè)務(wù)邏輯。如何讓W(xué)eb應(yīng)用程序的用戶界面功能實(shí)現(xiàn)模塊化,以便您可以輕松地單獨(dú)修改各個(gè)部分? Model-View-Controller正是這樣的模式,它實(shí)現(xiàn)功能模塊和顯示模塊的分離,使得應(yīng)用程序更加可維護(hù),可擴(kuò)展,可移植和可復(fù)用,它最初是Trygve Reenskaug在二十世紀(jì)七十年代末為Smalltalk平臺(tái)開發(fā)的框架[Fowler03],而發(fā)展到目前為止,已經(jīng)形成了一個(gè)非常成熟的模式。
MVC解決方案 Model-View-Controller (MVC)模式基于用戶輸入,將域的建模、顯示和操作分為三個(gè)獨(dú)立的類[Burbeck92]: q 模型。模型用于管理應(yīng)用程序域的行為和數(shù)據(jù),并響應(yīng)為獲取其狀態(tài)信息(通常來自視圖)而發(fā)出的請(qǐng)求,還會(huì)響應(yīng)更改狀態(tài)的指令(通常來自控制器)。 q 視圖。視圖用于管理信息的顯示。 q 控制器。控制器用于解釋用戶的鼠標(biāo)和鍵盤輸入,以通知模型和/或視圖進(jìn)行相應(yīng)的更改。 視圖和控制器都依賴于模型。但是,模型既不依賴于視圖,也不依賴于控制器。這是分離的主要優(yōu)點(diǎn)之一。這樣的分離允許模型在獨(dú)立于可視表示功能的情況下建立和測(cè)試。在許多胖客戶端應(yīng)用程序中,視圖與控制器的分離是次要的,實(shí)際上,許多用戶界面框架將角色實(shí)現(xiàn)為一個(gè)對(duì)象。另一方面,在Web應(yīng)用程序中,視圖(瀏覽器)與控制器(處理HTTP請(qǐng)求的服務(wù)器端組件)的分離是很好定義的。 Model-View-Controller是一個(gè)用于將用戶界面邏輯與業(yè)務(wù)邏輯分離開來的基礎(chǔ)設(shè)計(jì)模式。遺憾的是,此模式的普及導(dǎo)致了許多錯(cuò)誤的描述。特別是在不同的上下文中,術(shù)語“控制器”已經(jīng)用于意指不同的事物。幸運(yùn)的是,Web應(yīng)用程序的出現(xiàn)已經(jīng)幫助消除了一些不明確性,因?yàn)橐晥D與控制器的分離是如此明顯。
MVC的變型 在Application Programming in Smalltalk-80: How to use Model-View-Controller (MVC) [Burbeck92]中,Steve Burbeck描述了MVC的兩個(gè)變型:被動(dòng)模型和主動(dòng)模型。 當(dāng)一個(gè)控制器以獨(dú)占方式操作模型時(shí),將使用被動(dòng)模型??刂破鲗⑿薷哪P?,然后通知視圖:模型已經(jīng)更改,應(yīng)該進(jìn)行刷新(見圖2)。此情況下的模型完全獨(dú)立于視圖和控制器,這意味著模型無法報(bào)告其狀態(tài)更改。HTTP協(xié)議是此方案的示例。瀏覽器沒有從服務(wù)器獲取異步更新的簡單方法。瀏覽器顯示視圖并對(duì)用戶輸入作出響應(yīng),但是它不會(huì)檢測(cè)服務(wù)器上的數(shù)據(jù)更改。僅當(dāng)用戶顯式請(qǐng)求刷新時(shí),才會(huì)詢問服務(wù)器是否發(fā)生了更改。 當(dāng)模型更改狀態(tài)而不涉及控制器時(shí),將使用主動(dòng)模型。當(dāng)其他資源正在更改數(shù)據(jù)并且更改必須反映在視圖中時(shí),可能會(huì)發(fā)生這種情況。以股票報(bào)價(jià)機(jī)的顯示為例。您從外部源接收股票數(shù)據(jù),并希望當(dāng)股票數(shù)據(jù)更改時(shí)更新視圖(例如,報(bào)價(jià)機(jī)數(shù)據(jù)區(qū)和警告窗口)。因?yàn)橹挥心P蜋z測(cè)對(duì)其內(nèi)部狀態(tài)的更改(在這些更改發(fā)生時(shí)),所以模型必須通知視圖刷新顯示。 但是,使用MVC模式的一個(gè)目的是使模型獨(dú)立于視圖。如果模型必須將更改通知視圖,則會(huì)重新帶來您希望避免的依賴性。幸運(yùn)的是,Observer模式[Gamma95]提供了這樣的機(jī)制:提醒其他對(duì)象注意狀態(tài)的更改,而不會(huì)導(dǎo)致對(duì)這些對(duì)象的依賴性。各個(gè)視圖實(shí)現(xiàn)Observer接口,并向模型注冊(cè)。模型將跟蹤由訂閱更改的所有觀察器組成的列表。當(dāng)模型發(fā)生改變時(shí),模型將會(huì)遍歷所有已注冊(cè)的觀察器,并將更改通知它們。此方法通常稱為“發(fā)布-訂閱”。模型從不需要有關(guān)任何視圖的特定信息。實(shí)際上,在需要將模型更改通知控制器的情況下(例如,啟用或禁用菜單選項(xiàng)),控制器必須做的全部工作是實(shí)現(xiàn)Observer接口并訂閱模型更改。對(duì)于存在許多視圖的情況,定義多個(gè)主體是有意義的,其中每個(gè)主體都描述了特定類型的模型更改。然后,每個(gè)視圖都只能訂閱與視圖有關(guān)的更改類型。圖3顯示了使用Observer的主動(dòng)MVC的結(jié)構(gòu),以及觀察器如何將模型與直接引用視圖隔離開來。 圖4說明當(dāng)模型發(fā)生改變時(shí)Observer如何通知視圖。可惜的是,在統(tǒng)一建模語言(UML)序列圖中,沒有好的方法來演示模型與視圖的分離,因?yàn)樵搱D表示的是對(duì)象的實(shí)例而不是類和接口。 MVC的ASP.NET實(shí)現(xiàn) 為了解釋如何在ASP.NET中實(shí)現(xiàn)Model-View-Controller模式,并說明在軟件中分離模型、視圖和控制器角色的好處,下面的示例將一個(gè)沒有分離所有三個(gè)角色的單頁面解決方案重構(gòu)為分離這三個(gè)角色的解決方案。示例應(yīng)用程序是一個(gè)帶有下拉列表的網(wǎng)頁(如欄下圖所示),該頁面顯示了存儲(chǔ)在數(shù)據(jù)庫中的記錄。 利用Microsoft Visual Studio(r) .NET開發(fā)系統(tǒng)的代碼隱藏功能,可以很容易地將表示(視圖)代碼與Model-Controller代碼分離開來。每個(gè)ASP.NET頁都有一種機(jī)制,這種機(jī)制允許在單獨(dú)的類中實(shí)現(xiàn)從網(wǎng)頁調(diào)用的方法。該機(jī)制是通過Visual Studio .NET提供的,它有許多優(yōu)點(diǎn),例如Microsoft IntelliSense(r)技術(shù)。當(dāng)您使用代碼隱藏功能來實(shí)現(xiàn)網(wǎng)頁時(shí),可以使用IntelliSense來顯示網(wǎng)頁后面的代碼中所使用的對(duì)象的可用方法列表。IntelliSense不適用于.aspx頁。與此同時(shí),為了展現(xiàn)Model-Controller的分離,對(duì)于數(shù)據(jù)庫操作提取了DatabaseGateway,這樣就實(shí)現(xiàn)了三者的完整分離。 視圖 <%@ Page language="c#" Codebehind="Solution.aspx.cs" AutoEventWireup="false" Inherits="Solution" %> <html> <head> <title>解決方案</title> </head> <body> <form id="Solution" method="post" runat="server"> <h3>錄音</h3> 選擇錄音:<br/> <asp:dropdownlist id="recordingSelect" runat="server" /> <asp:button id="submit" runat="server" text="Submit" enableviewstate="False" /> <p/> <asp:datagrid id="MyDataGrid" runat="server" width="700" backcolor="#ccccff" bordercolor="black" showfooter="false" cellpadding="3" cellspacing="0" font-name="Verdana" font-size="8pt" headerstyle-backcolor="#aaaadd" enableviewstate="false" /> </form> </body> </html>
模型 using System; using System.Collections; using System.Data; using System.Data.SqlClient; public class DatabaseGateway { public static DataSet GetRecordings() { String selectCmd = "select * from Recording";
SqlConnection myConnection = new SqlConnection( "server=(local);database=recordings;Trusted_Connection=yes"); SqlDataAdapter myCommand = new SqlDataAdapter(selectCmd, myConnection);
DataSet ds = new DataSet(); myCommand.Fill(ds, "Recording"); return ds; }
public static DataSet GetTracks(string recordingId) { String selectCmd = String.Format( "select * from Track where recordingId = {0} order by id", recordingId);
SqlConnection myConnection = new SqlConnection( "server=(local);database=recordings;Trusted_Connection=yes"); SqlDataAdapter myCommand = new SqlDataAdapter(selectCmd, myConnection);
DataSet ds = new DataSet(); myCommand.Fill(ds, "Track"); return ds; }
控制 using System; using System.Data; using System.Collections; using System.Web.UI.WebControls;
public class Solution : System.Web.UI.Page { protected System.Web.UI.WebControls.Button submit; protected System.Web.UI.WebControls.DataGrid MyDataGrid; protected System.Web.UI.WebControls.DropDownList recordingSelect;
private void Page_Load(object sender, System.EventArgs e) { if(!IsPostBack) { DataSet ds = DatabaseGateway.GetRecordings(); recordingSelect.DataSource = ds; recordingSelect.DataTextField = "title"; recordingSelect.DataValueField = "id"; recordingSelect.DataBind(); } }
void SubmitBtn_Click(Object sender, EventArgs e) { DataSet ds = DatabaseGateway.GetTracks( (string)recordingSelect.SelectedItem.Value);
MyDataGrid.DataSource = ds; MyDataGrid.DataBind(); }
#region Web Form Designer generated code override protected void OnInit(EventArgs e) { // // CODEGEN: 此調(diào)用是 ASP.NET Web 窗體設(shè)計(jì)器所必需的。 // InitializeComponent(); base.OnInit(e); }
/// <summary> /// 設(shè)計(jì)器支持所必需的方法 - 不要使用代碼編輯器修改 /// 此方法的內(nèi)容。 /// </summary> private void InitializeComponent() { this.submit.Click += new System.EventHandler(this.SubmitBtn_Click); this.Load += new System.EventHandler(this.Page_Load);
} #endregion }
以上示例簡單的說明了ASP.NET的MVC實(shí)現(xiàn),在實(shí)際項(xiàng)目中,商務(wù)邏輯遠(yuǎn)遠(yuǎn)不止這樣,但是上述的代碼展現(xiàn)了一個(gè)基本的模型,在增加了代碼和復(fù)雜度的同時(shí)也帶來了顯而易見的好處,如:模塊依賴的降低、代碼重復(fù)的減少、職責(zé)和問題的分離、代碼的可測(cè)試性等等。 到現(xiàn)在您是否決定了使用Model-View-Controller (MVC)模式來將動(dòng)態(tài)Web應(yīng)用程序的用戶界面組件與業(yè)務(wù)邏輯分隔開來,要構(gòu)建的應(yīng)用程序?qū)⒁詣?dòng)態(tài)方式構(gòu)造網(wǎng)頁,但是目前的頁面導(dǎo)航都是基于靜態(tài)導(dǎo)航的形式。 在更加復(fù)雜的應(yīng)用系統(tǒng)中,如何考慮盡可能避免導(dǎo)航代碼的重復(fù),甚至考慮基于可配置的規(guī)則來動(dòng)態(tài)確定頁面導(dǎo)航,那么Page Controller和Front Controller某種意義來說是對(duì)于MVC模式在更加復(fù)雜的系統(tǒng)的優(yōu)化。
中等復(fù)雜程度的優(yōu)化—Page Controller(頁面控制器) 使用Page Controller模式接受來自頁面請(qǐng)求的輸入、調(diào)用請(qǐng)求對(duì)模型執(zhí)行的操作以及確定應(yīng)用于結(jié)果頁面的正確視圖。分隔調(diào)度邏輯和所有視圖相關(guān)代碼。如果合適,創(chuàng)建用于所有頁面控制器的公用基類,以避免代碼重復(fù)并提高一致性和可測(cè)試性。圖5顯示了頁面控制器與模型和視圖的關(guān)系。 頁面控制器可接收頁面請(qǐng)求、提取所有相關(guān)數(shù)據(jù)、調(diào)用對(duì)模型的所有更新以及向視圖轉(zhuǎn)發(fā)請(qǐng)求。而視圖又將根據(jù)該模型檢索要顯示的數(shù)據(jù)。定義獨(dú)立頁面控制器將分隔模型與Web請(qǐng)求細(xì)節(jié)(例如會(huì)話管理,或使用查詢字符串或隱藏表單域向頁面?zhèn)鬟f參數(shù))。按照這種基本形式,為Web應(yīng)用程序中的每個(gè)鏈接創(chuàng)建控制器??刂破饕蚨鴮⒆兊梅浅:唵危?yàn)槊看蝺H須考慮一個(gè)操作。 為每個(gè)網(wǎng)頁(或操作)創(chuàng)建獨(dú)立控制器可能會(huì)導(dǎo)致大量代碼重復(fù)。因此應(yīng)該創(chuàng)建BaseController類以合并驗(yàn)證參數(shù)(請(qǐng)參閱圖6)等公用函數(shù)。每個(gè)獨(dú)立頁面控制器都可以從BaseController繼承此公用功能。除了從公用基類繼承之外,還可以定義一組幫助器類,控制器可以調(diào)用這些類來執(zhí)行公用功能。 大多數(shù)情況下,頁面控制器取決于基于HTTP的Web請(qǐng)求的具體細(xì)節(jié)。因此,頁面控制器代碼通常包含對(duì)HTTP頭、查詢字符串、表單域、多部分表單請(qǐng)求等的引用。因此在Web應(yīng)用程序框架之外測(cè)試控制器代碼非常困難。唯一方法是通過模擬HTTP請(qǐng)求和分析結(jié)果來測(cè)試控制器。這種類型的測(cè)試既費(fèi)時(shí)且易出錯(cuò)。因此,要提高可測(cè)試性,可以將依賴Web的代碼和不依賴Web的代碼分別放入兩個(gè)單獨(dú)類中(請(qǐng)參閱圖7),這是Page Controller的變體實(shí)現(xiàn)。 Page Controller是大多數(shù)動(dòng)態(tài)Web應(yīng)用程序的默認(rèn)實(shí)現(xiàn)方式,它簡單,Web應(yīng)用框架內(nèi)置,可擴(kuò)展性及其開發(fā)人員責(zé)任的分隔等等優(yōu)點(diǎn)使其在Web開發(fā)得到廣泛的應(yīng)用,不過每頁面一控制器、較深的繼承樹和對(duì)于Web框架的依賴等等也是其比較明顯的限制。
高度復(fù)雜的優(yōu)化 —Front Controller(前端控制器) Front Controller通過讓單個(gè)控制器負(fù)責(zé)傳輸所有請(qǐng)求,從而解決了在Page Controller中存在的分散化問題。控制器本身通常分為以下兩部分實(shí)現(xiàn):處理程序和命令層次結(jié)構(gòu)(見圖8)。 處理程序具有以下兩項(xiàng)職責(zé): 1) 檢索參數(shù)。處理程序接收來自Web服務(wù)器的HTTP Post或Get請(qǐng)求,并從請(qǐng)求中檢索相關(guān)參數(shù)。 2) 選擇命令。處理程序首先使用請(qǐng)求中的參數(shù)選擇正確的命令,然后將控制權(quán)轉(zhuǎn)移給該命令以便執(zhí)行處理。 在前端控制器中,所有請(qǐng)求都通過單個(gè)(通常是兩部件)控制器來傳送??刂破鞯牡谝粋€(gè)部件是處理程序,第二個(gè)部件是Commands(命令)[Gof95]的層次。命令本身是控制器的一部分,代表控制器觸發(fā)的特定操作。在執(zhí)行該操作之后,命令選擇要使用哪個(gè)視圖來呈現(xiàn)頁面。通常,構(gòu)建的此控制器框架使用配置文件將請(qǐng)求映射到操作,因此,它在構(gòu)建之后便于更改。當(dāng)然,其缺點(diǎn)在于這種設(shè)計(jì)固有的復(fù)雜程度。
遺漏之后的補(bǔ)充 到目前為止,已經(jīng)完整地展示了Web表示模式中MVC的實(shí)現(xiàn)架構(gòu),同時(shí)在此技術(shù)上對(duì)于中等復(fù)雜和高度復(fù)雜的情況提出了Page Controller和Front Controller作為優(yōu)化的手段,在構(gòu)建Web表示的應(yīng)用程序,已經(jīng)給出了相對(duì)完整的解決方案,細(xì)心的讀者是否發(fā)現(xiàn),是不是還少了一點(diǎn)什么?在構(gòu)建Web應(yīng)用程序的時(shí)候,除了構(gòu)建一個(gè)“良構(gòu)”的系統(tǒng)(這表現(xiàn)在MVC的分離和系統(tǒng)的可擴(kuò)展),我們還需要考慮應(yīng)用程序的安全和性能,因此我們選取了Interceptiing Filter和Page Cache作為Web表示模式的補(bǔ)充內(nèi)容。
Intercepting Filter(篩選器) 如何圍繞Web頁面請(qǐng)求來實(shí)現(xiàn)公共的預(yù)處理和后處理步驟?這是篩選器需要解決的問題,使用此模式,您創(chuàng)建一串可組合的篩選器,以便在Web頁面請(qǐng)求期間實(shí)現(xiàn)公共的預(yù)處理和后處理任務(wù)。 篩選器構(gòu)成了一系列獨(dú)立模塊,在將頁面請(qǐng)求傳遞到控制器對(duì)象之前,這些模塊可以鏈接在一起以執(zhí)行一組公共的處理步驟。因?yàn)楦鱾€(gè)篩選器實(shí)現(xiàn)的是完全相同的接口,所以它們彼此之間沒有顯式依賴性。因此,可以在不會(huì)影響現(xiàn)有篩選器的情況下添加新的篩選器。您甚至可以在部署時(shí)添加篩選器,方法是基于配置文件動(dòng)態(tài)地對(duì)它們進(jìn)行實(shí)例化。 對(duì)各個(gè)篩選器的設(shè)計(jì)應(yīng)該盡可能讓它們不對(duì)是否存在其他篩選器作出任何假設(shè)。這樣可以維護(hù)可組合性,即添加、刪除或重新排列篩選器的能力。此外,某些實(shí)現(xiàn)Intercepting Filter模式的框架不會(huì)保證篩選器的執(zhí)行順序。如果發(fā)現(xiàn)多個(gè)篩選器之間存在很強(qiáng)的相互依賴性,最好采取調(diào)用幫助器類的常規(guī)方法,因?yàn)檫@樣可以保證保留篩選器序列中的約束信息。Intercepting Filter的直接實(shí)現(xiàn)方式是一個(gè)篩選器鏈,這個(gè)篩選器鏈可以用來遍歷一個(gè)由所有篩選器組成的列表。Web請(qǐng)求處理程序在將控制權(quán)傳遞到應(yīng)用程序邏輯之前將首先執(zhí)行篩選器鏈。 當(dāng)Web服務(wù)器收到頁面請(qǐng)求時(shí),請(qǐng)求處理程序首先將控制權(quán)傳遞給FilterChain(篩選器鏈)對(duì)象。此對(duì)象維護(hù)著一個(gè)包含所有篩選器的列表,并按順序調(diào)用每個(gè)篩選器。FilterChain可以從配置文件中讀取篩選器順序,以實(shí)現(xiàn)部署時(shí)的可組合性。每個(gè)篩選器都有機(jī)會(huì)修改傳入請(qǐng)求。例如,它可以修改URL,或添加應(yīng)用程序要使用的頭字段。執(zhí)行完所有篩選器之后,"請(qǐng)求處理程序"將控制權(quán)傳遞給控制器,后者將執(zhí)行應(yīng)用程序功能(見圖12)。 因?yàn)樵谔幚鞼eb請(qǐng)求時(shí)通常需要有截取篩選器,所以大多數(shù)Web框架都為應(yīng)用程序開發(fā)人員提供了將截取篩選器掛靠到請(qǐng)求-響應(yīng)過程中的機(jī)制。
Page Cache(頁面緩存)] 如果動(dòng)態(tài)生成的Web頁被頻繁請(qǐng)求并且構(gòu)建時(shí)需要耗用大量的系統(tǒng)資源,那么,如何才能改進(jìn)這類網(wǎng)頁的響應(yīng)時(shí)間?頁面緩存通過對(duì)從動(dòng)態(tài)網(wǎng)頁生成的內(nèi)容進(jìn)行緩存來提高請(qǐng)求響應(yīng)的吞吐量。默認(rèn)情況下,在ASP.NET中支持頁面緩存,但除非定義有效的過期策略,否則,不會(huì)對(duì)來自任何給定響應(yīng)的輸出進(jìn)行緩存。要定義過期策略,可以使用低級(jí)OutputCache API或高級(jí)@OutputCache指令。 頁面緩存的基本結(jié)構(gòu)是相對(duì)簡單的。Web服務(wù)器維護(hù)包含預(yù)先生成的頁面的本地?cái)?shù)據(jù)存儲(chǔ)(見圖13)。 下面的序列圖闡明了頁面緩存可以改進(jìn)性能的原因。第一個(gè)序列圖(見圖2)描述尚未緩存所需頁面的初始狀態(tài)(即所謂的緩存未命中)。在這種情況下,Web服務(wù)器必須訪問數(shù)據(jù)庫,并生成HTML頁,再將其存儲(chǔ)在緩存中,然后將它返回給客戶端瀏覽器。請(qǐng)注意,此過程比不進(jìn)行緩存的情況稍慢,因?yàn)樗鼒?zhí)行了下列額外步驟: 1. 確定頁面是否已緩存 2. 將頁面轉(zhuǎn)換為HTML代碼,然后存儲(chǔ)在緩存中
與數(shù)據(jù)庫訪問和HTML生成相比,其中的任一步驟都不應(yīng)該花費(fèi)很長時(shí)間。但是,因?yàn)樵诖饲闆r下需要進(jìn)行額外處理,所以您必須確保在系統(tǒng)完成與緩存未命中關(guān)聯(lián)的步驟之后連續(xù)多次命中緩存(如圖14所示)。 在圖15所示的緩存命中情況下,頁面已經(jīng)處于緩存中。通過跳過數(shù)據(jù)庫訪問、頁面生成和頁面存儲(chǔ),緩存命中節(jié)省了循環(huán)。 在ASP.NET中可以采用下述三種模式來實(shí)現(xiàn)緩存策略: 1.將<%@ OutputCache Duration="60" VaryByParam="none" %>這樣的指令插入到要緩存的每個(gè)頁面中。指令指定刷新間隔(以秒為單位)。刷新間隔不依賴于外部事件,而且緩存不能全部刷新。 2.Vary-By-Parameter Caching.此模式使用Absolute Expiration的變型,該變型使開發(fā)人員能夠指定影響頁面內(nèi)容的參數(shù)。因此,緩存將存儲(chǔ)頁面的多個(gè)版本,并按參數(shù)值為這些頁面版本編制索引。 3.Sliding Expiration Caching.此模式與Absolute Expiration(絕對(duì)過期)的類似之處是,頁面在指定的時(shí)間內(nèi)是有效的。但是,在每次請(qǐng)求時(shí)都會(huì)重置刷新間隔。例如,您可能使用滑動(dòng)過期緩存,將一個(gè)頁面緩存最長10分鐘。只要對(duì)頁面的請(qǐng)求是在10分鐘內(nèi)發(fā)出的,就將過期時(shí)間再延長10分鐘。
結(jié)束語 自此,我們已經(jīng)完整地介紹了Web表示集群中相關(guān)的各個(gè)模式,關(guān)于在MVC模式中Observer(觀察者)模式的用法,我們會(huì)用專門的篇幅來介紹,文字中提到的一些引用可以在http://www.microsoft.com/china/msdn/architecture/patterns/EspBiblio 找到相對(duì)詳細(xì)的列表,當(dāng)然,網(wǎng)絡(luò)就是您最好的資源。
|
|