.NET Web應用框架構建模式

發表于:2007-05-25來源:作者:點擊數: 標簽:框架web.NET應用構建
[簡介] 本文對應于Web表示模式集群,文章的前半部分重筆墨的描述了MVC模式的架構、設計及其ASP.NET實現,而在更加復雜的系統中,隨后提出了Page Controller(頁面控制器)和Front Controller(前端控制器)作為MVC實現的補充,最后,簡要介紹了Web表示模式集群的

  [簡介]

  本文對應于Web表示模式集群,文章的前半部分重筆墨的描述了MVC模式的架構、設計及其ASP.NET實現,而在更加復雜的系統中,隨后提出了Page Controller(頁面控制器)和Front Controller(前端控制器)作為MVC實現的補充,最后,簡要介紹了Web表示模式集群的另外兩個模式:Intercepting Filter(篩選器)和Page Cache(頁面緩存)模式。
  “體系結構設計者的第一個作品往往比較簡練和干凈。他知道自己并不了解正在進行的工作,因此他小心謹慎地設計它。在他設計第一個作品時,會進行多次修飾和潤色。這些會留到“下一次”使用……這第二個系統是他曾經設計的最危險的系統……一般趨勢是,在設計第二個系統時,將會使用在第一個作品中被小心擱置在一邊的所有思路和修飾,從而導致設計過了頭?!?BR>—Frederick P. Brooks, Jr.發表于1972年的The Mythical Man Month(人月神話)。

  Web上建立的第一個系統是簡單地鏈接在一起的靜態HTML頁面,以便在分散的小組之間共享文檔。隨著用戶的使用量增加,可響應用戶輸入的動態網頁日益普遍。早期的動態頁面一般是以通用網關接口(CGI)腳本的形式編寫的。這些CGI腳本不僅包含用來確定在響應用戶輸入時應當顯示什么內容的業務邏輯,而且還會生成表示HTML。隨著對更復雜邏輯需求的增加,對更豐富、更生動的表示形式的需求也隨之增加。這種增加了的復雜性給CGI編程模型帶來了挑戰。

  不久,基于頁面的開發手段(如ASP和JSP)出現了。這些新方法允許開發人員將腳本直接嵌入到HTML面中,從而簡化了編程模型。當這些嵌入的腳本應用程序變得更復雜時,開發人員希望在頁面級別將業務邏輯與表示邏輯分開。為適應這一要求,隨之出現了具有幫助器對象和代碼隱藏頁面策略的標記庫。然后,又出現了提供動態配置站點導航和命令調度程序的精細框架,但所有這一切都是以增加復雜性為代價的。假設現在有大量的Web表示可選方案,如何為您的應用程序選擇適當的Web表示設計策略?

  是否真的有一個設計策略能夠適應所有的情況?很不幸,在軟件設計中,消除過多的冗余和過度的復雜性是一個競爭性需求,很難能夠真正做到兩者之間的平衡。您可以從包含嵌入腳本的簡單頁面開始設計工作,但很快業務邏輯就會不斷重復出現在各個文件中,從而導致系統難以維護和擴展。通過將該邏輯移到一組協作組件中,可以消除冗余,但是這樣做會增加解決方案的復雜性。另一方面,您的設計工作可以從設計用來提供標記庫、動態配置和命令調度程序的框架入手,可是這樣雖然能夠消除冗余代碼,但它會大大增加系統的復雜性,而這通常是不必要的。

  而如何考慮各個方面的需求,提出一個最合適我們應用的Web表示策略呢?這需要在復雜解決方案(支持將來可能發生變化的情形)和簡單解決方案(滿足目前的要求)之間做出抉擇,原則上適當增加成本是可取的,而過多增加成本卻是不可取的。那么廢話少說,我們就從“簡單”開始吧。

  MVC(模型—視圖—控制)

  許多計算機系統的用途都是從數據存儲檢索數據并將其顯示給用戶。在用戶更改數據之后,系統再將更新內容存儲到數據存儲中。因為關鍵的信息流發生在數據存儲和用戶界面之間,所以您可能傾向于將這兩部分綁在一起,以減少編碼量并提高應用程序性能。但是,這種看起來自然而然的方法有一些大問題。一個問題是,用戶界面的更改往往比數據存儲系統的更改頻繁得多。將數據和用戶界面這兩部分耦合在一起帶來的另一個問題是,業務應用程序往往會并入遠不止數據傳輸功能的其他業務邏輯。如何讓Web應用程序的用戶界面功能實現模塊化,以便您可以輕松地單獨修改各個部分?

  Model-View-Controller正是這樣的模式,它實現功能模塊和顯示模塊的分離,使得應用程序更加可維護,可擴展,可移植和可復用,它最初是Trygve Reenskaug在二十世紀七十年代末為Smalltalk平臺開發的框架[Fowler03],而發展到目前為止,已經形成了一個非常成熟的模式。



  MVC解決方案

  Model-View-Controller (MVC)模式基于用戶輸入,將域的建模、顯示和操作分為三個獨立的類[Burbeck92]:

   模型。模型用于管理應用程序域的行為和數據,并響應為獲取其狀態信息(通常來自視圖)而發出的請求,還會響應更改狀態的指令(通常來自控制器)。

   視圖。視圖用于管理信息的顯示。

   控制器??刂破饔糜诮忉層脩舻氖髽撕玩I盤輸入,以通知模型和/或視圖進行相應的更改。


圖1、描述了這三個對象之間的結構關系

  視圖和控制器都依賴于模型。但是,模型既不依賴于視圖,也不依賴于控制器。這是分離的主要優點之一。這樣的分離允許模型在獨立于可視表示功能的情況下建立和測試。在許多胖客戶端應用程序中,視圖與控制器的分離是次要的,實際上,許多用戶界面框架將角色實現為一個對象。另一方面,在Web應用程序中,視圖(瀏覽器)與控制器(處理HTTP請求的服務器端組件)的分離是很好定義的。

  Model-View-Controller是一個用于將用戶界面邏輯與業務邏輯分離開來的基礎設計模式。遺憾的是,此模式的普及導致了許多錯誤的描述。特別是在不同的上下文中,術語“控制器”已經用于意指不同的事物。幸運的是,Web應用程序的出現已經幫助消除了一些不明確性,因為視圖與控制器的分離是如此明顯。

  MVC的變型

  在Application Programming in Smalltalk-80: How to use Model-View-Controller (MVC) [Burbeck92]中,Steve Burbeck描述了MVC的兩個變型:被動模型和主動模型。

  當一個控制器以獨占方式操作模型時,將使用被動模型??刂破鲗⑿薷哪P?,然后通知視圖:模型已經更改,應該進行刷新(見圖2)。此情況下的模型完全獨立于視圖和控制器,這意味著模型無法報告其狀態更改。HTTP協議是此方案的示例。瀏覽器沒有從服務器獲取異步更新的簡單方法。瀏覽器顯示視圖并對用戶輸入作出響應,但是它不會檢測服務器上的數據更改。僅當用戶顯式請求刷新時,才會詢問服務器是否發生了更改。


圖2、被動模型的行為

  當模型更改狀態而不涉及控制器時,將使用主動模型。當其他資源正在更改數據并且更改必須反映在視圖中時,可能會發生這種情況。以股票報價機的顯示為例。您從外部源接收股票數據,并希望當股票數據更改時更新視圖(例如,報價機數據區和警告窗口)。因為只有模型檢測對其內部狀態的更改(在這些更改發生時),所以模型必須通知視圖刷新顯示。

  但是,使用MVC模式的一個目的是使模型獨立于視圖。如果模型必須將更改通知視圖,則會重新帶來您希望避免的依賴性。幸運的是,Observer模式[Gamma95]提供了這樣的機制:提醒其他對象注意狀態的更改,而不會導致對這些對象的依賴性。各個視圖實現Observer接口,并向模型注冊。模型將跟蹤由訂閱更改的所有觀察器組成的列表。當模型發生改變時,模型將會遍歷所有已注冊的觀察器,并將更改通知它們。此方法通常稱為“發布-訂閱”。模型從不需要有關任何視圖的特定信息。實際上,在需要將模型更改通知控制器的情況下(例如,啟用或禁用菜單選項),控制器必須做的全部工作是實現Observer接口并訂閱模型更改。對于存在許多視圖的情況,定義多個主體是有意義的,其中每個主體都描述了特定類型的模型更改。然后,每個視圖都只能訂閱與視圖有關的更改類型。圖3顯示了使用Observer的主動MVC的結構,以及觀察器如何將模型與直接引用視圖隔離開來。


圖3、在主動模型中使用觀察器將模型與視圖分離

  圖4說明當模型發生改變時Observer如何通知視圖??上У氖?,在統一建模語言(UML)序列圖中,沒有好的方法來演示模型與視圖的分離,因為該圖表示的是對象的實例而不是類和接口。


圖4、主動模型的行為


  MVC的ASP.NET實現

  為了解釋如何在ASP.NET中實現Model-View-Controller模式,并說明在軟件中分離模型、視圖和控制器角色的好處,下面的示例將一個沒有分離所有三個角色的單頁面解決方案重構為分離這三個角色的解決方案。示例應用程序是一個帶有下拉列表的網頁(如欄下圖所示),該頁面顯示了存儲在數據庫中的記錄。


圖5

  利用Microsoft Visual Studio(r) .NET開發系統的代碼隱藏功能,可以很容易地將表示(視圖)代碼與Model-Controller代碼分離開來。每個ASP.NET頁都有一種機制,這種機制允許在單獨的類中實現從網頁調用的方法。該機制是通過Visual Studio .NET提供的,它有許多優點,例如Microsoft IntelliSense(r)技術。當您使用代碼隱藏功能來實現網頁時,可以使用IntelliSense來顯示網頁后面的代碼中所使用的對象的可用方法列表。IntelliSense不適用于.aspx頁。與此同時,為了展現Model-Controller的分離,對于數據庫操作提取了DatabaseGateway,這樣就實現了三者的完整分離。

  視圖

<%@ 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="#clearcase/" target="_blank" >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: 此調用是 ASP.NET Web 窗體設計器所必需的。
//
InitializeComponent();
base.OnInit(e);
}

/// <summary>
/// 設計器支持所必需的方法 - 不要使用代碼編輯器修改
/// 此方法的內容。
/// </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實現,在實際項目中,商務邏輯遠遠不止這樣,但是上述的代碼展現了一個基本的模型,在增加了代碼和復雜度的同時也帶來了顯而易見的好處,如:模塊依賴的降低、代碼重復的減少、職責和問題的分離、代碼的可測試性等等。

  到現在您是否決定了使用Model-View-Controller (MVC)模式來將動態Web應用程序的用戶界面組件與業務邏輯分隔開來,要構建的應用程序將以動態方式構造網頁,但是目前的頁面導航都是基于靜態導航的形式。
在更加復雜的應用系統中,如何考慮盡可能避免導航代碼的重復,甚至考慮基于可配置的規則來動態確定頁面導航,那么Page Controller和Front Controller某種意義來說是對于MVC模式在更加復雜的系統的優化。


  中等復雜程度的優化—Page Controller(頁面控制器)

  使用Page Controller模式接受來自頁面請求的輸入、調用請求對模型執行的操作以及確定應用于結果頁面的正確視圖。分隔調度邏輯和所有視圖相關代碼。如果合適,創建用于所有頁面控制器的公用基類,以避免代碼重復并提高一致性和可測試性。圖5顯示了頁面控制器與模型和視圖的關系。


圖6、使用BaseController消除代碼重復

  頁面控制器可接收頁面請求、提取所有相關數據、調用對模型的所有更新以及向視圖轉發請求。而視圖又將根據該模型檢索要顯示的數據。定義獨立頁面控制器將分隔模型與Web請求細節(例如會話管理,或使用查詢字符串或隱藏表單域向頁面傳遞參數)。按照這種基本形式,為Web應用程序中的每個鏈接創建控制器??刂破饕蚨鴮⒆兊梅浅:唵?,因為每次僅須考慮一個操作。

  為每個網頁(或操作)創建獨立控制器可能會導致大量代碼重復。因此應該創建BaseController類以合并驗證參數(請參閱圖6)等公用函數。每個獨立頁面控制器都可以從BaseController繼承此公用功能。除了從公用基類繼承之外,還可以定義一組幫助器類,控制器可以調用這些類來執行公用功能。

  大多數情況下,頁面控制器取決于基于HTTP的Web請求的具體細節。因此,頁面控制器代碼通常包含對HTTP頭、查詢字符串、表單域、多部分表單請求等的引用。因此在Web應用程序框架之外測試控制器代碼非常困難。唯一方法是通過模擬HTTP請求和分析結果來測試控制器。這種類型的測試既費時且易出錯。因此,要提高可測試性,可以將依賴Web的代碼和不依賴Web的代碼分別放入兩個單獨類中(請參閱圖7),這是Page Controller的變體實現。


圖7、分隔依賴Web的代碼和不依賴Web的代碼

  Page Controller是大多數動態Web應用程序的默認實現方式,它簡單,Web應用框架內置,可擴展性及其開發人員責任的分隔等等優點使其在Web開發得到廣泛的應用,不過每頁面一控制器、較深的繼承樹和對于Web框架的依賴等等也是其比較明顯的限制。

  高度復雜的優化

  —Front Controller(前端控制器)

  Front Controller通過讓單個控制器負責傳輸所有請求,從而解決了在Page Controller中存在的分散化問題??刂破鞅旧硗ǔ7譃橐韵聝刹糠謱崿F:處理程序和命令層次結構(見圖8)。


圖8、Front Controller結構

  處理程序具有以下兩項職責:

  1) 檢索參數。處理程序接收來自Web服務器的HTTP Post或Get請求,并從請求中檢索相關參數。

  2) 選擇命令。處理程序首先使用請求中的參數選擇正確的命令,然后將控制權轉移給該命令以便執行處理。


圖9、Front Controller的典型方案

  在前端控制器中,所有請求都通過單個(通常是兩部件)控制器來傳送??刂破鞯牡谝粋€部件是處理程序,第二個部件是Commands(命令)[Gof95]的層次。命令本身是控制器的一部分,代表控制器觸發的特定操作。在執行該操作之后,命令選擇要使用哪個視圖來呈現頁面。通常,構建的此控制器框架使用配置文件將請求映射到操作,因此,它在構建之后便于更改。當然,其缺點在于這種設計固有的復雜程度。


  遺漏之后的補充

  到目前為止,已經完整地展示了Web表示模式中MVC的實現架構,同時在此技術上對于中等復雜和高度復雜的情況提出了Page Controller和Front Controller作為優化的手段,在構建Web表示的應用程序,已經給出了相對完整的解決方案,細心的讀者是否發現,是不是還少了一點什么?在構建Web應用程序的時候,除了構建一個“良構”的系統(這表現在MVC的分離和系統的可擴展),我們還需要考慮應用程序的安全性能,因此我們選取了Interceptiing Filter和Page Cache作為Web表示模式的補充內容。

  Intercepting Filter(篩選器)

  如何圍繞Web頁面請求來實現公共的預處理和后處理步驟?這是篩選器需要解決的問題,使用此模式,您創建一串可組合的篩選器,以便在Web頁面請求期間實現公共的預處理和后處理任務。


圖10、一串可組合篩選器

  篩選器構成了一系列獨立模塊,在將頁面請求傳遞到控制器對象之前,這些模塊可以鏈接在一起以執行一組公共的處理步驟。因為各個篩選器實現的是完全相同的接口,所以它們彼此之間沒有顯式依賴性。因此,可以在不會影響現有篩選器的情況下添加新的篩選器。您甚至可以在部署時添加篩選器,方法是基于配置文件動態地對它們進行實例化。

  對各個篩選器的設計應該盡可能讓它們不對是否存在其他篩選器作出任何假設。這樣可以維護可組合性,即添加、刪除或重新排列篩選器的能力。此外,某些實現Intercepting Filter模式的框架不會保證篩選器的執行順序。如果發現多個篩選器之間存在很強的相互依賴性,最好采取調用幫助器類的常規方法,因為這樣可以保證保留篩選器序列中的約束信息。Intercepting Filter的直接實現方式是一個篩選器鏈,這個篩選器鏈可以用來遍歷一個由所有篩選器組成的列表。Web請求處理程序在將控制權傳遞到應用程序邏輯之前將首先執行篩選器鏈。


圖11、Intercepting Filter類關系圖

  當Web服務器收到頁面請求時,請求處理程序首先將控制權傳遞給FilterChain(篩選器鏈)對象。此對象維護著一個包含所有篩選器的列表,并按順序調用每個篩選器。FilterChain可以從配置文件中讀取篩選器順序,以實現部署時的可組合性。每個篩選器都有機會修改傳入請求。例如,它可以修改URL,或添加應用程序要使用的頭字段。執行完所有篩選器之后,"請求處理程序"將控制權傳遞給控制器,后者將執行應用程序功能(見圖12)。


圖12、Intercepting Filter序列關系圖

  因為在處理Web請求時通常需要有截取篩選器,所以大多數Web框架都為應用程序開發人員提供了將截取篩選器掛靠到請求-響應過程中的機制。

  Page Cache(頁面緩存)

  如果動態生成的Web頁被頻繁請求并且構建時需要耗用大量的系統資源,那么,如何才能改進這類網頁的響應時間?頁面緩存通過對從動態網頁生成的內容進行緩存來提高請求響應的吞吐量。默認情況下,在ASP.NET中支持頁面緩存,但除非定義有效的過期策略,否則,不會對來自任何給定響應的輸出進行緩存。要定義過期策略,可以使用低級OutputCache API或高級@OutputCache指令。

  頁面緩存的基本結構是相對簡單的。Web服務器維護包含預先生成的頁面的本地數據存儲(見圖13)。


圖13、頁面緩存的基本配置

  下面的序列圖闡明了頁面緩存可以改進性能的原因。第一個序列圖(見圖2)描述尚未緩存所需頁面的初始狀態(即所謂的緩存未命中)。在這種情況下,Web服務器必須訪問數據庫,并生成HTML頁,再將其存儲在緩存中,然后將它返回給客戶端瀏覽器。請注意,此過程比不進行緩存的情況稍慢,因為它執行了下列額外步驟:

  1. 確定頁面是否已緩存

  2. 將頁面轉換為HTML代碼,然后存儲在緩存中

  與數據庫訪問和HTML生成相比,其中的任一步驟都不應該花費很長時間。但是,因為在此情況下需要進行額外處理,所以您必須確保在系統完成與緩存未命中關聯的步驟之后連續多次命中緩存(如圖14所示)。


圖14、緩存未命中的序列(當頁面不在緩存中時)

  在圖15所示的緩存命中情況下,頁面已經處于緩存中。通過跳過數據庫訪問、頁面生成和頁面存儲,緩存命中節省了循環。


圖15、緩存命中的序列(頁面處于緩存中時)

  在ASP.NET中可以采用下述三種模式來實現緩存策略:

  1.將<%@ OutputCache Duration="60" VaryByParam="none" %>這樣的指令插入到要緩存的每個頁面中。指令指定刷新間隔(以秒為單位)。刷新間隔不依賴于外部事件,而且緩存不能全部刷新。

  2.Vary-By-Parameter Caching.此模式使用Absolute Expiration的變型,該變型使開發人員能夠指定影響頁面內容的參數。因此,緩存將存儲頁面的多個版本,并按參數值為這些頁面版本編制索引。

  3.Sliding Expiration Caching.此模式與Absolute Expiration(絕對過期)的類似之處是,頁面在指定的時間內是有效的。但是,在每次請求時都會重置刷新間隔。例如,您可能使用滑動過期緩存,將一個頁面緩存最長10分鐘。只要對頁面的請求是在10分鐘內發出的,就將過期時間再延長10分鐘。

  結束語

  自此,我們已經完整地介紹了Web表示集群中相關的各個模式,關于在MVC模式中Observer(觀察者)模式的用法,我們會用專門的篇幅來介紹,文字中提到的一些引用可以在http://www.microsoft.com/china/msdn/architecture/patterns/EspBiblio 找到相對詳細的列表,當然,網絡就是您最好的資源。

原文轉自:http://www.anti-gravitydesign.com

評論列表(網友評論僅供網友表達個人看法,并不表明本站同意其觀點或證實其描述)
国产97人人超碰caoprom_尤物国产在线一区手机播放_精品国产一区二区三_色天使久久综合给合久久97