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

Aspx頁面繼承自System.Web.UI.Page類。Page類對象通過繼承System.Web.UI.Control類,從而擁有了Web控件的特性,同時它還實現了IHttpHandler接口。作為ASP.NET處理HTTP Web請求的接口,提供了如下的定義:










Page類實現了ProcessRequest()方法,通過它可以設置Page對象的Request和Response屬性,從而完成對用戶請求/相應的控制。然后Page類通過從Control類繼承來的Load事件,將View與Model建立關聯,如Products.aspx.cs所示:









事件機制恰好是observer模式的實現,當ASPX頁面的Load事件被激發后,系統通過WebUtility類(在第28章中有對WebUtility類的詳細介紹)的GetCategoryName()方法,獲得Category值,并將其顯示在頁面的Title上。Page對象作為Controller,就好似一個調停者,用于協調View與Model之間的關系。
由于ASPX頁面中還可以包含Web控件,這些控件對象同樣是作為View對象,通過Page類型對象完成對它們的控制。例如在CheckOut.aspx頁面中,當用戶發出CheckOut的請求后,作為System.Web.UI.WebControls.Winzard控件類型的wzdCheckOut,會在整個向導過程結束時,觸發FinishButtonClick事件,并在該事件中調用領域對象Order的Insert()方法,如下所示:

































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













































由于CustomGrid繼承自Repeater控件,因而它同時還繼承了Repeater的DataSource屬性,這是一個虛屬性,它默認的set訪問器屬性如下:













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

















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





發表評論
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

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

評論