Asynchronous JavaScript and XML(AJAX)最近掀起的高潮,要完全歸功於Google在Google Suggest和Google Maps中的使用。對ASP.NET而言,AJAX不需要回傳就能進行伺服器端處理,從而使客戶機(瀏覽器)具有豐富的伺服器端能力。換句話說,它為非同步指派和處理請求與伺服器響應提供了一個架構。
AJAX利用了一些不是很新穎的已有技術,但是對這些技術(加到一起就是AJAX)的愛好最近突然升溫。
請嘗試Michael Schwarz的AJAX .NET封裝器,通過它ASP.NET開發人員可以快速方便的部署很容易利用AJAX功能的頁面。需要注意的是,這個封裝器處於初期開發階段,因此還沒有完全成熟。
它是如何工作的——概述
AJAX依靠代理(broker)指派和處理往返伺服器的請求。對此,.NET封裝器依靠用戶端XmlHttpRequest對象。多數瀏覽器都支援XmlHttpRequest對象,這就是選擇它的原因。因為封裝器的目的是隱藏XmlHttpRequest的實現,我們就不再詳細討論它了。
封裝器本身通過將.NET函數標記為AJAX方法來工作。標記之後,AJAX就建立對應的JavaScript函數,這些函數(和任何JavaScript函數一樣)作為代理可以在用戶端使用XmlHttpRequest調用。這些代理再映射回伺服器端函數。
複雜嗎?並不複雜。我們來看一個例子。假設有一個.NET函數:
ublic int Add(int firstNumber, int secondNumber)
{
return firstNumber + secondNumber;
}
AJAX .NET封裝器將自動建立名為“Add”、帶有兩個參數的JavaScript函數。使用JavaScript(在客戶機上)調用該函數時,請求將傳遞給伺服器並把結果返回給客戶機。
初始設定
我們首先介紹“安裝”項目中使用的.dll的步驟。如果您很清楚如何添加.dll檔案引用,可以跳過這一節。
首先,如果還沒有的話,請下載最新的AJAX版本。解壓下載的檔案並把ajax.dll放到項目的引用檔案夾中。在Visual Studio.NET中有機Solution Explorer的“References(引用)”節點並選擇Add Reference(添加引用)。在開啟的對話方塊中,單擊Browse(瀏覽)並找到ref/ajax.dll檔案。依次單擊Open(開啟)和Ok(確認)。這樣就可以用AJAX .NET封裝器編程了。
建立HttpHandler
為了保證正常工作,第一步是在web.config中設定封裝器的HttpHandler。不需要詳細解釋HttpHandlers是什麼及其如何工作,只要知道它們用於處理ASP.NET請求就足夠了。比如,所有*.aspx頁面請求都由
System.Web.UI.PageHandlerFactory類處理。類似的,我們讓所有對ajax/*.ashx的請求由Ajax.PageHandlerFactory處理:
〈configuration>
〈system.web>
〈httpHandlers>
〈add verb="POST,GET" path="ajax/*.ashx"
type="Ajax.PageHandlerFactory, Ajax" />
〈/httpHandlers>
〈system.web>
〈/configuration>
簡言之,上面的代碼告訴ASP.NET,和指定路徑(ajax/*.ashx)匹配的任何請求都由Ajax.PageHandlerFactory而不是預設處理常式工廠來處理。不需要建立ajax子目錄,使用這個神秘的目錄只是為了讓其他HttpHandlers能夠在自己建立的子目錄中使用.ashx擴充。
建立頁面
現在我們可以開始編碼了。建立一個新頁面或者開啟已有的頁面,在file後的代碼中,為Page_Load事件添加以下代碼:
public class Index : System.Web.UI.Page{
private void Page_Load(object sender, EventArgs e){
Ajax.Utility.RegisterTypeForAjax(typeof(Index));
//
}
//
}
調用RegisterTypeForAjax將在頁面上引發後面的JavaScript(或者在頁面中手工加入以下兩行代碼):
〈script language="javascript" src="ajax/common.ashx">〈/script>
〈script language="javascript"
src="ajax/Namespace.PageClass,AssemblyName.ashx">〈/script>
其中最後一行的含義是:
Namespace.PageClass——當前頁面的名稱空間和類(通常是@Page指令中Inherits屬性的值)
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路徑(查看原始碼,複製粘貼路徑)檢查是否一切正常。如果兩個路徑都輸出一些(似乎)毫無意義的文本,就萬事大吉了。如果什麼也沒輸出或者出現ASP.NET錯誤,則表明有些地方出現問題。
即便不知道HttpHandlers如何工作,上面的例子也很容易理解。通過web.config,我們已經保證所有對ajax/*.ashx的請求都由自訂的處理常式處理。顯然,這裡的兩個指令碼標籤將由自訂的處理常式處理。
建立伺服器端函數
現在來建立可從用戶端調用中非同步訪問的伺服器端函數。因為目前還不支援所有的傳回型別(不用擔心,將在目前的基礎上開發新的版本),我們繼續使用簡單的ServerSideAdd函數吧。在file後的代碼中,向頁面添加下列代碼:
[Ajax.AjaxMethod()]
public int ServerSideAdd(int firstNumber, int secondNumber)
{
return firstNumber + secondNumber;
}
要注意,這些函數具有Ajax.AjaxMethod屬性集。該屬性告訴封裝器這些方法建立javaScript代理,以便在用戶端調用。
用戶端調用
最後一步是用JavaScript調用該函數。AJAX封裝器負責建立帶有兩個參數的JavaScript函數Sample.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>
當然,我們不希望僅僅用這種強大的能力來警告使用者。這就是所有用戶端代理(如JavaScript Sample.ServerSideAd函數)還接受其他特性的原因。這種特性就是為了處理響應而調用的回呼函數:
Sample.ServerSideAdd(100,99, ServerSideAdd_CallBack);
function ServerSideAdd_CallBack(response){
if (response.error != null){
alert(response.error);
return;
}
alert(response.value);
}
從上述代碼中可以看到我們指定了另外一個參數。ServerSideAdd_CallBack(同樣參見上述代碼)是用於處理伺服器響應的用戶端函數。這個回呼函數接收一個響應對象,該對象公開了三個主要性質
Value——伺服器端函數實際返回的值(無論是字串、自訂對象還是資料集)。
Error——錯誤訊息,如果有的話。
Request——xml http請求的原始響應。
Context——內容物件。
首先我們檢查error只看看是否出現了錯誤。通過在伺服器端函數中拋出異常,可以很容易處理error特性。在這個簡化的例子中,然後用這個值警告使用者。Request特性可用於獲得更多資訊(參見下一節)。
處理類型
返回複雜類型
Ajax封裝器不僅能處理ServerSideAdd函數所返回的整數。它目前還支援integers、strings、double、booleans、DateTime、DataSets和DataTables,以及自訂類和數組等基本類型。其他所有類型都返回它們的ToString值。
返回的DataSets和真正的.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] = "〈/table>";
tableDisplay.innerHTML = s.join("");
}
else {
alert("Error. [3001] " + response.request.responseText);
}
}
〈/script>
Ajax還可以返回自訂類,唯一的要求是必須用Serializable屬性標記。假設有如下的類:
[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和UserId)。
自訂轉換器
我們已經看到,Ajax .NET封裝器能夠處理很多不同的.NET類型。但是除了大量.NET類和內建類型以外,封裝器對不能正確返回的其他類型僅僅調用ToString()。為了避免這種情況,Ajax .NET封裝器允許開發人員建立對象轉換器,用於在伺服器和客戶機之間平滑傳遞複雜物件。
其他事項
在其他類中註冊函數
上面的例子中,我們的伺服器端函數都放在執行頁面背後的代碼中。但是,沒有理由不能把這些函數放在單獨的類檔案中。要記住,封裝器的工作方式是在指定類中發現所有帶Ajax.AjaxMethod的方法。需要的類通過第二個指令碼標籤指定。使用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
通過指定類的類型而不是頁面就可以讓Ajax封裝器建立代理:
private void Page_Load(object sender, EventArgs e){
Ajax.Utility.RegisterTypeForAjax(typeof(AjaxFunctions));
//
}
要記住,用戶端代理的名稱是〈ClassName>.〈ServerSideFunctionName>。因此,如果ServerSideAdd函數放在上面虛構的AjaxFunctions類中,用戶端調用就應該是: AjaxFunctions.ServerSideAdd(1,2)。
代理到底是如何工作的
Ajax工具產生的第二個指令碼標籤(也可以手工插入)傳遞了頁面的名稱空間、類名和程式集。根據這些資訊,Ajax.PageHandlerFactory就能夠使用反射得到具有特定屬性的任何函數的詳細資料。顯然,處理函數尋找具有AjaxMethod屬性的函數並得到它們的簽名(傳回型別、名稱和參數),從能夠建立必要的用戶端代理。具體而言,封裝器建立一個和類同名的JavaScript對象,該對象提供代理。換句話說,給定一個帶有Ajax ServerSideAdd方法的伺服器端類AjaxFunctions,我們就會得到公開ServerSideAdd函數的AjaxFunction 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;
}
SessionState
伺服器端函數中很可能需要訪問會話資訊。為此,只需要通過傳遞給Ajax.AjaxMethod屬性的一個參數告訴Ajax啟用這種功能。
在考察封裝器會話能力的同時,我們來看看其他幾個特性。這個例子中我們有一個文件管理系統,使用者編輯的時候會對文檔加鎖。其他使用者可以請求在文檔可用的時候得到通知。如果沒有AJAX,我們就只能等待該使用者再次返回來檢查請求的文檔是否可用。顯然不夠理想。使用支援工作階段狀態的Ajax就非常簡單了。
首先來編寫伺服器端函數,目標是迴圈遍曆使用者希望編輯的documentId(儲存在會話中)並返回所有已釋放的文檔。
[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和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); ">
結束語
AJAX技術已經催生了原來只有案頭開發才具備的健壯而豐富的Web介面。Ajax .NET封裝器讓您很容易就能利用這種新的強大技術。請注意,Ajax .NET封裝器和文檔仍在開發之中。