摘要
本文介紹了在
NET框架下應用Web設計模式改進WebForm程序設計的一些基本方法及要點
關鍵字
設計模式
ASP
NET
WebForm
MVC
Page Controller
Front Controller
Page Cache
目錄
引言
經典的WebForm架構
設計模式
MVC模式下的WebForm
Page Controller模式下的WebForm
Front Controller模式下的WebForm
Page Cache模式下的WebForm
引言
記得微軟剛剛推出ASP
NET時
給人的震撼是開發Web程序不再是編寫傳統的網頁
而像是在構造應用程序
因而微軟稱之為WebForm
但是兩年後的今天
有相當多的開發人員仍然延用寫腳本程序的思路構建一個又一個的WebForm
而沒有發揮出ASP
NET的優勢
就此本文希望通過實例能夠啟發讀者一些新的思路
由於篇幅有限
本文不可能通過一個復雜的Web應用來向讀者展示結合設計模式的WebForm
但是如果僅僅是一個小程序的確沒有使用模式的必要
為了便於理解
希望您能把它想象成是一個大型系統中的小模塊(如果代碼是大型系統的一部分那麼使用模式就變得非常重要)
在本文的末尾給出了所有源程序的下載地址
經典的WebForm架構
首先來看一個簡單的應用
數據庫設計如下圖
Portal是Subject的父表
通過portalId進行一對多關聯
程序需要根據portalId顯示不同的Subject列表
按照我們編寫WebForm一般的習慣
首先在頁面上拖放一個DropDownList
一個DataGrid
一個Button控件
界面(webForm
aspx)
〈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>
然後利用VS
NET代碼隱藏功能編寫的核心代碼如下
後置代碼(webForm
aspx
cs)
//頁面初始化事件
private void Page_Load(object sender
System
EventArgs e)
{
if ( ! IsPostBack )
{
string SQL_SELECT_PORTAL =
SELECT * FROM PORTAL
;
//使用using確保釋放數據庫連接
//連接字符串存放在Web
Config文件中便於修改
using( SqlConnection conn = new SqlConnection( ConfigurationSettings
AppSettings[
ConnectionString
] ) )
{
SqlDataAdapter dataAdapter = new SqlDataAdapter( SQL_SELECT_PORTAL
conn );
DataSet dataSet = new DataSet();
dataAdapter
Fill( dataSet );
//設置下拉列表的數據源與文本域
值域
dropDownList
DataSource = dataSet;
dropDownList
DataTextField =
portalName
;
dropDownList
DataValueField =
portalId
;
dropDownList
DataBind();
}
}
}
//Button的Click事件
private void button_Click(object sender
System
EventArgs e)
{
string SQL_SELECT_SUBJECT =
SELECT * FROM SUBJECT WHERE portalId = {
}
;
using( SqlConnection conn = new SqlConnection( ConfigurationSettings
AppSettings[
ConnectionString
] ) )
{
//用下拉列表選擇的值替換掉SQL語句中的待定字符{
}
SqlDataAdapter dataAdapter = new SqlDataAdapter( string
Format( SQL_SELECT_SUBJECT
dropDownList
SelectedValue )
conn );
DataSet dataSet = new DataSet();
dataAdapter
Fill( dataSet );
dataGrid
DataSource = dataSet;
dataGrid
DataBind();
}
}
執行結果如圖所示
程序將根據下拉列表框選擇的值綁定DataGrid
非常典型的一個WebForm架構
體現出ASP
NET事件驅動的思想
實現了界面與代碼的分離
但是仔細看看可以從中發現幾個問題
對數據庫操作的代碼重復
重復代碼是軟件開發中絕對的
壞味道
往往由於某些原因當你修改了一處代碼
卻忘記要更改另外一處相同的代碼
從而給程序留下了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(SQLHelper
cs)
封裝所有對數據庫的操作
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 = ConfigurationSettings
AppSettings[
ConnectionString
];
public static DataSet GetPortal()
{
return GetDataSet( SQL_SELECT_PORTAL );
}
public static DataSet GetSubject( string portalId )
{
return GetDataSet( string
Format( 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();
dataAdapter
Fill( dataSet );
return dataSet;
}
}
Controller(webForm
aspx
cs)
負責轉化用戶的輸入
private void Page_Load(object sender
System
EventArgs e)
{
if ( ! IsPostBack )
{
//調用Model的方法獲得數據源
dropDownList
DataSource = SQLHelper
GetPortal();
dropDownList
DataTextField =
portalName
;
dropDownList
DataValueField =
portalId
;
dropDownList
DataBind();
}
}
private void button_Click(object sender
System
EventArgs e)
{
dataGrid
DataSource = SQLHelper
GetSubject( dropDownList
SelectedValue );
dataGrid
DataBind();
}
修改後的代碼非常清晰
M
V
C各司其制
對任意模塊的改寫都不會引起其他模塊的變更
類似於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一般繼承自System
Web
UI
Page類
而Page Controller的實現思想是所有的WebForm繼承自定義頁面基類
如圖
利用自定義頁面基類
我們可以統一的接收頁面請求
提取所有相關數據
調用對Model的所有更新以及向View轉發請求
輕松實現統一的頁面風格
而由它所派生的Controller的邏輯將變得更簡單
更具體
下面看一下Page Controller的具體實現
Page Controller(BasePage
cs)
public class BasePage : System
Web
UI
Page
{
private string _title;
public string Title//頁面標題
由子類負責指定
{
get
{
return _title;
}
set
{
_title = value;
}
}
public DataSet GetPortalDataSource()
{
return SQLHelper
GetPortal();
}
public DataSet GetSubjectDataSource( string portalId )
{
return SQLHelper
GetSubject( portalId );
}
protected override void Render( HtmlTextWriter writer )
{
writer
Write(
〈html>〈head>〈title>
+ Title +
〈/title>〈/head>〈body>
);//統一的頁面頭
base
Render( writer );//子頁面的輸出
writer
Write( @
〈a
>ASP
NET〈/a>〈/body>〈/html>
);//統一的頁面尾
}
}
現在它封裝了Model的功能
實現了統一的頁面標題和頁尾
子類只須直接調用
修改後的Controller(webForm
aspx
cs)
public class webForm : BasePage//繼承頁面基類
{
private void Page_Load(object sender
System
EventArgs e)
{
Title =
Hello
World!
;//指定頁面標題
if ( ! IsPostBack )
{
dropDownList
DataSource = GetPortalDataSource();//調用基類的方法
dropDownList
DataTextField =
portalName
;
dropDownList
DataValueField =
portalId
;
dropDownList
DataBind();
}
}
private void button_Click(object sender
System
EventArgs e)
{
dataGrid
DataSource = GetSubjectDataSource( dropDownList
SelectedValue );
dataGrid
DataBind();
}
}
從上可以看出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模式的具體實現
首先在Web
Config裡定義
〈!
指定對Dummy開頭的aspx文件交由Handler處理
>
〈httpHandlers>
〈add verb=
*
path=
/WebPatterns/FrontController/Dummy*
aspx
type=
WebPatterns
FrontController
Handler
WebPatterns
/>
〈/httpHandlers>
〈!
指定名為FrontControllerMap的頁面映射塊
交由UrlMap類處理
程序將根據key找到對應的url作為最終的執行路徑
您在這可以定義多個key與url的鍵值對
>
〈configSections>
〈section name=
FrontControllerMap
type=
WebPatterns
FrontController
UrlMap
WebPatterns
>〈/section>
〈/configSections>
〈FrontControllerMap>
〈entries>
〈entry key=
/WebPatterns/FrontController/DummyWebForm
aspx
url=
/WebPatterns/FrontController/ActWebForm
aspx
/>
〈/entries>
〈/FrontControllerMap>
修改webForm
aspx
cs
private void button_Click( object sender
System
EventArgs e )
{
Response
Redirect(
DummyWebForm
aspx?requestParm=
+ dropDownList
SelectedValue );
}
當程序執行到這裡時將會根據Web
Config裡的定義觸發類Handler的ProcessRequest事件
Handler
cs
public class Handler : IHttpHandler
{
public void ProcessRequest( HttpContext context )
{
Command command = CommandFactory
Make( context
Request
Params );
command
Execute( 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
{
//獲得Web
Config中定義的key和url鍵值對
UrlMap類詳見下載包中的代碼
private UrlMap map = UrlMap
SoleInstance;
protected abstract void OnExecute( HttpContext context );
public void Execute( HttpContext context )
{
OnExecute( context );
//根據key和url鍵值對提交到具體處理的頁面
string url = String
Format(
{
}?{
}
map
Map[ context
Request
Url
AbsolutePath ]
context
Request
Url
Query );
context
Server
Transfer( url );
}
}
public class FirstPortal : RedirectCommand
{
protected override void OnExecute( HttpContext context )
{
//在輸入參數中加入項portalId以便頁面處理
context
Items[
portalId
] =
;
}
}
public class SecondPortal : RedirectCommand
{
protected override void OnExecute(HttpContext context)
{
context
Items[
portalId
] =
;
}
}
最後在ActWebForm
aspx
cs中
dataGrid
DataSource = GetSubjectDataSource( HttpContext
Current
Items[
portalId
]
ToString() );
dataGrid
DataBind();
上面的例子展示了如何通過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模式中的SQLHelper
cs
我對它進行了少許修改
public static DataSet GetPortal()
{
DataSet dataSet;
if ( HttpContext
Current
Cache[
SELECT_PORTAL_CACHE
] != null )
{
//如果數據存在於緩存中則直接取出
dataSet = ( DataSet ) HttpContext
Current
Cache[
SELECT_PORTAL_CACHE
];
}
else
{
//否則從數據庫中取出並插入到緩存中
設定絕對過期時間為
分鐘
dataSet = GetDataSet( SQL_SELECT_PORTAL );
HttpContext
Current
Cache
Insert(
SELECT_PORTAL_CACHE
dataSet
null
DateTime
Now
AddMinutes(
)
TimeSpan
Zero );
}
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