記得早年在鄉間,對外的通信往來主要依靠一種特殊職業的人:信客。外出 謀生的人多了,少不了要帶幾封平安家信、捎一點衣物食品的,那就用得著信客 了。信客要有一點文化,知道各大碼頭的情形,還要一副強健的筋骨,背得動重 重的行李。信客沉重的腳步,是鄉村和城市的紐帶。
-- 余秋雨《文化苦旅·信客》
■ 一個饅頭引發的血案 - 回發與事件
基於WEB的分布式系統中,用戶往往是通過提交表單,浏覽器產生相應的HTTP POST請求來完成交互過程,這個過程稱為回發(PostBack)。在同一個網頁中,常 會有許多HTML標簽可能引起回發,申請交於服務器處理。
控件對應著客戶端的HTML標簽,有著自己的狀態和行為。用戶操作引起每一 次回發,會調用頁面中一個或多個控件行為修改其狀態,也就是說,杯中的粉圓 (《隨想十》中對控件的比喻)之間是有關聯的,用戶撥動其中一個,可能引起其 它粉圓震動。拓展開來,當用戶操作或系統內部引發狀態改變時,類需要發送一 個消息給關聯類,讓關聯類做相應的狀態調整。在.NET框架中,這個消息被稱為 事件(event),發接消息的類被稱為事件源(event source),關聯類被稱為事件 接收者(event sink)。回發的處理過程,實質上是事件源調用事件接收者的行為 函數,稱為回調(callback)。
我們不希望在編譯時就確定回調的對象,否則這種強耦合關系就意味著每次 使用時需要拎一串關聯粉圓放到杯子中。相反,我們希望到運行時再來確定回調 關系,在.NET框架中,這種方式被定義成委托(delegate),我們在《隨想七》和 《隨想八》已經對其有了初步的認識。事件基於發布-訂閱機制,每一個產生事 件的類都有一個委托成員(發布機制),在系統初始化時,接收器或其它類需要將 具體的事件處理程序綁定到委托成員(訂閱機制),運行時,系統自動完成回調。
■ 口信 -用戶操作引發的服務器端事件
"終於有婦女來給信客說悄悄話:'關照他,往後帶東西幾次並一次,不要雞 零狗碎的';'你給他說說,那些貨色不能在上海存存?我一個女人家,來強盜來 賊怎麽辦'……信客沉穩地點點頭。"
用戶會對客戶端浏覽器中的頁面元素做出各種操作,浏覽器可以通過 JavaSript之類的腳本語言來捕獲這些操作並且做出相應回應,但對服務器而言 ,它卻常常視而不見。要產生服務器端事件,就必須在設計期讓事件源對應的表 單元素引發帶有鮮明特征的回發,從而讓頁面能夠正確識別,並傳遞給控件以做 相應回調,完成用戶操作到事件的映射過程。
ASP.NET用接口IPostBackEventHandler做為信客的口信,帶回遠方的消息, 它包含一個方法:RaisePostBackEvent。在回傳後,頁面會在控件樹中尋找與引 發回傳HTML元素的UniqueID相匹配的控件,並調用該方法,下例為依賴於用戶點 擊引發事件的自定義控件范例。
// MyControls.cs 自定義控件集
using System;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace essay
{
public class myButton:WebControl,IPostBackEventHandler
{
//定義控件屬性Text
public virtual string Text
{
get
{
string s =(string)ViewState["Text"];
return (s==null)?string.Empty:s;
}
set {ViewState["Text"]=value;}
}
//生成控件對應的HTML代碼
protected override void Render(HtmlTextWriter writer)
{
writer.Write("<INPUT TYPE=submit name=" + this.UniqueID + " Value='"+this.Text+"' />");
}
//定義Click事件委托
public event EventHandler Click;
//把客戶端提交映射到自定義的Click事件
void IPostBackEventHandler.RaisePostBackEvent(string eventArgument)
{ OnClick(EventArgs.Empty); }
//實現回調
protected virtual void OnClick(EventArgs e)
{ if(Click!=null)Click(this,e); }
}
}