表示層(Presentation Layer)的設(shè)計(jì)可以給系統(tǒng)客戶最直接的體驗(yàn)和最十足的信心。正如人與人的相交相識(shí)一樣,初次見(jiàn)面的感覺(jué)總是永難忘懷的。一件交付給客戶使用的產(chǎn)品,如果在用戶界面(User Interface,UI)上缺乏吸引人的特色,界面不友好,操作不夠體貼,即使這件產(chǎn)品性能非常優(yōu)異,架構(gòu)設(shè)計(jì)合理,業(yè)務(wù)邏輯都滿足了客戶的需求,卻仍然難以討得客戶的歡心。俗語(yǔ)云:“佛要金裝,人要衣裝”,特別是對(duì)于Web應(yīng)用程序而言,Web網(wǎng)頁(yè)就好比人的衣裝,代表著整個(gè)系統(tǒng)的身份與臉面,是招徠“顧客”的最大賣點(diǎn)。
“獻(xiàn)丑不如藏拙”,作為藝術(shù)細(xì)胞缺乏的我,并不打算在用戶界面的美術(shù)設(shè)計(jì)上大做文章,是以本書略過(guò)不提。本章所關(guān)注的表示層設(shè)計(jì),還是以架構(gòu)設(shè)計(jì)的角度,闡述在表示層設(shè)計(jì)中對(duì)模式的應(yīng)用,ASP.NET控件的設(shè)計(jì)與運(yùn)用,同時(shí)還包括了對(duì)ASP.NET 2.0新特色的介紹。
6.1 MVC模式
表示層設(shè)計(jì)中最重要的模式是MVC(Model-View-Controller,即模型-視圖-控制器)模式。MVC模式最早是由SmallTalk語(yǔ)言研究團(tuán)提出的,被廣泛應(yīng)用在用戶交互應(yīng)用程序中。Controller根據(jù)用戶請(qǐng)求(Response)修改Model的屬性,此時(shí)Event(事件)被觸發(fā),所有依賴于Model的View對(duì)象會(huì)自動(dòng)更新,并基于Model對(duì)象產(chǎn)生一個(gè)響應(yīng)(Response)信息,返回給Controller。Martin Fowler在《企業(yè)應(yīng)用架構(gòu)模式》一書中,展示了MVC模式應(yīng)用的全過(guò)程,如圖6-1所示:
圖6-1 典型的MVC模式
如果將MVC模式拆解為三個(gè)獨(dú)立的部分:Model、View、Controller,我們可以通過(guò)GOF設(shè)計(jì)模式來(lái)實(shí)現(xiàn)和管理它們之間的關(guān)系。在體系架構(gòu)設(shè)計(jì)中,業(yè)務(wù)邏輯層的領(lǐng)域?qū)ο笠约皵?shù)據(jù)訪問(wèn)層的數(shù)據(jù)值對(duì)象都屬于MVC模式的Model對(duì)象。如果要管理Model與View之間的關(guān)系,可以利用Observer模式,View作為觀察者,一旦Model的屬性值發(fā)生變化,就會(huì)通知View基于Model的值進(jìn)行更新。而Controller作為控制用戶請(qǐng)求/響應(yīng)的對(duì)象,則可以利用Mediator模式,專門負(fù)責(zé)請(qǐng)求/響應(yīng)任務(wù)之間的調(diào)節(jié)。而對(duì)于View本身,在面向組件設(shè)計(jì)思想的基礎(chǔ)上,我們通常將它設(shè)計(jì)為組件或者控件,這些組件或者控件根據(jù)自身特性的不同,共同組成一種類似于遞歸組合的對(duì)象結(jié)構(gòu),因而我們可以利用Composite模式來(lái)設(shè)計(jì)View對(duì)象。
然而在.NET平臺(tái)下,我們并不需要自己去實(shí)現(xiàn)MVC模式。對(duì)于View對(duì)象而言,ASP.NET已經(jīng)提供了常用的Web控件,我們也可以通過(guò)繼承System.Web.UI.UserControl,自定義用戶控件,并利用ASPX頁(yè)面組合Web控件來(lái)實(shí)現(xiàn)視圖。ASP.NET定義了System.Web.UI.Page類,它相當(dāng)于MVC模式的Controller對(duì)象,可以處理用戶的請(qǐng)求。由于利用了codebehind技術(shù),使得用戶界面的顯示與UI實(shí)現(xiàn)邏輯完全分離,也即是說(shuō),View對(duì)象與Controller對(duì)象成為相對(duì)獨(dú)立的兩部分,從而有利于代碼的重用性。比較ASP而言,這種編程方式更符合開(kāi)發(fā)人員的編程習(xí)慣,同時(shí)有利于開(kāi)發(fā)人員與UI設(shè)計(jì)人員的分工與協(xié)作。至于Model對(duì)象,則為業(yè)務(wù)邏輯層的領(lǐng)域?qū)ο蟆4送猓?NET平臺(tái)通過(guò)ADO.NET提供了DataSet對(duì)象,便于與Web控件的數(shù)據(jù)源綁定。
6.2 Page Controller模式的應(yīng)用
通觀PetShop的表示層設(shè)計(jì),充分利用了ASP.NET的技術(shù)特點(diǎn),通過(guò)Web頁(yè)面與用戶控件控制和展現(xiàn)視圖,并利用codebehind技術(shù)將業(yè)務(wù)邏輯層的領(lǐng)域?qū)ο蠹尤氲奖硎緦訉?shí)現(xiàn)邏輯中,一個(gè)典型的Page Controller模式呼之欲出。
Page Controller模式是Martin Fowler在《企業(yè)應(yīng)用架構(gòu)模式》中最重要的表示層模式之一。在.NET平臺(tái)下,Page Controller模式的實(shí)現(xiàn)非常簡(jiǎn)單,以Products.aspx頁(yè)面為例。首先在aspx頁(yè)面中,進(jìn)行如下的設(shè)置:

Aspx頁(yè)面繼承自System.Web.UI.Page類。Page類對(duì)象通過(guò)繼承System.Web.UI.Control類,從而擁有了Web控件的特性,同時(shí)它還實(shí)現(xiàn)了IHttpHandler接口。作為ASP.NET處理HTTP Web請(qǐng)求的接口,提供了如下的定義:










Page類實(shí)現(xiàn)了ProcessRequest()方法,通過(guò)它可以設(shè)置Page對(duì)象的Request和Response屬性,從而完成對(duì)用戶請(qǐng)求/相應(yīng)的控制。然后Page類通過(guò)從Control類繼承來(lái)的Load事件,將View與Model建立關(guān)聯(lián),如Products.aspx.cs所示:









事件機(jī)制恰好是observer模式的實(shí)現(xiàn),當(dāng)ASPX頁(yè)面的Load事件被激發(fā)后,系統(tǒng)通過(guò)WebUtility類(在第28章中有對(duì)WebUtility類的詳細(xì)介紹)的GetCategoryName()方法,獲得Category值,并將其顯示在頁(yè)面的Title上。Page對(duì)象作為Controller,就好似一個(gè)調(diào)停者,用于協(xié)調(diào)View與Model之間的關(guān)系。
由于ASPX頁(yè)面中還可以包含Web控件,這些控件對(duì)象同樣是作為View對(duì)象,通過(guò)Page類型對(duì)象完成對(duì)它們的控制。例如在CheckOut.aspx頁(yè)面中,當(dāng)用戶發(fā)出CheckOut的請(qǐng)求后,作為System.Web.UI.WebControls.Winzard控件類型的wzdCheckOut,會(huì)在整個(gè)向?qū)н^(guò)程結(jié)束時(shí),觸發(fā)FinishButtonClick事件,并在該事件中調(diào)用領(lǐng)域?qū)ο驩rder的Insert()方法,如下所示:

































在上面的一段代碼中,非常典型地表達(dá)了Model與View之間的關(guān)系。它通過(guò)獲取控件的屬性值,作為參數(shù)值傳遞給數(shù)據(jù)值對(duì)象OrderInfo,從而利用頁(yè)面上產(chǎn)生的訂單信息創(chuàng)建訂單對(duì)象,然后再調(diào)用領(lǐng)域?qū)ο驩rder的Inser()方法將OrderInfo對(duì)象插入到數(shù)據(jù)表中。此外,它還對(duì)領(lǐng)域?qū)ο骃hoppingCart的數(shù)據(jù)項(xiàng)作出判斷,如果其值等于0,就在頁(yè)面中顯示UI提示信息。此時(shí),View的內(nèi)容決定了Model的值,而Model值反過(guò)來(lái)又決定了View的顯示內(nèi)容。
6.3 ASP.NET控件
ASP.NET控件是View對(duì)象最重要的組成部分,它充分利用了面向?qū)ο蟮脑O(shè)計(jì)思想,通過(guò)封裝與繼承構(gòu)建一個(gè)個(gè)控件對(duì)象,使得用戶在開(kāi)發(fā)Web頁(yè)面時(shí),能夠重用這些控件,甚至自定義自己的控件。在第8章中,我已經(jīng)介紹了.NET Framework中控件的設(shè)計(jì)思想,通過(guò)引入一種“復(fù)合方式”的Composite模式實(shí)現(xiàn)了控件樹(shù)。在ASP.NET控件中,System.Web.UI.Control就是這棵控件樹(shù)的根,它定義了所有ASP.NET控件共有的屬性、方法和事件,并負(fù)責(zé)管理和控制控件的整個(gè)執(zhí)行生命周期。
Control基類并沒(méi)有包含UI的特定功能,如果需要提供與UI相關(guān)的方法屬性,就需要從System.Web.UI.WebControls.WebControl類派生。該類實(shí)際上也是Control類的子類,但它附加了諸如ForeColor、BackColor、Font等屬性。
除此之外,還有一個(gè)重要的類是System.Web.UI.UserControl,即用戶控件類,它同樣是Control類的子類。我們可以自定義一些用戶控件派生自UserControl,在Visual Studio的Design環(huán)境下,我們可以通過(guò)拖動(dòng)控件的方式將多種類型的控件組合成一個(gè)自定義用戶控件,也可以在codebehind方式下,為自定義用戶控件類添加新的屬性和方法。
整個(gè)ASP.NET控件類的層次結(jié)構(gòu)如圖6-2所示:
圖6-2 ASP.NET控件類的層次結(jié)構(gòu)
ASP.NET控件的執(zhí)行生命周期如表6-1所示:
階段
|
控件需要執(zhí)行的操作
|
要重寫的方法或事件
|
初始化
|
初始化在傳入
Web
請(qǐng)求生命周期內(nèi)所需的設(shè)置。
|
Init
事件(
OnInit
方法)
|
加載視圖狀態(tài)
|
在此階段結(jié)束時(shí),就會(huì)自動(dòng)填充控件的
ViewState
屬性,控件可以重寫
LoadViewState
方法的默認(rèn)實(shí)現(xiàn),以自定義狀態(tài)還原。
|
LoadViewState
方法
|
處理回發(fā)數(shù)據(jù)
|
處理傳入窗體數(shù)據(jù),并相應(yīng)地更新屬性。
注意:只有處理回發(fā)數(shù)據(jù)的控件參與此階段。 |
LoadPostData
方法(如果已實(shí)現(xiàn)
IPostBackDataHandler
)
|
加載
|
執(zhí)行所有請(qǐng)求共有的操作,如設(shè)置數(shù)據(jù)庫(kù)查詢。此時(shí),樹(shù)中的服務(wù)器控件已創(chuàng)建并初始化、狀態(tài)已還原并且窗體控件反映了客戶端的數(shù)據(jù)。
|
Load
事件(
OnLoad
方法)
|
發(fā)送回發(fā)更改通知
|
引發(fā)更改事件以響應(yīng)當(dāng)前和以前回發(fā)之間的狀態(tài)更改。
注意:只有引發(fā)回發(fā)更改事件的控件參與此階段。 |
RaisePostDataChangedEvent
方法(如果已實(shí)現(xiàn)
IPostBackDataHandler
)
|
處理回發(fā)事件
|
處理引起回發(fā)的客戶端事件,并在服務(wù)器上引發(fā)相應(yīng)的事件。
注意:只有處理回發(fā)事件的控件參與此階段。 |
RaisePostBackEvent
方法(如果已實(shí)現(xiàn)
IPostBackEventHandler
)
|
預(yù)呈現(xiàn)
|
在呈現(xiàn)輸出之前執(zhí)行任何更新。可以保存在預(yù)呈現(xiàn)階段對(duì)控件狀態(tài)所做的更改,而在呈現(xiàn)階段所對(duì)的更改則會(huì)丟失。
|
PreRender
事件
(
OnPreRender
方法
)
|
保存狀態(tài)
|
在此階段后,自動(dòng)將控件的
ViewState
屬性保持到字符串對(duì)象中。此字符串對(duì)象被發(fā)送到客戶端并作為隱藏變量發(fā)送回來(lái)。為了提高效率,控件可以重寫
SaveViewState
方法以修改
ViewState
屬性。
|
SaveViewState
方法
|
呈現(xiàn)
|
生成呈現(xiàn)給客戶端的輸出。
|
Render
方法
|
處置
|
執(zhí)行銷毀控件前的所有最終清理操作。在此階段必須釋放對(duì)昂貴資源的引用,如數(shù)據(jù)庫(kù)鏈接。
|
Dispose
方法
|
卸載
|
執(zhí)行銷毀控件前的所有最終清理操作。控件作者通常在
Dispose
中執(zhí)行清除,而不處理此事件。
|
UnLoad
事件(
On UnLoad
方法)
|
表6-1 ASP.NET控件的執(zhí)行生命周期
在這里,控件設(shè)計(jì)利用了Template Method模式,Control基類提供了大部分protected虛方法,留待其子類改寫其方法。以PetShop 4.0為例,就定義了兩個(gè)ASP.NET控件,它們都屬于System.Web.UI.WebControls.WebControl的子類。其中,CustomList控件派生自System.Web.UI.WebControls.DataList,CustomGrid控件則派生自System.Web.UI.WebControls.Repeater。
由于這兩個(gè)控件都改變了其父類控件的呈現(xiàn)方式,故而,我們可以通過(guò)重寫父類的Render虛方法,完成控件的自定義。例如CustomGrid控件:













































由于CustomGrid繼承自Repeater控件,因而它同時(shí)還繼承了Repeater的DataSource屬性,這是一個(gè)虛屬性,它默認(rèn)的set訪問(wèn)器屬性如下:













對(duì)于CustomGrid而言,DataSource屬性有著不同的設(shè)置行為,因而在定義CustomGrid控件的時(shí)候,需要改寫DataSource虛屬性,如下所示:

















當(dāng)設(shè)置的value對(duì)象值不為IList類型時(shí),set訪問(wèn)器就將捕獲異常,然后將dataSource字段設(shè)置為null。
由于我們改寫了DataSource屬性,因而改寫Repeater類的OnDataBinding()方法也就勢(shì)在必行。此外,CustomGrid還提供了分頁(yè)的功能,我們也需要實(shí)現(xiàn)分頁(yè)的相關(guān)操作。與DataSource屬性不同,Repeater類的OnDataBinding()方法實(shí)際上是繼承和改寫了Control基類的OnDataBinding()虛方法,而我們又在此基礎(chǔ)上改寫了Repeater類的OnDataBinding()方法:





- 2007-08-31 16:03
- 瀏覽 183
- 評(píng)論(0)
- 相關(guān)推薦
發(fā)表評(píng)論
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元

評(píng)論