asp.net|用戶端 開發人員使用JavaScript的一個主要原因就是可以避免回傳過程中帶來的頁面重新整理。例如我們可以根據使用者的需要使用Treeview控制項來展開和摺疊相應的資料節點。當你展開一個節點時,該Treeview控制項將會利用JavaScript讀取伺服器上的子節點資訊,然後平滑無重新整理地插入這些新節點。如果沒有使用JavaScript的話,Treeview控制項將會因為頁面的回傳而重新構建。不但使用者會發現因頁面重新整理而帶來的延遲,而且頁面極有可能回複到原來的狀態,即丟失前面所展開的那些子節點資訊。對於伺服器端來說,因為每次回傳的過程中都要處理大量的檢視狀態(View State)資訊,這也會嚴重影響程式的整體執行效能。
以前我們使用的JavaScript樣本幾乎都是自包含的,即它們通常是為了完成一些特殊的顯示效果(例如彈出一個新的頁面視窗),而沒有和伺服器端代碼進行資訊的互動。如果你也想構建一個類似的無重新整理頁面的話,你首先必須調用伺服器端的一個特定的方法,等待伺服器響應後就會將請求的資訊傳遞到用戶端,從而避免了回傳這個過程。為了實現這個方案,你首先需要對如何將用戶端指令碼和伺服器端代碼進行通訊有個大致的瞭解。儘管有許多中方法可以實現這兩者間的互動(例如調用Web服務),但是由於受到特定的瀏覽器和平台的限制,它們的實現是還是有一定的難度的。而在ASP.NET 2.0中,引入了一個稱為"用戶端回調"的功能,利用這個內建的解決方案我們可以輕鬆實現用戶端指令碼和伺服器端代碼間的互動,從而避免了頁面因回傳帶來的頻繁重新整理。
天極開發ASP.NET專欄:http://dev.yesky.com/msdn/msdnasp/
用戶端回調本質上就是指通過前端的用戶端指令碼向伺服器端傳遞相應的資料參數,伺服器端再以接受到的參數進行查詢和處理,最後將結果回傳到用戶端進行顯示。雖然這樣的過程不是一種創舉,但是對於許多開發人員來說這在某種思維上還是無法理解的,因為JavaScript的記憶體管理和.NET CLR的記憶體管理是不同的進程,而且管理的空間上也截然不同,所以彼此間無法直接參照也沒有直接進行互動的方式,而用戶端回調卻是實現用戶端和伺服器端進行溝通的方法之一,又因為它是在用戶端觸發的,所以這就應該是"用戶端回調"命名的由來吧!
建立一個簡單的用戶端回調
為了在ASP.NET中展示一個用戶端回調的執行個體,首先我們將概述用戶端回調間的互動過程是如何?的。下面是基本的步驟:
1. 在某時刻啟用一個JavaScript事件,從而觸發用戶端回調。
2. 觸發用戶端回調發生後,伺服器端的一個方法將被執行。該方法有一個固定的模式――它接受的是一個字串參數,並且返回的也是一個字串參數。
3. 一旦頁面接受到來自伺服器端方法的響應結果後,它就可以利用JavaScript修改一些和使用者介面有關的資訊(例如顯示在頁面上顯示返回的結果)
對於開發人員來說,底層的互動過程是非常複雜的,ASP.NET則將互動的處理過程進行了抽象化,這樣使得開發人員可以直接建立表層的用戶端回調,而無需考慮底層的操作是如何?的。
下面的執行個體中,頁面中放置了一個文字框,一個提交按鈕和一個標籤。文字框是用來接受使用者的輸入資訊,在單擊提交按鈕後將把文字框中輸入的資訊在標籤上進行即時的顯示。注意,在輸入資訊後單擊提交按鈕時,並沒有像以前傳統的提交方式那樣重新對頁面進行構建和重新整理。圖1-1為該執行個體的效果圖。
建立基本的頁面
按照上圖的布局在工具列的"標準"標籤中拖拽出一個TextBox控制項,一個Label控制項到主表單上。然後在"HTML"標籤中拖拽出一個InputButton的HTML按鈕,注意,該按鈕不是我們經常使用的伺服器端控制項,而是一個HTML元素。在按鈕中添加一個onclick事件,點擊該按鈕這將向伺服器端發出回調請求,這個onclick事件的具體實現細節將在以後的過程中加以說明。得到的初始頁面代碼如下:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="CallBackExample.aspx.cs" Inherits="CallBackExample" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>用戶端回調</title>
</head>
<body>
<form id="form1" runat="server">
<div>
請輸入資訊:<asp:TextBox ID="txtEnter" runat="server"></asp:TextBox>
<input id="btnSubmit" type="button" value="提交" />
<br />
<asp:Label ID="lblShow" runat="server"></asp:Label>
</div>
</form>
</body>
</html>
執行回調
為了實現用戶端回調,你的頁面邏輯代碼中必須實現一個ICallbackEventHandler介面。代碼如下:
public partial class CallBackExample : System.Web.UI.Page,
System.Web.UI.ICallbackEventHandler
{… …}
ICallbackEventHandler介面定義了兩個方法,RaiseCallbackEvent()從瀏覽器接受一個字串作為事件參數,即該方法接受用戶端JavaScript使傳遞的參數,注意它是首先觸發的。接下來觸發的就是GetCallbackResult()方法,它將所得到的結果傳回給用戶端的JavaScript,JavaScript再將結果更新到頁面。
本例中的RaiseCallbackEvent()中的參數為我們在文字框的輸入資訊。為了表明它是從伺服器返回的,我們加了一些說明性的文字。然後用GetCallbackResult()方法中將結果傳回到用戶端。完整的頁面邏輯代碼如下:
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
public partial class CallBackExample : System.Web.UI.Page,
System.Web.UI.ICallbackEventHandler
{
//定義一個字串,回調的結果資訊將儲存在該字串中
private string result;
//引發回調事件處理
public void RaiseCallbackEvent(string eventArgument)
{
//"eventArgument"為從用戶端的JavaScript傳遞過來的參數
result = "從伺服器端返回的內容:" + eventArgument;
}
//回傳回調結果
public string GetCallbackResult()
{
return result;
}
}
編寫用戶端指令碼
用戶端指令碼主要是用來在伺服器端和用戶端之間進行資訊的互動,就拿本例來說,我們在前面的頁面邏輯代碼中使用了一個名為eventArgument參數,這是怎麼實現參數的傳遞的呢?我們將在後面的章節進行討論,現在在頁面中添加如下的JavaScript函數代碼
function CallServer(inputcontrol,context)
{
//回調還沒有處理完全時其積極式載入的顯示值
context.innerHTML = "載入中......";
//為你在文字框中輸入的資訊,並且arg在這裡就是將其值傳遞到
//RaiseCallbackEvent(String eventArgument)方法對應的eventArgument中
arg = inputcontrol.value;
//擷取一個對用戶端函數的引用;調用該函數時,將啟動一個對伺服器端事件的用戶端回調。
<%= ClientScript.GetCallbackEventReference(this, "arg", "ReceiveServerData", "context")%>;
}
我們在上述的JavaScript函數代碼引用了一個ClientScript.GetCallbackEventReference(……)方法,該方法實現的是什麼功能呢?下面是摘自MSDN2上的對ClientScript.GetCallbackEventReference(……)的詳細說明。
public string GetCallbackEventReference (Control control,string argument,string clientCallback,string context)
參數:
參數 |
作用 |
control |
處理用戶端回調的伺服器 Control。該控制項必須實現 ICallbackEventHandler 介面並提供 RaiseCallbackEvent 方法。 |
argument |
從用戶端指令碼傳遞一個參數到伺服器端的RaiseCallbackEvent 方法。 |
clientCallback |
一個用戶端事件處理常式的名稱,該處理常式接收伺服器端事件返回的結果。 |
context |
啟動回調之前在用戶端的用戶端指令碼資訊。指令碼的結果傳回給用戶端事件處理常式。 |
傳回值 |
調用用戶端回調的用戶端函數的名稱。 |
下面是ClientScriptManager.GetCallbackEventReference 方法的重載列表
名稱 |
說明 |
ClientScriptManager.GetCallbackEventReference (Control, String, String, String) |
擷取一個對用戶端函數的引用;調用該函數時,將啟動一個對伺服器端事件的用戶端回調。此重載方法的用戶端函數包含指定的控制項、參數、用戶端指令碼和上下文。 |
ClientScriptManager.GetCallbackEventReference (Control, String, String, String, Boolean) |
擷取一個對用戶端函數的引用;調用該函數時,將啟動一個對伺服器端事件的用戶端回調。此重載方法的用戶端函數包含指定的控制項、參數、用戶端指令碼、上下文和布爾值。 |
ClientScriptManager.GetCallbackEventReference (Control, String, String, String, String, Boolean) |
擷取一個對用戶端函數的引用;調用該函數時,將啟動一個對伺服器端事件的用戶端回調。此重載方法的用戶端函數包含指定的控制項、參數、用戶端指令碼、上下文、錯誤處理程式和布爾值。 |
ClientScriptManager.GetCallbackEventReference (String, String, String, String, String, Boolean) |
擷取一個對用戶端函數的引用;調用該函數時,將啟動一個對伺服器端事件的用戶端回調。此重載方法的用戶端函數包含指定的目標、參數、用戶端指令碼、上下文、錯誤處理程式和布爾值。 我們就整個程式作個系統的說明,並且列出前台的頁面代碼和背景邏輯代碼,這樣可以使得你對程式有個直觀的理解。 |
後台代碼 CallBackExample.aspx.cs
代碼說明:
若要從用戶端成功運行伺服器代碼,而不執行回傳,必須在伺服器頁代碼中實現合適的介面。為此我們在第12行代碼中對 ICallbackEventHandler 介面進行聲明。第15和19行建立了兩個伺服器端的代碼回調方法。第15行的"RaiseCallbackEvent()"方法中的"eventArgument"字串參數是來什麼地方呢?轉到前台頁面的第10行代碼,arg就相當於傳遞給"RaiseCallbackEvent()"方法的實參。第19行的"GetCallbackResult()"方法則將通過"RaiseCallbackEvent()"方法得到的結果返回給用戶端,即結果"result"最終被傳遞到前台頁面的第15代碼所示的"ReceiveServerData()"方法的"result"參數中。
前台代碼 CallBackExample.aspx
代碼說明:
為了向伺服器頁發送回調和接收結果這兩個功能,我們在前台頁面中定義了2個用戶端指令碼函數。第7行所示的"CallServer()"函數實現的就是發送回調的功能,注意發送回調的函數實際是在伺服器端實現的,這是因為真正實現發送回調的是第11行的"ClientScript.GetCallbackEventReference()"方法,而"CallServer()"函數只是對"ClientScript.GetCallbackEventReference()"方法的引用,並提供一些必要的參數。
現在我們來詳細講解下這些用戶端函數的具體實現細節,我們通過單擊頁面第25和26行所聲明的按鈕後,將觸發OnClick事件。
在"CallServer()"中將文字框和標籤作為參數傳遞到第7行相應的JavaScript函數後,"inputcontrol"和"context"就如同成為文字框和標籤控制項形參數。第9行代碼錶示沒有接受到回調結果前"context"將顯示一個"載入中……"的資訊,直到回調完成後才用第17行的代碼將回調的結果用"context"重新進行顯示。第10行代碼錶示將文字框的輸入值賦予"arg",並且"arg"在第12行代碼的ClientScript.GetCallbackEventReference()方法中作為對應的第2個參數。第一個參數用"this"表示對本頁面的引用,因為ClientScript.GetCallbackEventReference()方法也是在CallBackExample.aspx實現的。第三個參數表示接受回調結果的用戶端函數,它在這和第15行代碼所實現的ReceiveServerData()函數相匹配(注意函數名稱必須是一致的,否則會導致出錯),回調的結果將通過"context"進行顯示。第四個參數"context"用於從伺服器端返回的上下文,因為ClientScript.GetCallbackEventReference()方法是在伺服器端執行的,在該方法中原本傳遞的"context"內容為一個"載入中……"的資訊,回調返回後"context"在第17行中被重新進行改寫,如果沒有對內容相關的引用的話,你可以將該參數設定為"null"。
讀取資料庫資訊的用戶端回調程式
本程式是一個實現讀取Northwind資料庫的Emlpoyees資訊,為此你必須先保證Northwind資料庫存在。下圖是Emlpoyees表的內容。
在文字框輸入要尋找的使用者名稱後,接著點擊"回調"按鈕就會發生用戶端回調。這是尋找到使用者的顯示結果
使用者不存在的顯示資訊:
後台代碼:ClientCallbacksSimple.aspx.cs
01 using System;
02 using System.Data;
03 using System.Configuration;
04 using System.Collections;
05 using System.Web;
06 using System.Web.Security;
07 using System.Web.UI;
08 using System.Web.UI.WebControls;
09 using System.Web.UI.WebControls.WebParts;
10 using System.Web.UI.HtmlControls;
11 using System.Data.SqlClient;
12 public partial class ClientCallbacksSimple : System.Web.UI.Page, 13
13 System.Web.UI.ICallbackEventHandler
14 {
15 protected string strUserInfo; //儲存讀取的使用者資訊
16 //引發回調事件
17 public void RaiseCallbackEvent(string txtFirstName)
18 {
19 if (txtFirstName != null)
20 {
21 SqlConnection conn = new SqlConnection("data source=localhost;initial
22 catalog=Northwind;integrated security=SSPI");
23 conn.Open();
24 SqlCommand cmd = new SqlCommand("select EmployeeID,FirstName,City,Address 25
25 from Employees where FirstName=@FirstName", conn);
26 cmd.Parameters.Add("@FirstName", SqlDbType.NVarChar, 10).Value = txtFirstName;
27 SqlDataReader dr = cmd.ExecuteReader();
28 if (dr.Read())
29 {
30 strUserInfo = "員工代號:" + dr["EmployeeID"] + "\r\n";
31 strUserInfo += "姓名:" + dr["FirstName"] + "\r\n";
32 strUserInfo += "居住城市:" + dr["City"] + "\r\n";
33 strUserInfo += "地址:" + dr["Address"].ToString().Replace("\r\n","")+ "\r\n";
34 strUserInfo += "伺服器查詢時間:" + DateTime.Now.ToLongTimeString();
35 }
36 else
37 {
38 if (String.IsNullOrEmpty(txtFirstName))
39 {
40 strUserInfo = "請輸入姓名";
41 }
42 else
43 {
44 strUserInfo = "查無此人";
45 }
46 }
47 cmd.Dispose();
48 dr.Dispose();
49 conn.Dispose();
50 }
51 }
52 //回傳回調結果
53 public string GetCallbackResult()
54 {
55 return strUserInfo; //回傳員工的基本資料
56 }
57 }
代碼說明:在RaiseCallbackEvent()方法中,傳遞了一個從前台頁面文字框的輸入資料作為其參數,即所要從資料庫查詢的使用者名稱。第28-34行代碼的功能為讀取使用者的詳細資料,並且將使用者資訊儲存在一個字串strUserInfo中。如果尋找不到相應的使用者,則返回一些出錯資訊,見代碼36-45。GetCallbackResult()方法回傳回調結果,即儲存使用者資訊的字串。
前台代碼:ClientCallbacksSimple.aspx
01 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="ClientCallbacksSimple.aspx.cs"
02 Inherits="ClientCallbacksSimple" %>
03 <html xmlns="http://www.w3.org/1999/xhtml" >
04 <head runat="server">
05 <title>讀取資料庫資訊的用戶端回調程式</title>
06 <script type="text/JavaScript">
07 function OnCallback(strUserInfo,context)
08 {
09 Results.innerText = strUserInfo;
10 }
11 </script>
12 </head>
13 <body>
14 <form id="form1" runat="server">
15 <div>
16 姓名:<input id="txtUserName" type="text" />
17 <input id="btnCallback" type="button" value="回調" document.form1.txtUserName.value",
19 "OnCallback",null) %>" />
20 <br />
21 <div ID="Results" style="background-color: pink"></div>
22 </div>
23 </form>
24 </body>
25 </html>
代碼說明:這個程式與第一個程式的最大不同就是前台頁面上有些細微的區別。如代碼17-19所示,我們將發送回調的ClientScript.GetCallbackEventReference()方法直接寫在了按鈕的單擊事件中,這也是一種可行且簡捷的方式。ClientScript.GetCallbackEventReference()方法的3個參數為"OnCallback",表示回調完成後將回調結果返回給用戶端的OnCallback()指令碼函數,在此回調結果strUserInfo將作為該函數的一個參數在頁面進行顯示,如代碼9所示。由於我們在此沒有用到內容相關的聯絡,所以ClientScript.GetCallbackEventReference()方法的4個參數為"null",但是OnCallback()指令碼函數還是要保留該"context"參數,因為這是接受回調結果的用戶端函數的固定格式。
小結:
注意所有的非同步技術如本文所探討的Callback用戶端回調,以及微軟新推出的Atlas架構,都不再使用傳統的Postback。因此用戶端在呈現由伺服器端返回的資料時,瀏覽器下方將看不到一閃而過的綠色狀態條,並且非同步過程只傳送和接受少量的資料,而非Postback過程中傳遞的整個ViewState狀態,因此程式在執行效能上有了較大的提高。希望讀者耐心理解和練習上述的兩個執行個體,只有通過自己的實踐才能理解用戶端回調的精髓。