程守華 譯
概述
AJax依靠服務器作為中介來分發和處理請求。為了完成這項工作,.Net封裝類依賴於客戶端的請求對象,而xmlHttpRequest對象被大部分的浏覽器支持,因此使用這個對象是一個不錯的解決方案。因為封裝的目的是隱藏XMLHttpRequest的實現,故我們不討論他的實現細節。
封裝類是通過在.Net的方法上增加AJAX屬性標記來實現的,一旦被標記,AJax創建客戶端的Javascript函數(這類似於客戶端編寫的Javascript函數),並使用XMLhttprequest創建服務器代理,這個代理映射客戶端的函數到服務器的處理函數。
復雜嗎?不會的,讓我們看看下面的簡單例子,給出的.Net 函數
''VB.Net
public function Add(firstNumber as integer, secondNumber as integer) as integer
return firstNumber + secondNumber
end sub
//C#
public int Add(int firstNumber, int secondNumber)
{
return firstNumber + secondNumber;
}
AJax.Net會立即自動的創建帶有兩個參數、名字為Add的Javascript函數,當客戶端調用這個Javascript函數時,請求將從後台送到服務器端並從將計算結果返回給客戶端。
初始安裝
我們首先從如何把”.dll”安裝到你的項目開始,當然,如果你了解如何使用,這一節可以跳過。
如果你還沒有AJax.dll,可以首先下載AJax的最新版本。解壓文件放到可以被你的項目引用的地方,在.Net項目中,添加上對其的引用,然後就可以開始使用AJax.dll封裝進行開發了。
如果你在安裝引用時遇到了麻煩,可以參考這個鏈接的說明:
http://msdn.microsoft.com/library/default.ASP?url=/library/en-us/vbcon/Html/vbtskaddingremovingreferences.ASP
設置HttpHandle
為了使其可以工作,第一步必須做的是在web.config中安裝設置封裝包的HttpHandle,不去詳細解釋HttpHandle是如何工作的,我們只需要了解他們可以用來處理ASP.Net請求。例如,所有的目的為*.ASPx的請求可以通過System.Web.UI.PageHandlerFactory類發送到控制句柄,簡單的說,我們把任何向AJax/*.ashx的請求發送到AJax.PageHandlerFactory的請求處理句柄:
<configuration>
<system.web>
<httpHandlers>
<add verb="POST,GET" path="
type="AJax.PageHandlerFactory,
</httpHandlers>
...
<system.web>
</configuration>
上面的代碼告訴ASP.Net把任何匹配到特定的路徑(AJax/*.ashx)請求發送到AJax.PageHandlerFactory產生的HttpHandle,而不再是默認的Handler factory。你不需要創建AJax子目錄,這是一個只用來臨時使用的虛擬的目錄,因此別的HttpHandler可以用他們自己的目錄來使用.ashx擴展名的文件。
配置頁面
現在我們准備好開始代碼編寫了。打開一個新的網頁或者已經存在的頁面,在其codebehind文件中的Page_Load事件中增加以下代碼:
''vb.Net
Public Class Index
Inherits System.Web.UI.Page
Private Sub Page_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Ajax.Utility.RegisterTypeForAJax(GetType(Index))
end sub
''...
End Class
//C#
public class Index : System.Web.UI.Page{
private void Page_Load(object sender, EventArgs e){
Ajax.Utility.RegisterTypeForAJax(typeof(Index));
//...
}
//...
}
對RegisterTypeForAJax方法的調用在頁面產生如下的Javascript代碼(另外一種選擇,你也可以人工在頁面上添加如下的Javascript代碼)
<script language="Javascript" src="AJax/common.ashx"></script>
<script language="Javascript"
src="AJax/NAMESPACE.PAGECLASS,ASSEMBLYNAME.ashx"></script>
上面這段代碼的粗體部分NAMESPACE.PAGECLASS,ASSEMBLYNAME含義如下
NAMESPACE.PAGECLASS
當前頁面的命名空間和類
ASSEMBLYNAME
當前頁面的程序集的名稱
下面是在AJaxPlay項目中sample.ASPx的示例輸出:
<%@ Page Inherits="AJaxPlay.Sample" Codebehind="sample.ASPx.cs" ... %>
<Html>
<head>
<script language="Javascript" src="AJax/common.ashx"></script>
<script language="Javascript"
src="AJax/AJaxPlay.Sample,AJaxPlay.ashx"></script>
</head>
<body>
<form id="Form1" method="post" runat="server">
...
</form>
</body>
</Html>
你可以測試一下,人工通過浏覽器將src path(通過查看源文件並copy)打開,一切都能正常的工作。如果輸出了無意義的文本表示到目前為止是正確的,如果輸出ASP.Net錯誤,則表示中間出現了錯誤。
即使你不了解HttpHandle的工作方式,也應該可以理解上面的描述。通過web.config,我們可以確保發送向AJax/*.ashx的請求由我們自定義的句柄來處理,很顯然,兩個腳本標記由自定義句柄處理。
編寫服務端函數
現在我們編寫服務器端函數,他們可以被客戶端異步的調用。盡管現在還不能支持全部的返回類型,我們仍堅持服務器端添加功能。在codebehind文件的頁面類裡,添加下面的方法:
''VB.Net
<Ajax.AJaxMethod()> _
Public Function ServerSideAdd (byval firstNumber As Integer, byval secondNumber
As Integer) As Integer
Return firstNumber + secondNumber
End Function
//C#
[Ajax.AJaxMethod()]
public int ServerSideAdd(int firstNumber, int secondNumber)
{
return firstNumber + secondNumber;
}
注意,這個函數有Ajax.AjaxMethod()定制屬性,屬性服務會告知AJax封裝類為此方法創建一個Javascript代理,這樣才能被客戶端調用。
定制客戶端調用接下來在客戶端用Javascript調用函數。AJax封裝類會創建一個Javascript函數,帶有兩個參數,名字是 類名.ServerSideAdd。作為最基本的功能,我們所需要作的只是調用這個方法並且傳遞參數:
<%@ Page Inherits="AJaxPlay.Sample" Codebehind="sample.ASPx.cs" ... %>
<Html>
<head>
<script language="Javascript" src="AJax/common.ashx"></script>
<script language="Javascript"
src="ajax/AjaxPlay.Sample,AJaxPlay.ashx"></script>
</head>
<body>
<form id="Form1" method="post" runat="server">
<script language="Javascript">
var response = Sample.ServerSideAdd(100,99);
alert(response.value);
</script>
</form>
</body>
</Html>
當然,我們不能把這麼強大的功能僅僅用來通過alert來提醒浏覽者,這就是為什麼所有的客戶端代理(如 類名.ServerSideAdd函數)同時帶有一個額外的定制屬性。這個屬性是用來處理服務器響應的回調函數:
Sample.ServerSideAdd(100,99, ServerSideAdd_CallBack);
function ServerSideAdd_CallBack(response){
if (response.error != null){
alert(response.error);
return;
}
alert(response.value);
}
從上面的代碼中可以看出,我們為ServerSideAdd函數增加了一個額外參數ServerSideAdd_CallBack,這個參數就是用來處理服務器端響應的客戶端函數。這個callback函數接受一個帶有四個關鍵屬性的response對象:
value
服務器端函數執行的返回值(可能是一個字符串、自定義對象或者dataset)
error
如果發生錯誤,則返回錯誤信息.
request
原始的XMLHttpRequest請求
context
一個上下文對象
我們首先應該檢查是否有錯誤發生,你可以通過在服務器端函數拋出異常來實現這個error屬性。在上面這個例子中,我們簡單的alert了一個值,就是value屬性;request屬性可以用來取得額外的信息(見下面的表格)
如果你想了解更多的關於XMLHttpRequest的知識,可以查看下面的鏈接: http://www.quirksmode.org/blog/archives/2005/02/XMLhttp_linkdum.Html
處理類型
返回一個復雜類型
AJax可以支持除了我們上面ServerSideAdd函數返回的Int值以外很多類型。他可以直接支持integers, strings, double, booleans, DateTime, DataSets 和 DataTables,也支持簡單的自定義類型和數組。其他的類型通過其ToString方式來返回字符串。
返回DataSet的工作就像真正的.Net Dataset.給出一個返回DataSet的服務器端函數,我們可以通過下面的方法在客戶端顯示:
<script language="JavaScript">
//Asynchronous call to the mythical "GetDataSet" server-side function
function getDataSet(){
AJaxFunctions.GetDataSet(GetDataSet_callback);
}
function GetDataSet_callback(response){
var ds = response.value;
if(ds != null && typeof(ds) == "object" && ds.Tables != null){
var s = new Array();
s[s.length] = "<table border=1>";
for(var i=0; i<ds.Tables[0].Rows.length; i++){
s[s.length] = "<tr>";
s[s.length] = "<td>" + ds.Tables[0].Rows[i].FirstName + "</td>";
s[s.length] = "<td>" + ds.Tables[0].Rows[i].Birthday + "</td>";
s[s.length] = "</tr>";
}
s[s.length] = &q
tableDisplay.innerHtml = s.join("");
}
else{
alert("Error. [3001] " + response.request.responseText);
}
}
</script>
AJax也可以支持自定義類,但是需要這個類是可以被序列化的。如下面的類:
[Serializable()]
public class User{
private int _userId;
private string _firstName;
private string _lastName;
public int userId{
get { return _userId; }
}
public string FirstName{
get { return _firstName; }
}
public string LastName{
get { return _lastName; }
}
public User(int _userId, string _firstName, string _lastName){
this._userId = _userId;
this._firstName = _firstName;
this._lastName = _lastName;
}
public User(){}
[AJaxMethod()]
public static User GetUser(int userId){
//Replace this with a DB hit or something :)
return new User(userId,"Michael", "Schwarz");
}
}
我們需要通過調用RegisterTypeForAJax向服務器注冊GetUser代理
private void Page_Load(object sender, EventArgs e){
Utility.RegisterTypeForAJax(typeof(User));
}
在客戶端我們可以通過這樣的方式調用GetUser函數:
<script language="Javascript">
function getUser(userId){
User.GetUser(GetUser_callback);
}
function GetUser_callback(response){
if (response != null && response.value != null){
var user = response.value;
if (typeof(user) == "object"){
alert(user.FirstName + " " + user.LastName);
}
}
}
getUser(1);
</script>
返回值同服務器端對象一樣有三個屬性(FirstName, LastName and UserId)。
譯者注:其他的類型只能由開發者通過在服務器端函數在返回值時自定義轉換為ajax支持的類型來實現了,AJax推薦使用ToString方法。
其他工作方式
在其他類注冊函數
在上面的例子及描述中,我們都是通過在頁面的codebehind文件裡完成函數的注冊,但並不是說只能在頁面的codebehide文件裡完成注冊,我們也可以在其他類中注冊函數。回憶一下,Ajax封裝類是通過在特定類裡面查找那些有Ajax.AjaxMethod()屬性的方法來完成工作的,這些類在客戶端又通過兩個script片斷來完成返回值描述。使用Ajax.Utility.RegisterTypeForAJax,我們可以得到任何我們想得到類的詳細內容。例如,下面的例子可以說明我們在其他類中使用服務器端函數是合法的:
Public Class AJaxFunctions
<Ajax.AJaxMethod()> _
Public Function Validate(username As String, passWord As String) As Boolean
''do something
''Return something
End Function
End Class
不過我們需要首先在調用頁面注冊這個代理類,類的名字不再是頁面類,而是我們使用的這個類:
''Vb.Net
Private Sub Page_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Ajax.Utility.RegisterTypeForAjax(GetType(AJaxFunctions))
''...
End Sub
//C#
private void Page_Load(object sender, EventArgs e){
Ajax.Utility.RegisterTypeForAjax(typeof(AJaxFunctions));
//...
}
記住,客戶端調用使用這種格式的名字<ClassName>.<ServerSideFunctionName>。因此,如果上面的Serversideadd函數位於AjaxFunctions類,而不是頁面類的話,客戶端調用則變為:AJaxFunctions.ServerSideAdd(1,2)
代理是怎樣工作的呢?
第二個script標簽,如下面的示例
<script type="text/Javascript" src="/cqyd/AJax/cqyd.SchemeSendWatch,cqyd.ashx"></script>
是由Ajax utility通過命名空間、類以及頁面程序集自動生成的(當然也可以人工加入),從這一點我們可以想到Ajax.PageHandlerFactory是通過反射來取得有定制屬性的函數的細節。很顯然,Handler尋找帶有AjaxMethod定制屬性的函數,取得他們的特征(返回類型、名稱、參數)並依據這些信息創建客戶端代理。特別的,AJax創建一個與類型相同的JavaScript對象作為代理。
返回Unicode字符
AJax.Net可以從服務器端向客戶端返回Unicode字符,為了做到這一點,在服務端函數返回時返回的值必須是Html編碼的:
[Ajax.AJaxMethod]
public string Test1(string name, string email, string comment){
string Html = "";
Html += "Hello " + name + "<br>";
Html += "Thank you for your comment <b>";
html += System.Web.HttpUtility.HtmlEncode(comment);
Html += "</b>.";
return Html;
}
在服務端函數,你可能需要接受傳送的session信息,為了做到這一點,必須要在想實現這個方式的服務端函數的Ajax.AJaxMethod屬性上傳遞一個參數。
在查看ajax可以支持session的時候,我們先看看其他的特征。在下面這個例子中,我們有一個文檔管理系統,當一個用戶對文檔進行編輯的時候會給這個文檔加鎖,其他用戶需要等到這個文檔可用時才能修改。不使用Ajax,用戶需要不斷等待刷新,因為不得不不斷的去檢查文檔的狀態是否為可用,這當然不是一個很好的方案。用AJax的session state支持,這就比較容易了。
我們首先寫一個函數,這個函數通過遍歷文檔ID找到用戶需要的文檔,存儲到session裡,並返回沒有占用的文檔:
''Vb.Net
<Ajax.AJaxMethod(HttpSessionStateRequirement.Read)> _
Public Function DocumentReleased() As ArrayList
If HttpContext.Current.Session("DocumentsWaiting") Is Nothing Then
Return Nothing
End If
Dim readyDocuments As New ArrayList
Dim documents() As Integer = CType(HttpContext.Current.Session("DocumentsWaiting"), Integer())
For i As Integer = 0 To documents.Length - 1
Dim document As Document = document.GetDocumentById(documents(i))
If Not document Is Nothing AndAlso document.Status = DocumentStatus.Ready Then
readyDocuments.Add(document)
End If
Next
Return readyDocuments
End Function
//C#
[Ajax.AJaxMethod(HttpSessionStateRequirement.Read)]
public ArrayList DocumentReleased(){
if (HttpContext.Current.Session["DocumentsWaiting"] == null){
return null;
}
ArrayList readyDocuments = new ArrayList();
int[] documents = (int[])HttpContext.Current.Session["DocumentsWaiting"];
for (int i = 0; i < documents.Length; ++i){
Document document = Document.GetDocumentById(documents[i]);
if (document != null && document.Status == DocumentStatus.Ready){
readyDocuments.Add(document);
}
}
return readyDocuments;
}
}
我們在屬性參數中指明了HttpSessionStateRequirement.Read(還可以是Write and ReadWrite)
下面寫Javascript函數來使用這個方法帶來的結果:
<script language="Javascript">
function DocumentsReady_CallBack(response){
if (response.error != null){
alert(response.error);
return;
}
if (response.value != null && response.value.length > 0){
var div = document.getElementById("status");
div.innerHtml = "The following documents are ready!<br />";
for (var i = 0; i < response.value.length; ++i){
div.innerHtml += "<a href=\"edit.ASPx?documentId=" + response.value[i].DocumentId + "\">" + response.value[i].Name + "</a><br />";
}
}
setTimeout(''page.DocumentReleased(DocumentsReady_CallBack)'', 10000);
}
</script>
<body onload="setTimeout(''Document.DocumentReleased(DocumentsReady_CallBack)'', 10000);">
頁面加載後每10秒鐘向服務器函數請求一次。如果有返回,則call back函數檢查response,並把最新的結果顯示出來。
結論
Ajax技術可以給客戶端提供豐富的客戶體驗,而ajax.net為您容易的實現這樣強大的功能提供了可能,你可以通過下面的鏈接查看AJax.Net的最新文檔
Keep a close eye on the
http://AJax.schwarz-interactive.de/
For a good hands-on sample, check out the following demo application:
http://ajax.schwarz-interactive.de/download/AJaxsample.zip