簡介
Microsoft的 ASP.NET 技術提供了一個物件導向、事件驅動的編程模型,並將其與已編譯代碼的優勢結合起來。但其伺服器側的處理模型仍存在技術本身所固有的幾點不足,其中很多可以通過Microsoft ASP.NET 3.5 AJAX Extensions的新特性得到解決。這些擴充提供了大量新的用戶端特性,包括進行頁面局部呈現而無需重新整理整個頁面、通過用戶端指令碼(包括ASP.NET 設定檔 API)訪問 Web 服務、以及功能廣泛的用戶端側API,用於映射 ASP.NET 伺服器側控制項集中的許多控制項方案。
本白皮書將深入介紹 ASP.NET AJAX UpdatePanel 組件的 XML 觸發器功能。通過 XML 觸發器,可以精確控制能提供特定UpdatePanel 控制項的局部顯示功能的組件。
本白皮書基於 .NET Framework 3.5的Beta 2 版本及Visual Studio 2008。之前作為ASP.NET 2.0 外掛程式程式集的ASP.NET AJAX Extensions現在被整合到 .NET Framework基礎類庫中。本白皮書還假定讀者使用的是Visual Studio 2008 而非 Visual Web Developer Express,並將根據Visual Studio 的使用者介面提供演練(但無論使用的是哪個開發環境,代碼清單都將是完全相容的)。
觸發器
預設情況下,某個給定 UpdatePanel 的觸發器自動包含了調用回傳的所有子控制項,包括(例如)AutoPostBack屬性被設為 true 的TextBox 控制項。但也可通過使用標記聲明性地包含觸發器。這一操作在UpdatePanel 控制項聲明的 <triggers> 部分完成。儘管可以通過Triggers 集合屬性訪問觸發器,但建議在運行時(例如,如果某個控制項在設計時不可用)註冊局部顯示觸發器:在Page_Load 事件中,使用您頁面中 ScriptManager 對象的 RegisterAsyncPostBackControl(Control)方法。請記住頁面是無狀態的,因此每次建立控制項時都要重新註冊這些控制項。
通過將 ChildrenAsTriggers屬性設為 false,可以關閉自動包含子觸發器的功能(這樣建立回傳的子控制項就不會自動觸發局部呈現)。這允許在指定可調用頁面呈現的控制項時享有最大的靈活性,所以建議採用這種方法。這樣,開發人員將以“決定參與”的方式來響應事件,而不是處理可能發生的任何事件。
注意,當 UpdatePanel 控制項被嵌套時,若將 UpdateMode 設為 Conditional,那麼當子 UpdatePanel 被觸發但父 UpdatePanel 未被觸發時,則只有前者被重新整理。但如果父UpdatePanel 也被重新整理,則導致子 UpdatePanel 也將被重新整理。
<Triggers>元素
使用 Visual Studio 中的標記編輯器時,你可能(從 IntelliSense)注意到一個 UpdatePanel 控制項有兩個子項目。最常見的元素是<ContentTemplate> 元素,它實質上封裝的是將被update panel 包含的內容(即我們實現局部呈現的內容)。另一個元素是<Triggers>,用於指定頁面上的控制項(或您在使用的使用者控制項),該控制項將觸發<Triggers> 元素所屬 UpdatePanel 的局部呈現。
<Triggers>元素可以包含以下兩個位元組點中的每一個且數量不限:<asp:AsyncPostBackTrigger>和 <asp:PostBackTrigger>。這兩個子節點都接受兩個屬性:ControlID 和 EventName,且可以在當前封裝單元中指定任何控制項(例如,如果您的UpdatePanel 控制項位於一個 Web 使用者控制項中,您不應試圖引用將放置使用者控制項的頁面上的控制項)。
<asp:AsyncPostBackTrigger>元素最大的用處是它可用於從封裝單元中的 任何 UpdatePanel 控制項的子控制項指定事件,而不只是此觸發器的父UpdatePanel。這樣,任何控制項都可觸發局部頁面更新。
同樣,<asp:PostBackTrigger>元素可用於觸發部分網頁呈現,但要求到伺服器的完整往返行程。此觸發器元素也可用於在控制項如無異常則正常觸發部分網頁呈現時(例如,當UpdatePanel 控制項的 <ContentTemplate> 元素中有一個 Button 控制項時),強制執行完整頁面呈現。同樣,PostBackTrigger 元素可指定當前封裝單元中的任何UpdatePanel 控制項的任何子控制項。
<Triggers>元素引用
標記子項:
標籤 |
說明 |
<asp:AsyncPostBackTrigger> |
指定一個控制項和事件,將引發包含觸發器引用的UpdatePanel的局部頁面更新。 |
<asp:PostBackTrigger> |
指定一個將引發完全頁面更新(整個頁面重新整理)的控制項和事件。此標記可用於在控制項如無異常則正常觸發部分網頁呈現時強制執行完整頁面呈現。 |
演練:Cross-UpdatePanel觸發器
- 建立一個 ASP.NET 頁面,並建立一個 ScriptManager 對象集來啟用局部呈現。向建立頁面添加兩個UpdatePanel:首先添加一個 Label 控制項(Label1),然後添加兩個 Button 控制項(Button1 和 Button2)。Button1 的文字為 “Click to Update Both”,Button2 為 “Click to Update This”或類似內容。在第二個 UpdatePanel 中只添加一個 Label 控制項(Label2),但將其 ForeColor 屬性設為預設值以外的值以便區別。
- 將兩個 UpdatePanel 標記的 UpdateMode 屬性都設為 Conditional。
程式列表 1:default.aspx的標記:
- <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs"
- Inherits="_Default" %>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml" >
- <head runat="server">
- <title>Untitled Page</title>
- </head>
- <body>
- <form id="form1" runat="server">
- <asp:ScriptManager EnablePartialRendering="true"
- ID="ScriptManager1" runat="server"></asp:ScriptManager>
- <div>
- <asp:UpdatePanel ID="UpdatePanel1" runat="server"
- UpdateMode="Conditional">
- <ContentTemplate>
- <asp:Label ID="Label1" runat="server" /><br />
- <asp:Button ID="Button1" runat="server"
- Text="Update Both Panels" OnClick="Button1_Click" />
- <asp:Button ID="Button2" runat="server"
- Text="Update This Panel" OnClick="Button2_Click" />
- </ContentTemplate>
- </asp:UpdatePanel>
- <asp:UpdatePanel ID="UpdatePanel2" runat="server"
- UpdateMode="Conditional">
- <ContentTemplate>
- <asp:Label ID="Label2" runat="server" ForeColor="red" />
- </ContentTemplate>
- <Triggers>
- <asp:AsyncPostBackTrigger ControlID="Button1"
- EventName="Click" />
- </Triggers>
- </asp:UpdatePanel>
- </div>
- </form>
- </body>
- </html>
- 在 Button1 的 Click event handler中,將 Label1.Text 和 Label2.Text 設為與時間無關的值(如 DateTime.Now.ToLongTimeString())。在 Button2 的 Click event handler 中,只將 Label1.Text 設為與時間無關的值。
程式列表2:default.aspx.cs中的代碼檔案(已處理)
- public partial class _Default : System.Web.UI.Page
- {
- protected void Button1_Click(object sender, EventArgs e)
- {
- Label1.Text = DateTime.Now.ToLongTimeString();
- Label2.Text = DateTime.Now.ToLongTimeString();
- }
-
- protected void Button2_Click(object sender, EventArgs e)
- {
- Label1.Text = DateTime.Now.ToLongTimeString();
- }
- }
- 按 F5 鍵運行項目。注意,當您單擊 Update Both Panels時,兩個標籤的文字都改變了。但單擊Update This Panel 時,只有 Label1 更新。
內幕與解密
利用我們前面剛建的樣本,可以瞭解 ASP.NET AJAX 的作用以及 UpdatePanel 跨層面的觸發器是如何運作的。為此,我們將使用產生頁面的源HTML,以及被稱作 FireBug 的 Mozilla Firefox 擴充,它將協助我們更方便地查看AJAX 回傳。此外我們還將使用Lutz Roeder 的 .NET Reflector 。上述工具都可從網上免費下載,通過Internet 搜尋即可找到。
通過對頁面原始碼的檢查發現幾乎沒有什麼異常。UpdatePanel 控制項呈現為 <div> 容器,而且我們可以看到<asp:ScriptManager>提供的指令碼源。此外在 AJAX 用戶端指令碼庫內部,還有一些新的AJAX 特有的對 PageRequestManager 的調用。最後我們看到兩個UpdatePanel容器。其中一個有呈現的<input>按鈕以及兩個呈現為<span>容器的 <asp:Label>控制項。(如果您在FireBug 查看 DOM 樹,可以發現標籤變暗,表示它們不產生可見內容。)
單擊 Update This Panel按鈕,注意頂部的UpdatePanel 將被更新為當前的伺服器時間。在FireBug 中選擇 Console 選項卡以便查看請求。首先查看POST 請求參數:
注意,UpdatePanel 向伺服器側 AJAX 代碼準確表示了通過 ScriptManager1參數觸發的是哪個控制項:即UpdatePanel1控制項的Button1。現在單擊 Update Both Panels按鈕。然後查看響應。我們將看到一個由一系列豎線分隔的變數集組成的字串。其中將看到頂部的UpdatePanel 即 UpdatePanel1 的所有 HTML 都發送至瀏覽器。AJAX 用戶端指令碼庫通過 .innerHTML 屬性,用新內容代替了 UpdatePanel 的原有 HTML 內容,因此伺服器將更改的內容作為HTML 發送出去。
現在單擊 Update Both Panels按鈕並查看從伺服器發回的結果。結果很相似:兩個 UpdatePanel 都接收到了來自伺服器的新HTML。與前一個調用一樣,發送了其它的頁面狀態。
正如我們能看到的那樣,由於沒有使用專門的代碼來執行 AJAX 回傳,AJAX 用戶端指令碼庫不需任何額外的代碼就可以截取表單回傳。伺服器控制項自動使用JavaScript 以便無需自動認可表單:ASP.NET 已經自動注入代碼以便完成表單驗證和狀態設定,主要由自動指令碼資源套件含、PostBackOptions 類和 ClientScriptManager 類來完成。
例如,考慮針對一個 CheckBox 控制項進行此操作。檢查 .NET Reflector中的類反組譯工具。操作方式為:確保您的System.Web 程式集是開啟的,導航到System.Web.UI.WebControls.CheckBox類,開啟 RenderInputTag 方法。尋找一個檢查AutoPostBack屬性的條件:
當某個 heckBox控制項上開啟了自動回傳(通過將 AutoPostBack 屬性設為 true實現)時,呈現結果的 <input>標記時,其 onclick屬性中便會有一個 ASP.NET 事件處理指令碼。然後,對提交表單的截取便可允許向頁面注入ASP.NET AJAX 而不產生任何影響。這有助於防止由於可能插入的不準確字串而造成的打破相容性的更改。此外,這樣還可使 任何定製ASP.NET 控制項都可利用 ASP.NET AJAX 的功能,而無需添加代碼以支援在UpdatePanel 容器中使用 ASP.NET AJAX。
<triggers>功能對應於在 PageRequestManager對 _updateControls的調用中初始化的值(注意 ASP.NET AJAX 用戶端指令碼庫利用了“以底線開始的方法、事件和欄位名稱都標記為內部的,且不能單獨在庫之外使用”這一約定)通過這一功能,可以查看哪些控制項用於引發AJAX 回傳。
例如,我們向頁面添加兩個額外的控制項,將其中一個控制項完全放在UpdatePanel 之外,另一個放在UpdatePanel 之內。我們將在上面的 UpdatePanel 內添加一個 CheckBox 控制項,並添加一個定義了多種顏色的DropDownList。新的標記如下:
程式列表 3:新標記
- <%@ Page Language="C#" AutoEventWireup="true"
- CodeFile="Default.aspx.cs" Inherits="_Default" %>
-
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-
- <html xmlns="http://www.w3.org/1999/xhtml" >
- <head id="Head1" runat="server">
- <title>Untitled Page</title>
- </head>
- <body>
- <form id="form1" runat="server">
- <asp:ScriptManager EnablePartialRendering="true"
- ID="ScriptManager1" runat="server"></asp:ScriptManager>
- <div>
- <asp:UpdatePanel ID="UpdatePanel1" runat="server"
- UpdateMode="Conditional">
- <ContentTemplate>
- <asp:Label ID="Label1" runat="server" /><br />
- <asp:Button ID="Button1" runat="server"
- Text="Update Both Panels" OnClick="Button1_Click" />
- <asp:Button ID="Button2" runat="server"
- Text="Update This Panel" OnClick="Button2_Click" />
- <asp:CheckBox ID="cbDate" runat="server"
- Text="Include Date" AutoPostBack="false"
- OnCheckedChanged="cbDate_CheckedChanged" />
- </ContentTemplate>
- </asp:UpdatePanel>
- <asp:UpdatePanel ID="UpdatePanel2" runat="server"
- UpdateMode="Conditional">
- <ContentTemplate>
- <asp:Label ID="Label2" runat="server"
- ForeColor="red" />
- </ContentTemplate>
- <Triggers>
- <asp:AsyncPostBackTrigger ControlID="Button1"
- EventName="Click" />
- <asp:AsyncPostBackTrigger ControlID="ddlColor"
- EventName="SelectedIndexChanged" />
- </Triggers>
- </asp:UpdatePanel>
- <asp:DropDownList ID="ddlColor" runat="server"
- AutoPostBack="true"
- OnSelectedIndexChanged="ddlColor_SelectedIndexChanged">
- <asp:ListItem Selected="true" Value="Red" />
- <asp:ListItem Value="Blue" />
- <asp:ListItem Value="Green" />
- </asp:DropDownList>
- </div>
- </form>
- </body>
- </html>
代碼檔案如下:
程式列表 4:代碼檔案
- public partial class _Default : System.Web.UI.Page
- {
- protected void Button1_Click(object sender, EventArgs e)
- {
- if (cbDate.Checked)
- {
- Label1.Text = DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss");
- Label2.Text = DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss");
- }
- else
- {
- Label1.Text = DateTime.Now.ToLongTimeString();
- Label2.Text = DateTime.Now.ToLongTimeString();
- }
- }
-
- protected void Button2_Click(object sender, EventArgs e)
- {
- if (cbDate.Checked)
- {
- Label1.Text = DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss");
- }
- else
- {
- Label1.Text = DateTime.Now.ToLongTimeString();
- }
- }
-
- protected void cbDate_CheckedChanged(object sender, EventArgs e)
- {
- cbDate.Font.Bold = cbDate.Checked;
- }
-
- protected void ddlColor_SelectedIndexChanged(object sender, EventArgs e)
- {
- Color c = Color.FromName(ddlColor.SelectedValue);
- Label2.ForeColor = c;
- }
- }
本頁面的構思是下拉式清單從三種顏色中選擇一種來顯示第二個標籤,複選框確定是否顯示為粗體、以及標籤是否顯示日期和時間。複選框不應引發AJAX 更新,但下拉式清單應引發,即便它不在UpdatePanel 中也是如此。
從上面的螢幕中可以清楚地看出,最後單擊的按鈕是右面的Update This Panel,它更新了上方的時間,而不受底部時間的影響。通過單擊還關閉了日期顯示,因為只有下方有日期顯示。最後一個有意思的地方是底部標籤的顏色:它的更新時間比標籤文本還要晚,這表示控制項狀態相當重要,使用者希望通過AJAX 回傳將其保留。然而,時間未被更新 。控制項在伺服器上重新顯示時,時間通過儲存由ASP.NET 運行時解釋的頁面的 __VIEWSTATE 欄位自動重新填充。ASP.NET AJAX 伺服器代碼無法識別控制項在哪些方法中更改狀態,它只是從檢視狀態重新填充,然後運行適當的事件。
但應指出,如果我們之前在 Page_Load 事件中初始化了時間,那麼時間就會正確顯示了。因此,開發人員應注意在適當的event handler 中運行適當的代碼,並避免在適合使用控制項event handler 時使用Page_Load。
小結
ASP.NET AJAX Extensions UpdatePanel 控制項有多種用途,可以使用大量方法來識別導致其更新的控制項事件。它支援通過自己的子控制項自動更新,也可以響應頁面上其它位置的控制項事件。
要降低可能的伺服器處理負載,建議將 UpdatePanel 的 ChildrenAsTriggers屬性賦值為 false,且將事件設為“選擇參與”而非預設包含。這也可以防止不需要的事件可能引發的不必要的結果,包括驗證和對輸入欄位的更改等。這些類型的bug 可能很難隔離,因為頁面更新對於使用者而言是透明的,因此其引發原因就可能不是立即可見的。
通過檢查 ASP.NET AJAX 表單張貼截模數型的內部運行,我們可以確定它使用了ASP.NET 早已提供的架構,以便於保持對使用同一個架構設計的控制項的最大相容性,並盡量減少為頁面額外編寫的JavaScript。
快樂編程!