ASP.NET編程入門隨想之信客
記得早年在鄉間,對外的通信往來主要依靠一種特殊職業的人:信客。外出謀生的人多了,少不了要帶幾封平安家信、捎一點衣物食品的,那就用得著信客了。信客要有一點文化,知道各大碼頭的情形,還要一副強健的筋骨,背得動重重的行李。信客沉重的腳步,是鄉村和城市的紐帶。
-- 余秋雨《文化苦旅·信客》
■ 一個饅頭引發的血案 - 回發與事件
基于WEB的分布式系統中,用戶往往是通過提交表單,瀏覽器產生相應的HTTP POST請求來完成交互過程,這個過程稱為回發(PostBack)。在同一個網頁中,常會有許多HTML標簽可能引起回發,申請交于服務器處理。
控件對應著客戶端的HTML標簽,有著自己的狀態和行為。用戶操作引起每一次回發,會調用頁面中一個或多個控件行為修改其狀態,也就是說,杯中的粉圓(《隨想十》中對控件的比喻)之間是有關聯的,用戶撥動其中一個,可能引起其它粉圓震動。拓展開來,當用戶操作或系統內部引發狀態改變時,類需要發送一個消息給關聯類,讓關聯類做相應的狀態調整。在.NET框架中,這個消息被稱為事件(event),發接消息的類被稱為事件源(event source),關聯類被稱為事件接收者(event sink)。回發的處理過程,實質上是事件源調用事件接收者的行為函數,稱為回調(callback)。
我們不希望在編譯時就確定回調的對象,否則這種強耦合關系就意味著每次使用時需要拎一串關聯粉圓放到杯子中。相反,我們希望到運行時再來確定回調關系,在.NET框架中,這種方式被定義成委托(delegate),我們在《隨想七》和《隨想八》已經對其有了初步的認識。事件基于發布-訂閱機制,每一個產生事件的類都有一個委托成員(發布機制),在系統初始化時,接收器或其它類需要將具體的事件處理程序綁定到委托成員(訂閱機制),運行時,系統自動完成回調。
■ 口信 -用戶操作引發的服務器端事件
"終于有婦女來給信客說悄悄話:'關照他,往后帶東西幾次并一次,不要雞零狗碎的';'你給他說說,那些貨色不能在上海存存?我一個女人家,來強盜來賊怎麼辦'……信客沉穩地點點頭。"
用戶會對客戶端瀏覽器中的頁面元素做出各種操作,瀏覽器可以通過JavaSript之類的腳本語言來捕獲這些操作并且做出相應回應,但對服務器而言,它卻常常視而不見。要產生服務器端事件,就必須在設計期讓事件源對應的表單元素引發帶有鮮明特征的回發,從而讓頁面能夠正確識別,并傳遞給控件以做相應回調,完成用戶操作到事件的映射過程。
ASP.NET用接口IPostBackEventHandler做為信客的口信,帶回遠方的消息,它包含一個方法:RaisePostBackEvent。在回傳后,頁面會在控件樹中尋找與引發回傳HTML元素的UniqueID相匹配的控件,并調用該方法,下例為依賴于用戶點擊引發事件的自定義控件范例。
■ 行李 - 回發數據引發的服務器端事件
"一次,村里一戶人家的姑娘要出嫁,姑娘的父親在上海謀生,托老信客帶來兩匹紅綢。"
除了依賴于用戶操作引發事件外,我們時常還需要根據回發的用戶數據,來修改相應控件的狀態,從而引發事件。
回發的客戶端表單數據會被集中整理到包含數據名/值集的一個System.Collections.Specialized.NameValueCollection實例中,頁面會利用UniqueID在控件樹中尋找匹配控件,如果匹配控件實現接口IpostBackDataHandler,則調用LoadPostData方法更新狀態并返回更新標識,RaisePostDataChangedEvent方法檢查標識從而引發事件。下例為依賴于狀態變化引發事件的自定義控件范例。拓展一下,可以更加靈活地使用這個事件機制,例如當用戶輸入特定數據時,也可以在此引發特定事件。
■ 眼神 - 非回發事件與完整的控件執行生命周期
"只要信客一回村,他家里總是人頭濟濟。多數都不是來收發信、物的,只是來看個熱鬧。農民的眼光里,有羨慕,有嫉妒;比較得多了,也有輕蔑,有嘲笑。這些眼神,是千年故土對城市的探詢。"
以上兩個事件皆與回發有直接關系,利用.NET的事件框架,我們可以在控件中的任何一個地方引發非回發事件。例如我們可以在頁面中加入對用戶透明的用戶行為分析處理控件,窺視其它控件狀態從而引發其特定的事件。
至此,我們已經深入了解與控件執行相關的各種要素細節,最后,通過圖11-2,我們小結一下控件完整的執行生命周期。
-- 余秋雨《文化苦旅·信客》
■ 一個饅頭引發的血案 - 回發與事件
基于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); } } } |
■ 行李 - 回發數據引發的服務器端事件
"一次,村里一戶人家的姑娘要出嫁,姑娘的父親在上海謀生,托老信客帶來兩匹紅綢。"
除了依賴于用戶操作引發事件外,我們時常還需要根據回發的用戶數據,來修改相應控件的狀態,從而引發事件。
回發的客戶端表單數據會被集中整理到包含數據名/值集的一個System.Collections.Specialized.NameValueCollection實例中,頁面會利用UniqueID在控件樹中尋找匹配控件,如果匹配控件實現接口IpostBackDataHandler,則調用LoadPostData方法更新狀態并返回更新標識,RaisePostDataChangedEvent方法檢查標識從而引發事件。下例為依賴于狀態變化引發事件的自定義控件范例。拓展一下,可以更加靈活地使用這個事件機制,例如當用戶輸入特定數據時,也可以在此引發特定事件。
using System; using System.Web; using System.Web.UI; using System.Collections.Specialized; namespace essay { public class MyTextBox: Control, IPostBackDataHandler { //定義控件屬性Text public String Text { get {return (String) ViewState["Text"];} set {ViewState["Text"] = value;} } //生成控件對應的HTML代碼 protected override void Render(HtmlTextWriter output) { writer.Write("<INPUT TYPE=text name=" + this.UniqueID + " Value='"+this.Text+"' />"); } //定義TextChanged事件委托 public event EventHandler TextChanged; //更新控件的Text狀態并返回更新標識 //參數NameValueCollection為回發數據集 public virtual bool LoadPostData(string postDataKey, NameValueCollection values) { String presentValue = Text; String postedValue = values[postDataKey]; if (!presentValue.Equals(postedValue)) { Text = postedValue; return true; } return false; } //檢查更新標識引發自定義事件TextChanged public virtual void RaisePostDataChangedEvent() {OnTextChanged(EventArgs.Empty);} //實現回調 protected virtual void OnTextChanged(EventArgs e) {if (TextChanged != null)TextChanged(this,e);} } } |
■ 眼神 - 非回發事件與完整的控件執行生命周期
"只要信客一回村,他家里總是人頭濟濟。多數都不是來收發信、物的,只是來看個熱鬧。農民的眼光里,有羨慕,有嫉妒;比較得多了,也有輕蔑,有嘲笑。這些眼神,是千年故土對城市的探詢。"
以上兩個事件皆與回發有直接關系,利用.NET的事件框架,我們可以在控件中的任何一個地方引發非回發事件。例如我們可以在頁面中加入對用戶透明的用戶行為分析處理控件,窺視其它控件狀態從而引發其特定的事件。
至此,我們已經深入了解與控件執行相關的各種要素細節,最后,通過圖11-2,我們小結一下控件完整的執行生命周期。
![]() |