[ASP.NET入門隨想十] 珍珠奶茶
——伺服器控制項模型
他迷上了珍珠奶茶。說實話,以前他一直覺得珍珠奶茶挺蠢的,真不知道是那一個天才的點子,居然把粉圓和泡沫紅茶攪和在一起,還加上一根那麼滑稽的超大吸
管;妹妹剛迷上珍珠奶茶的時候,他還挺嗤之以鼻的呢。但這天以後,他幾乎天天都會去買一杯珍珠奶茶,愈喝愈覺得珍珠奶茶的風味很特別,的確滿好喝的。知道
他迷上珍珠奶茶,妹妹還笑他:“早跟你說好喝你不信,男生就是遲鈍。”
—— 管家琪《珍珠奶茶的誘惑》
■ 物件導向的粉圓 – 伺服器控制項模型
《隨想九》
中我們已經認識到,XHTML的一個重要目標就是將結構與表現分離,如:<div
id=”author”>老燕</div>;但從一個動態網站的角度來看,需要進一步將網頁結構與內容分離,即:<div
id=”author”><!—-動態資料--></div>,相比較而言,網頁結構是靜態,內容是動態。
部落格園中charly的《內容發布系統的開發》
引發我對一段往事的追憶,作者討論的命題是如何用動態資料產生靜態頁面,方法是用資料替換模板中指定標籤。引申開來就是ASP程式員非常久遠的夢想——代
碼與頁面分離,即避免出現<div
id=”author”><%=author%></div>之類HTML代碼與ASP程式碼混雜在一塊的情況,而解決
之道,用的就是charly描述的方法——Regex替換。
如果用靜態/動態這個角度去分析一個.aspx檔案,可以將其分成兩部份:一部份是靜態連續的文本,如:<html>……<
body>;另一部份是動態特殊標籤,如:<asp:TextBox id="txtName" runat="server"
/>。兩者以是否擁有屬性runat=”server”為判斷標準。ASP.NET將後一部份稱之為伺服器控制項,程式員以伺服器控制項為物件模型來定義Web應用程式的使用者介面,控制使用者互動;而前一部份在運行時也將被建立成一種特殊的控制項——LiteralControl。
如果用ASP面向過程的方法來處理所謂的伺服器控制項,就是根據使用者的需求直接產生對應的HTML代碼;而在ASP.NET中,程式員與HTML代碼被抽象的物件導向的伺服器控制項概念隔離開來。既然物件導向,伺服器控制項就應該擁有屬性(property)來描述自己的狀態;用方法(method)描述自己的動作;需要事件(event)來觸發方法,改變狀態,最後自動產生相應的HTML代碼。當然我們不需要從頭來構架這個模型,所有的伺服器控制項,包括Page類,都直接或間接繼承於System.Web.UI.Control類,而顯示為HTML表單元素的控制項,往往又繼承於System.Web.UI.WebControl類,稱為Web控制項。下例是一個簡單的自訂控制項範例,訪問該例的TestMyControls.aspx頁,查看原始碼會發現控制項對應的html代碼為“1”。
// MyControls.cs 自訂控制項集
using System;
using System.Web.UI;
namespace essay
{
public class MyFirstControl:Control //輸出控制項屬性Number的絕對值
{
private int _number;
public int Number //定義屬性
{
get {return _number;}
set {_number=value;}
}
//重寫Control.Render方法,產生控制項對應的HTML代碼
protected override void Render(HtmlTextWriter writer)
{
writer.Write(Math.Abs(Number));
}
}
}
// TestMyControls.aspx分頁檔,<%Register%>註冊自訂控制項集
// <mc: ……>在頁面增加自訂控制項並將屬性Number值設為-1
<%@ Register TagPrefix="mc" Namespace="essay" Assembly="essay" %>
<HTML><HEAD></HEAD><body>
<form runat="server">
<mc:MyFirstControl id="test1" Number="-1" runat="server" />
</form></body></HTML>
■ 葛玲是誰? – 伺服器控制項的狀態保持
若干年前有一個火腿腸廣告,對話如下:
呂麗萍:冬寶,在想啥呢?
葛 優:想葛玲
呂麗萍:別想了,我給你介紹一位新朋友——DUDU牌火腿腸
呂麗萍:(過一會兒)還想葛玲嗎?
葛 優:葛玲是誰?
人機互動設計的一個重要內容是互動工作流程,而實現互動工作流程的前提是狀態保持,否則就會出現“葛玲是誰”這樣的幽默。控制項可以利用傳統的cookies、session、隱藏控制項等方法來儲存狀態值,在《隨想八》中我們已經探討過檢視狀態(ViewState)的作用和原理,本質上,ASP.NET創造出的有狀態、連續的頁面狀態保持機制是通過頁面隱藏資料。接下來我們通過改造上例進一步研究利用檢視狀態來完成控制項狀態保持的細節。
// MyControls.cs 自訂控制項集
……
public class MyFirstControl:Control
{
private int _number;
public int Number{……}
//增加屬性NumberInViewState,用以存取屬性Number的檢視狀態值
public int NumberInViewState
{
get
{
object o = ViewState["NumberInViewState"];
return (o==null)?0 int)o;
}
set { ViewState["NumberInViewState"]=value; }
}
protected override void Render(HtmlTextWriter writer){……}
}
……
// TestMyControls.aspx分頁檔
<html>
……
<%@ Page Language="C#" %>
<script runat="server">
protected override void OnLoad(EventArgs e)
{
test1.NumberInViewState--; //檢視狀態相應值自減1
test1.Number = test1.NumberInViewState; //自訂控制項值與視圖值保持一致
base.OnLoad(e);
}
</script>
<form runat="server">
<mc:MyFirstControl id="test1" runat="server" />
<input type="submit" />
</form></body></html>
■ 換杯子還是換粉圓? – 控制項樹與伺服器控制項的生命週期
在運行期,頁面架構會在杯子裡放入指定的伺服器控制項類執行個體,當然它們不是胡亂堆積在一塊,而是組合成一顆控制項樹,圖10-2為例2的頁面控制項樹模型,我們可以通過控制項的ID或在樹中的位置控制控制項,也可以增加或刪除控制項。
在假想的有狀態、連續的頁面前提,意味著在初始請求後,頁面必須儲存每一個控制項的狀態,在回傳(PostBack)
後,首先是恢複控制項原來的狀態,再處理新的請求。也就是說,每一個用戶端對同一個頁面的連續N次請求,相當於向奶茶鋪連續要了N杯同一品種的奶茶,第一杯
珍珠奶茶中粉圓狀態是預設的,從顧客提出第二杯奶茶請求起,夥計必須先把這杯中粉圓狀態撥弄成與端給顧客時的上一杯狀態一模一樣,然後根據顧客新的事件進
行調整。10-2。詳細過程請查閱MSDN的《控制項執行生命週期》。
這種無中生有的互動工作流程實現方法是要付出代價的,作為編程模型而言,程式員可以在伺服器控制項的抽象概念上輕鬆實現,但對整個系統的效能而言,ASP.NET並未對用戶端和伺服器端做過多少負載平衡最佳化,用合金槍頭的話來說,“如果就連選擇籍貫省市的下拉框都要用伺服器控制項來提交一下,同時重新整理頁面的話,那的確很噁心”。
一個很自然的想法是為什麼對每一次請求我們都要把整個杯子換掉呢?如果僅僅是換掉幾個粉圓和一小部份奶茶,將大大改善系統效能,這就是Ajax技術的出發點。