熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

針對Web設計模式的領悟

2013-11-13 09:44:50  來源: .NET編程 
摘要

本文介紹了在NET框架下應用Web設計模式改進WebForm程序設計的一些基本方法及要點

關鍵字

設計模式ASPNETWebFormMVCPage ControllerFront ControllerPage Cache

目錄

引言
經典的WebForm架構
設計模式
MVC模式下的WebForm
Page Controller模式下的WebForm
Front Controller模式下的WebForm
Page Cache模式下的WebForm

引言

記得微軟剛剛推出ASPNET時給人的震撼是開發Web程序不再是編寫傳統的網頁而像是在構造應用程序因而微軟稱之為WebForm但是兩年後的今天有相當多的開發人員仍然延用寫腳本程序的思路構建一個又一個的WebForm而沒有發揮出ASPNET的優勢就此本文希望通過實例能夠啟發讀者一些新的思路
由於篇幅有限本文不可能通過一個復雜的Web應用來向讀者展示結合設計模式的WebForm但是如果僅僅是一個小程序的確沒有使用模式的必要為了便於理解希望您能把它想象成是一個大型系統中的小模塊(如果代碼是大型系統的一部分那麼使用模式就變得非常重要)
在本文的末尾給出了所有源程序的下載地址
經典的WebForm架構
首先來看一個簡單的應用數據庫設計如下圖Portal是Subject的父表通過portalId進行一對多關聯程序需要根據portalId顯示不同的Subject列表

按照我們編寫WebForm一般的習慣首先在頁面上拖放一個DropDownList一個DataGrid一個Button控件
界面(webFormaspx)
〈form id=webForm method=post runat=server>
〈asp:DropDownList id=dropDownList runat=server>〈/asp:DropDownList>
〈asp:Button id=button runat=server Text=Button>〈/asp:Button>
〈asp:DataGrid id=dataGrid runat=server>〈/asp:DataGrid>
〈/form>

然後利用VSNET代碼隱藏功能編寫的核心代碼如下
後置代碼(webFormaspxcs)
//頁面初始化事件
private void Page_Load(object sender SystemEventArgs e)
{
if ( ! IsPostBack )
{
string SQL_SELECT_PORTAL = SELECT * FROM PORTAL;
//使用using確保釋放數據庫連接
//連接字符串存放在WebConfig文件中便於修改
using( SqlConnection conn = new SqlConnection( ConfigurationSettingsAppSettings[ConnectionString] ) )
{
SqlDataAdapter dataAdapter = new SqlDataAdapter( SQL_SELECT_PORTAL conn );
DataSet dataSet = new DataSet();
dataAdapterFill( dataSet );
//設置下拉列表的數據源與文本域值域
dropDownListDataSource = dataSet;
dropDownListDataTextField = portalName;
dropDownListDataValueField = portalId;
dropDownListDataBind();
}
}
}
//Button的Click事件
private void button_Click(object sender SystemEventArgs e)
{
string SQL_SELECT_SUBJECT = SELECT * FROM SUBJECT WHERE portalId = {};
using( SqlConnection conn = new SqlConnection( ConfigurationSettingsAppSettings[ConnectionString] ) )
{
//用下拉列表選擇的值替換掉SQL語句中的待定字符{}
SqlDataAdapter dataAdapter = new SqlDataAdapter( stringFormat( SQL_SELECT_SUBJECT dropDownListSelectedValue ) conn );
DataSet dataSet = new DataSet();
dataAdapterFill( dataSet );
dataGridDataSource = dataSet;
dataGridDataBind();
}
}

執行結果如圖所示程序將根據下拉列表框選擇的值綁定DataGrid非常典型的一個WebForm架構體現出ASPNET事件驅動的思想實現了界面與代碼的分離但是仔細看看可以從中發現幾個問題

對數據庫操作的代碼重復重復代碼是軟件開發中絕對的壞味道往往由於某些原因當你修改了一處代碼卻忘記要更改另外一處相同的代碼從而給程序留下了Bug的隱患

後置代碼完全依賴於界面在WebForm下界面的變化遠遠大於數據存儲結構和訪問的變化當界面改變時您將不得不修改代碼以適應新的頁面有可能將會重寫整個後置代碼

後置代碼不僅處理用戶的輸入而且還負責了數據的處理如果需求發生變更比如需要改變數據的處理方式那麼你將幾乎重寫整個後置代碼
一個優秀的設計需要每一個模塊每一種方法只專注於做一件事這樣的結構才清晰易修改畢竟項目的需求總是在不斷變更的唯一不變的就是變化本身好的程序一定要為變化作出准備避免牽一發而動全身所以一定要想辦法解決上述問題下面讓我們來看看設計模式

設計模式

設計模式描述了一個不斷重復出現的問題以及對該問題的核心解決方案它是成功的構架設計及實施方案是經驗的總結設計模式的概念最早來自於西方建築學但最成功的案例首推中國古代的三十六計

MVC模式下的WebForm

MVC模式是一個用於將用戶界面邏輯與業務邏輯分離開來的基礎設計模式它將數據處理界面以及用戶的行為控制分為Model-View-Controller
Model負責當前應用的數據獲取與變更及相關的業務邏輯
View負責顯示信息
Controller負責收集轉化用戶的輸入

View和Controller都依賴於Model但是Model既不依賴於View也不依賴於Controller這是分離的主要優點之一這樣Model可以單獨的建立和測試以便於代碼復用View和Controller只需要Model提供數據它們不會知道也不會關心數據是存儲在SQL Server還是Oracle數據庫中或者別的什麼地方
根據MVC模式的思想可以將上面例子的後置代碼拆分為Model和Controller用專門的一個類來處理數據後置代碼作為Controller僅僅負責轉化用戶的輸入修改後的代碼為
Model(SQLHelpercs)封裝所有對數據庫的操作
private static string SQL_SELECT_PORTAL = SELECT * FROM PORTAL;
private static string SQL_SELECT_SUBJECT = SELECT * FROM SUBJECT WHERE portalId = {};
private static string SQL_CONNECTION_STRING = ConfigurationSettingsAppSettings[ConnectionString];
public static DataSet GetPortal()
{
return GetDataSet( SQL_SELECT_PORTAL );
}
public static DataSet GetSubject( string portalId )
{
return GetDataSet( stringFormat( SQL_SELECT_SUBJECT portalId ) );
}
public static DataSet GetDataSet( string sql )
{
using( SqlConnection conn = new SqlConnection( SQL_CONNECTION_STRING ) )
{
SqlDataAdapter dataAdapter = new SqlDataAdapter( sql conn );
DataSet dataSet = new DataSet();
dataAdapterFill( dataSet );
return dataSet;
}
}

Controller(webFormaspxcs)負責轉化用戶的輸入
private void Page_Load(object sender SystemEventArgs e)
{
if ( ! IsPostBack )
{
//調用Model的方法獲得數據源
dropDownListDataSource = SQLHelperGetPortal();
dropDownListDataTextField = portalName;
dropDownListDataValueField = portalId;
dropDownListDataBind();
}
}
private void button_Click(object sender SystemEventArgs e)
{
dataGridDataSource = SQLHelperGetSubject( dropDownListSelectedValue );
dataGridDataBind();
}

修改後的代碼非常清晰MVC各司其制對任意模塊的改寫都不會引起其他模塊的變更類似於MFC中Doc/View結構但是如果相同結構的程序很多而我們又需要做一些統一的控制如用戶身份的判斷統一的界面風格等或者您還希望Controller與Model分離的更徹底在Controller中不涉及到Model層的代碼此時僅僅靠MVC模式就顯得有點力不從心那麼就請看看下面的Page Controller模式

Page Controller模式下的WebForm

MVC 模式主要關注Model與View之間的分離而對於Controller的關注較少(在上面的MVC模式中我們僅僅只把Model和Controller分離開並未對Controller進行更多的處理)但在基於WebForm的應用程序中View和Controller本來就是分隔的(顯示是在客戶端浏覽器中進行)而Controller是服務器端應用程序同時不同用戶操作可能會導致不同的Controller策略應用程序必須根據上一頁面以及用戶觸發的事件來執行不同的操作還有大多數WebForm都需要統一的界面風格如果不對此處理將可能產生重復代碼因此有必要對Controller進行更為仔細的劃分
Page Controller模式在MVC模式的基礎上使用一個公共的頁基類來統一處理諸如Http請求界面風格等如圖

傳統的WebForm一般繼承自SystemWebUIPage類而Page Controller的實現思想是所有的WebForm繼承自定義頁面基類如圖

利用自定義頁面基類我們可以統一的接收頁面請求提取所有相關數據調用對Model的所有更新以及向View轉發請求輕松實現統一的頁面風格而由它所派生的Controller的邏輯將變得更簡單更具體
下面看一下Page Controller的具體實現
Page Controller(BasePagecs)
public class BasePage : SystemWebUIPage
{
private string _title;
public string Title//頁面標題由子類負責指定
{
get
{
return _title;
}
set
{
_title = value;
}
}
public DataSet GetPortalDataSource()
{
return SQLHelperGetPortal();
}
public DataSet GetSubjectDataSource( string portalId )
{
return SQLHelperGetSubject( portalId );
}
protected override void Render( HtmlTextWriter writer )
{
writerWrite( 〈html>〈head>〈title> + Title + 〈/title>〈/head>〈body> );//統一的頁面頭
baseRender( writer );//子頁面的輸出
writerWrite( @〈a >ASPNET〈/a>〈/body>〈/html> );//統一的頁面尾
}
}

現在它封裝了Model的功能實現了統一的頁面標題和頁尾子類只須直接調用
修改後的Controller(webFormaspxcs)
public class webForm : BasePage//繼承頁面基類
{
private void Page_Load(object sender SystemEventArgs e)
{
Title = Hello World!;//指定頁面標題
if ( ! IsPostBack )
{
dropDownListDataSource = GetPortalDataSource();//調用基類的方法
dropDownListDataTextField = portalName;
dropDownListDataValueField = portalId;
dropDownListDataBind();
}
}
private void button_Click(object sender SystemEventArgs e)
{
dataGridDataSource = GetSubjectDataSource( dropDownListSelectedValue );
dataGridDataBind();
}
}

從上可以看出BagePage Controller接管了大部分原來Controller的工作使Controller變得更簡單更容易修改(為了便於講解我沒有把控件放在BasePage中但是您完全可以那樣做)但是隨著應用復雜度的上升用戶需求的變化我們很容易會將不同的頁面類型分組成不同的基類造成過深的繼承樹又例如對於一個購物車程序需要預定義好頁面路徑對於向導程序來說路徑是動態的(事先並不知道用戶的選擇)
面對以上這些應用來說僅僅使用Page Controller還是不夠的接下來再看看Front Controller模式

Front Controller模式下的WebForm

Page Controller的實現需要在基類中為頁面的公共部分創建代碼但是隨著時間的推移需求會發生較大的改變有時不得不增加非公用的代碼這樣基類就會不斷增大您可能會創建更深的繼承層次結構以刪除條件邏輯這樣一來我們很難對它進行重構因此需要更進一步對Page Controller進行研究
Front Controller通過對所有請求的控制並傳輸解決了在Page Controller中存在的分散化處理的問題它分為Handler和Command樹兩個部分Handler處理所有公共的邏輯接收HTTP Post或Get請求以及相關的參數並根據輸入的參數選擇正確的命令對象然後將控制權傳遞到Command對象由其完成後面的操作在這裡我們將使用到Command模式
Command模式通過將請求本身變成一個對象可向未指定的應用對象提出請求這個對象可被存儲並像其他的對象一樣被傳遞此模式的關鍵是一個抽象的Command類它定義了一個執行操作的接口最簡單的形式是一個抽象的Execute操作具體的Command子類將接收者作為其一個實例變量並實現Execute操作指定接收者采取的動作而接收者具有執行該請求所需的具體信息

因為Front Controller模式要比上面兩個模式復雜一些我們再來看看例子的類圖

關於Handler的原理請查閱MSDN在這就不多講了我們來看看Front Controller模式的具體實現

首先在WebConfig裡定義

〈! 指定對Dummy開頭的aspx文件交由Handler處理 >
〈httpHandlers>
〈add verb=* path=/WebPatterns/FrontController/Dummy*aspx type=WebPatternsFrontControllerHandlerWebPatterns/>
〈/httpHandlers>
〈! 指定名為FrontControllerMap的頁面映射塊交由UrlMap類處理程序將根據key找到對應的url作為最終的執行路徑您在這可以定義多個key與url的鍵值對 >
〈configSections>
〈section name=FrontControllerMap type=WebPatternsFrontControllerUrlMap WebPatterns>〈/section>
〈/configSections>
〈FrontControllerMap>
〈entries>
〈entry key=/WebPatterns/FrontController/DummyWebFormaspx url=/WebPatterns/FrontController/ActWebFormaspx />

〈/entries>
〈/FrontControllerMap>

修改webFormaspxcs
private void button_Click( object sender SystemEventArgs e )
{
ResponseRedirect( DummyWebFormaspx?requestParm= + dropDownListSelectedValue );
}
當程序執行到這裡時將會根據WebConfig裡的定義觸發類Handler的ProcessRequest事件
Handlercs
public class Handler : IHttpHandler
{
public void ProcessRequest( HttpContext context )
{
Command command = CommandFactoryMake( contextRequestParams );
commandExecute( context );
}
public bool IsReusable
{
get
{
return true;
}
}
}

而它又會調用類CommandFactory的Make方法來處理接收到的參數並返回一個Command對象緊接著它又會調用該Command對象的Execute方法把處理後參數提交到具體處理的頁面
public class CommandFactory
{
public static Command Make( NameValueCollection parms )
{
string requestParm = parms[requestParm];
Command command = null;
//根據輸入參數得到不同的Command對象
switch ( requestParm )
{
case :
command = new FirstPortal();
break;
case :
command = new SecondPortal();
break;
default :
command = new FirstPortal();
break;
}
return command;
}
}
public interface Command
{
void Execute( HttpContext context );
}
public abstract class RedirectCommand : Command
{
//獲得WebConfig中定義的key和url鍵值對UrlMap類詳見下載包中的代碼
private UrlMap map = UrlMapSoleInstance;
protected abstract void OnExecute( HttpContext context );
public void Execute( HttpContext context )
{
OnExecute( context );
//根據key和url鍵值對提交到具體處理的頁面
string url = StringFormat( {}?{} mapMap[ contextRequestUrlAbsolutePath ] contextRequestUrlQuery );
contextServerTransfer( url );
}
}
public class FirstPortal : RedirectCommand
{
protected override void OnExecute( HttpContext context )
{
//在輸入參數中加入項portalId以便頁面處理
contextItems[portalId] = ;
}
}
public class SecondPortal : RedirectCommand
{
protected override void OnExecute(HttpContext context)
{
contextItems[portalId] = ;
}
}
最後在ActWebFormaspxcs中
dataGridDataSource = GetSubjectDataSource( HttpContextCurrentItems[portalId]ToString() );
dataGridDataBind();

上面的例子展示了如何通過Front Controller集中和處理所有的請求它使用CommandFactory來確定要執行的具體操作無論執行什麼方法和對象Handler只調用Command對象的Execute方法您可以在不修改 Handler的情況下添加額外的命令它允許讓用戶看不到實際的頁面當用戶輸入一個URL時然後系統將根據nfig文件將它映射到特定的URL這可以讓程序員有更大的靈活性還可以獲得Page Controller實現中所沒有的一個間接操作層
對於相當復雜的Web應用我們才會采用Front Controller模式它通常需要將頁面內置的Controller替換為自定義的Handler在Front Controllrer模式下我們甚至可以不需要頁面不過由於它本身實現比較復雜可能會給業務邏輯的實現帶來一些困擾
以上兩個Controller模式都是處理比較復雜的WebForm應用相對於直接處理用戶輸入的應用來講復雜度大大提高性能也必然有所降低為此我們最後來看一個可以大幅度提高程序性能的模式Page Cache模式

Page Cache模式下的WebForm

幾乎所有的WebForm面臨的都是訪問很頻繁改動卻很少的應用對WebForm的訪問者來說有相當多的內容是重復的因此我們可以試著把WebForm或者某些相同的內容保存在服務器內存中一段時間以加快程序的響應速度
這個模式實現起來很簡單只需在頁面上加入
〈%@ OutputCache Duration= VaryByParam=none %>
這表示該頁面會在秒以後過期也就是說在這秒以內所有的來訪者看到該頁面的內容都是一樣的但是響應速度大大提高就象靜態的HTML頁面一樣
也許您只是想保存部分的內容而不是想保存整個頁面那麼我們回到MVC模式中的SQLHelpercs我對它進行了少許修改
public static DataSet GetPortal()
{
DataSet dataSet;
if ( HttpContextCurrentCache[SELECT_PORTAL_CACHE] != null )
{
//如果數據存在於緩存中則直接取出
dataSet = ( DataSet ) HttpContextCurrentCache[SELECT_PORTAL_CACHE];
}
else
{
//否則從數據庫中取出並插入到緩存中設定絕對過期時間為分鐘
dataSet = GetDataSet( SQL_SELECT_PORTAL );
HttpContextCurrentCacheInsert( SELECT_PORTAL_CACHE dataSet null DateTimeNowAddMinutes( ) TimeSpanZero );
}
return dataSet;
}

在這裡把SELECT_PORTAL_CACHE作為Cache的鍵把GetDataSet( SQL_SELECT_PORTAL )取出的內容作為Cache的值這樣除了程序第次調用時會進行數據庫操作外在Cache過期時間內都不會進行數據庫操作同樣大大提高了程序的響應能力
小結
自從NET框架引入設計模式以後在很大程度上提高了其在企業級應用方面的實力可以毫不誇張的說在企業級應用方面NET已經趕上了Java的步伐並大有後來居上之勢本文通過一個實例的講解向讀者展示了在NET框架下實現Web設計模式所需的一些基本知識希望能起到一點拋磚引玉的作用

From:http://tw.wingwit.com/Article/program/net/201311/11479.html
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.