[按]
你可能會誤解本文標題:讓WEB FORM 更像WINDOWS FORM。 你也許會把本文當作是討論應用程式介面. 其實本文討論的是ASP.NET表現層設計。 也許會給你一點啟示:如何設計邏輯隔離良好的、更容易設計和管理的應用程式。作者是一個偏向於應用的開發人員,因此可能在某些方面涉論不深,希望可以與有更多想法的朋友交流。作者的電子郵件xujian(a)nwpu.edu.cn
[引言]
在項目設計和實施的過程中,我經常再想,既然我們必須給使用者以樣式美觀並且易用的使用者介面,就應該好好設計表現層的UI邏輯。或者想辦法把商務邏輯、系統邏輯、和UI邏輯隔離得更好。使介面相關元素得到最大程度的重用和最大的靈活性。
本文的核心論述對象是ASCX(ASP.NET User Controls)。這裡我們把ASCX比作WIN32應用程式的”表單(FORM)“。我們知道,設計WINDOWS應用程式的時候,不管使用什麼開發工具,使用者介面的主要構成元素是“表單”,我們預先設計好承擔不同任務的表單,然後在適當的時候把他們彈出來,任務完成後把他們關掉。這樣就基本完成了使用者介面的流程式控制制。
而ASCX是類似可嵌入到網頁中的UI單元。我們可以通過編寫HTML來開發一個ASCX。這隻是ASP.NET架構所提供的一種機制。我們可以把ASCX做為FORM,像操作WINDOWS FORMS一樣操作網頁中的ASCX,來完成我們的UI邏輯。
由於ASCX可以嵌入頁面中,這就使得最大程度重用頁面構圖提供了可能性。我們可以把ASPX頁面當作ASCX的容器。ASPX僅包含UI邏輯。更複雜的業務上的邏輯由ASCX承擔。甚至ASCX僅承載資料。(在我的實際經驗中,我總是設計兩類ASCX。一類是資料類,僅以一定格式顯示資料,不承擔商務邏輯;另外一種是事務型,用來完成某種操作。)ASPX控制其中的各個ASCX互動顯示隱藏,由此完成流程式控制制。
實現的障礙在哪裡?提供ASCX的主要目的是隱藏其中的邏輯,並且提供一定程度上的重用性。但是ASCX內部的邏輯並不能和其容器直接交流。也就是說ASCX對外界是一無所知的。承載ASCX的容器(可能是一個ASPX頁面或者另一個ASCX)可以很方便的控制他的顯示,隱藏,但是容器卻不知道什麼時候關閉他。在流程完成後,ASCX無法直接調用容器的方法是自己關掉。
關鍵性的技巧就是讓ASCX可以向外引發事件。只要ASCX需要變動自己的UI狀態,就以一定的事件通知容器。而不用管容器如何處理。而容器可以很容易捕獲控制項的事件而最初相應處理。這樣就給我們提供了一種指導性的模式。
作者的設計。一般而言, 我給表現層的ASPX和ASCX都至少抽象一個基類。基類裡包含所有ASCX都需要的內容。有安全方面的,有系統配置方面的。對於本文所論述的內容,我使所有ASCX都可以引發三種事件,分別代表ASCX生命週期的各個階段,他們是:
MissionStart:事務開始,代表控制項最初狀態。
MissionAbort:事務中止,使用者的操作取消。需要返回原介面。
MissionEnd:事務結束, 使用者的操作完成,需要進一步處理。
這三個事件都沒有任何參數,他們僅僅包含了介面邏輯,和商務邏輯沒有關係。 這樣我們可以在使用者沒有進行有結果的操作,並且需要返回原介面時引發MissionAbort事件,在使用者操作完成,需要進一步處理時引發MissionEnd事件。(MissionStart我還從來沒有應用到, 但我想預留這麼一個事件是必要的。)
[有關原始碼]
以下原始碼摘自UserControlBase.cs 該類繼承自System.System.Web.UI.UserControl
/// <summary>
/// 關鍵系統事件: 事務開始. ASCX生命週期開始.
/// </summary>
public event System.EventHandler MissionStart;
/// <summary>
/// 關鍵系統事件: 事務中止. 未進行到事件最終狀態. ASCX關閉但操作未完成.
/// </summary>
public event System.EventHandler MissionAbort;
/// <summary>
/// 關鍵系統事件: 事務完成. 既定操作完成. ASCX被關閉, 通知容器向下進行.
/// </summary>
public event System.EventHandler MissionEnd;
/// <summary>
/// 引發MissionStart事件
/// </summary>
protected void RaiseMissionStart()
{
if(this.MissionStart != null)
{
this.MissionStart(this, new System.EventArgs());
}
}
/// <summary>
/// 引發MissionAbort事件
/// </summary>
protected void RaiseMissionAbort()
{
if(this.MissionAbort != null)
{
this.MissionAbort(this, new System.EventArgs());
}
}
/// <summary>
/// 引發MissionEnd事件
/// </summary>
protected void RaiseMissionEnd()
{
if(this.MissionEnd != null)
{
this.MissionEnd(this, new System.EventArgs());
}
}
ASCX需要從此基類派生. 例如:
public class ComplexSearch : XxxXxx.YyyYyy.UserControlBase
{
...
}
上述ASCX是一個做組合查詢的UI. 有一個“取消“命令按鈕. 我們在取消按鈕的Click事件上做如下命令
This.RaiseMissionAbort();
之後容器捕獲該事件, 在private void InitializeComponent()方法中加上:
this.XxxXxxxx.MissionAbort += new EventHandler(AttributeInsert_MissionAbort);
(注意:以上代碼是+=以後的部分IDE可以自動為你完成. 並且自動產生複合編碼約定的方法名):
private void XxxXxxxx_MissionAbort(object sender, EventArgs e)
{
///...(處理過程)
}
以上僅僅是基類相關事件的定義以及應用範例. 實際情況也複雜不到哪裡去. 不過除了這些之外, 作者所開發的項目中, 幾乎每個ASCX都有著異常豐富的特性. 有著較高的可重用性/靈活性和獨立性.
[幾點與主題無關的原則]
1. 盡量抽象一些公用特性到基類
2. 盡量分離邏輯. 公開足夠的特性(Properties), 方法, 事件.
3. 設計上多用一天, 可以減少幾天的編碼及維護工作量. 並且可為後繼的工作帶來便利.
[結束]
本著拋磚引玉的想法,本文僅僅討論了給ASCX加上三個事件。想法簡單明晰,但在一定程度上反映了”架構“上的一些思想。