Asynchronous JavaScript and XML(AJAX)最近掀起的高潮,要完全歸功于Google在Google Suggest和Google Maps中的使用。對ASP.NET而言,AJAX不需要回傳就能進(jìn)行服務(wù)器端處理,從而使客戶(hù)機(瀏覽器)具有豐富的服務(wù)器端能力。換句話(huà)說(shuō),它為異步指派和處理請求與服務(wù)器響應提供了一個(gè)框架。AJAX利用了一些不是很新穎的已有技術(shù),但是對這些技術(shù)(加到一起就是AJAX)的愛(ài)好最近突然升溫。
請嘗試Michael Schwarz的AJAX .NET包裝器,通過(guò)它ASP.NET開(kāi)發(fā)人員可以快速方便的部署很容易利用AJAX功能的頁(yè)面。需要注意的是,這個(gè)包裝器處于初期開(kāi)發(fā)階段,因此還沒(méi)有完全成熟。
然而,AJAX這樣的技術(shù)很可能破壞分層體系結構(N-Tier)。我的看法是,AJAX增加了表示邏輯層(甚至更糟,業(yè)務(wù)層)滲透到表示層的可能性。像我這樣嚴肅的架構師對這種想法可能畏步不前。我感到AJAX的使用即便稍微越過(guò)了層次邊界,這種代價(jià)也是值得深思的。當然,這要視具體的項目和環(huán)境而定。
起步
它是如何工作的——概述
AJAX依靠代理(broker)指派和處理往返服務(wù)器的請求。對此,.NET包裝器依靠客戶(hù)端XmlHttpRequest對象。多數瀏覽器都支持XmlHttpRequest對象,這就是選擇它的原因。因為包裝器的目的是隱藏XmlHttpRequest的實(shí)現,我們就不再詳細討論它了。
包裝器本身通過(guò)將.NET函數標記為AJAX方法來(lái)工作。標記之后,AJAX就創(chuàng )建對應的JavaScript函數,這些函數(和任何JavaScript函數一樣)作為代理可以在客戶(hù)端使用XmlHttpRequest調用。這些代理再映射回服務(wù)器端函數。
復雜嗎?并不復雜。我們來(lái)看一個(gè)例子。假設有一個(gè).NET函數:
ublic int Add(int firstNumber, int secondNumber)
{
return firstNumber + secondNumber;
}
AJAX .NET包裝器將自動(dòng)創(chuàng )建名為“Add”、帶有兩個(gè)參數的JavaScript函數。使用JavaScript(在客戶(hù)機上)調用該函數時(shí),請求將傳遞給服務(wù)器并把結果返回給客戶(hù)機。
初始設置
我們首先介紹“安裝”項目中使用的.dll的步驟。如果您很清楚如何添加.dll文件引用,可以跳過(guò)這一節。
首先,如果還沒(méi)有的話(huà),請下載最新的AJAX版本。解壓下載的文件并把ajax.dll放到項目的引用文件夾中。在Visual Studio.NET中有機Solution Explorer的“References(引用)”節點(diǎn)并選擇Add Reference(添加引用)。在打開(kāi)的對話(huà)框中,單擊Browse(瀏覽)并找到ref/ajax.dll文件。依次單擊Open(打開(kāi))和Ok(確認)。這樣就可以用AJAX .NET包裝器編程了。
建立HttpHandler
為了保證正常工作,第一步是在web.config中設置包裝器的HttpHandler。不需要詳細解釋HttpHandlers是什么及其如何工作,只要知道它們用于處理ASP.NET請求就足夠了。比如,所有*.aspx頁(yè)面請求都由System.Web.UI.PageHandlerFactory類(lèi)處理。類(lèi)似的,我們讓所有對ajax/*.ashx的請求由Ajax.PageHandlerFactory處理:
<configuration>
<system.web>
<httpHandlers>
<add verb="POST,GET" path=" ajax/*.ashx"
type="Ajax.PageHandlerFactory, Ajax" />
</httpHandlers>
...
<system.web>
</configuration>
簡(jiǎn)言之,上面的代碼告訴ASP.NET,和指定路徑(ajax/*.ashx)匹配的任何請求都由Ajax.PageHandlerFactory而不是默認處理程序工廠(chǎng)來(lái)處理。不需要創(chuàng )建ajax子目錄,使用這個(gè)神秘的目錄只是為了讓其他HttpHandlers能夠在自己建立的子目錄中使用.ashx擴展。
建立頁(yè)面
現在我們可以開(kāi)始編碼了。創(chuàng )建一個(gè)新頁(yè)面或者打開(kāi)已有的頁(yè)面,在file后的代碼中,為Page_Load事件添加以下代碼:
public class Index : System.Web.UI.Page{
private void Page_Load(object sender, EventArgs e){
Ajax.Utility.RegisterTypeForAjax(typeof(Index));
//...
}
//...
}
調用RegisterTypeForAjax將在頁(yè)面上引發(fā)后面的JavaScript(或者在頁(yè)面中手工加入以下兩行代碼):
<script language="javascript" src="ajax/common.ashx"></script>
<script language="javascript"
src="ajax/Namespace.PageClass, AssemblyName.ashx"></script>
其中最后一行的含義是:
下面是AjaxPlay項目中sample.aspx頁(yè)面的結果例子:
<%@ 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路徑(查看源代碼,復制粘貼路徑)檢查是否一切正常。如果兩個(gè)路徑都輸出一些(似乎)毫無(wú)意義的文本,就萬(wàn)事大吉了。如果什么也沒(méi)輸出或者出現ASP.NET錯誤,則表明有些地方出現問(wèn)題。
即便不知道HttpHandlers如何工作,上面的例子也很容易理解。通過(guò)web.config,我們已經(jīng)保證所有對ajax/*.ashx的請求都由自定義的處理程序處理。顯然,這里的兩個(gè)腳本標簽將由自定義的處理程序處理。
創(chuàng )建服務(wù)器端函數
現在來(lái)創(chuàng )建可從客戶(hù)端調用中異步訪(fǎng)問(wèn)的服務(wù)器端函數。因為目前還不支持所有的返回類(lèi)型(不用擔心,將在目前的基礎上開(kāi)發(fā)新的版本),我們繼續使用簡(jiǎn)單的ServerSideAdd函數吧。在file后的代碼中,向頁(yè)面添加下列代碼:
[Ajax.AjaxMethod()]
public int ServerSideAdd(int firstNumber, int secondNumber)
{
return firstNumber + secondNumber;
}
要注意,這些函數具有Ajax.AjaxMethod屬性集。該屬性告訴包裝器這些方法創(chuàng )建javaScript代理,以便在客戶(hù)端調用。
客戶(hù)端調用
最后一步是用JavaScript調用該函數。AJAX包裝器負責創(chuàng )建帶有兩個(gè)參數的JavaScript函數Sample.ServerSideAdd。對這種最簡(jiǎn)單的函數,只需要調用該方法并傳遞兩個(gè)數字:
<%@ 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>
當然,我們不希望僅僅用這種強大的能力來(lái)警告用戶(hù)。這就是所有客戶(hù)端代理(如JavaScript Sample.ServerSideAd函數)還接受其他特性的原因。這種特性就是為了處理響應而調用的回調函數:
Sample.ServerSideAdd(100,99, ServerSideAdd_CallBack);
function ServerSideAdd_CallBack(response){
if (response.error != null){
alert(response.error);
return;
}
alert(response.value);
}
從上述代碼中可以看到我們指定了另外一個(gè)參數。ServerSideAdd_CallBack(同樣參見(jiàn)上述代碼)是用于處理服務(wù)器響應的客戶(hù)端函數。這個(gè)回調函數接收一個(gè)響應對象,該對象公開(kāi)了三個(gè)主要性質(zhì)
首先我們檢查error只看看是否出現了錯誤。通過(guò)在服務(wù)器端函數中拋出異常,可以很容易處理error特性。在這個(gè)簡(jiǎn)化的例子中,然后用這個(gè)值警告用戶(hù)。Request特性可用于獲得更多信息(參見(jiàn)下一節)。
處理類(lèi)型
返回復雜類(lèi)型
Ajax包裝器不僅能處理ServerSideAdd函數所返回的整數。它目前還支持integers、strings、double、booleans、DateTime、DataSets和DataTables,以及自定義類(lèi)和數組等基本類(lèi)型。其他所有類(lèi)型都返回它們的ToString值。
返回的DataSets和真正的.NET DataSet差不多。假設一個(gè)服務(wù)器端函數返回DataSet,我們可以通過(guò)下面的代碼在客戶(hù)端顯示其中的內容:
<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還可以返回自定義類(lèi),唯一的要求是必須用Serializable屬性標記。假設有如下的類(lèi):
[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");
}
}
我們可以通過(guò)調用RegisterTypeForAjax注冊GetUser代理:
private void Page_Load(object sender, EventArgs e){
Utility.RegisterTypeForAjax(typeof(User));
}
這樣就可以在客戶(hù)端異步調用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>
響應中返回的值實(shí)際上是一個(gè)對象,公開(kāi)了和服務(wù)器端對象相同的屬性(FirstName、LastName和UserId)。
自定義轉換器
我們已經(jīng)看到,Ajax .NET包裝器能夠處理很多不同的.NET類(lèi)型。但是除了大量.NET類(lèi)和內建類(lèi)型以外,包裝器對不能正確返回的其他類(lèi)型僅僅調用ToString()。為了避免這種情況,Ajax .NET包裝器允許開(kāi)發(fā)人員創(chuàng )建對象轉換器,用于在服務(wù)器和客戶(hù)機之間平滑傳遞復雜對象。
其他事項
在其他類(lèi)中注冊函數
上面的例子中,我們的服務(wù)器端函數都放在執行頁(yè)面背后的代碼中。但是,沒(méi)有理由不能把這些函數放在單獨的類(lèi)文件中。要記住,包裝器的工作方式是在指定類(lèi)中發(fā)現所有帶Ajax.AjaxMethod的方法。需要的類(lèi)通過(guò)第二個(gè)腳本標簽指定。使用Ajax.Utility.RegisterTypeForAjax,我們可以指定需要的任何類(lèi)。比如,將我們的服務(wù)器端函數作為單獨的類(lèi)是合情合理的:
Public Class AjaxFunctions
<Ajax.AjaxMethod()> _
Public Function Validate(username As String, password As String) As Boolean
‘do something
‘Return something
End Function
End Class
通過(guò)指定類(lèi)的類(lèi)型而不是頁(yè)面就可以讓Ajax包裝器創(chuàng )建代理:
private void Page_Load(object sender, EventArgs e){
Ajax.Utility.RegisterTypeForAjax(typeof(AjaxFunctions));
//...
}
要記住,客戶(hù)端代理的名稱(chēng)是<ClassName>.<ServerSideFunctionName> 。因此,如果ServerSideAdd函數放在上面虛構的AjaxFunctions類(lèi)中,客戶(hù)端調用就應該是: AjaxFunctions.ServerSideAdd(1,2) 。
代理到底是如何工作的
Ajax工具生成的第二個(gè)腳本標簽(也可以手工插入)傳遞了頁(yè)面的名稱(chēng)空間、類(lèi)名和程序集。根據這些信息,Ajax.PageHandlerFactory就能夠使用反射得到具有特定屬性的任何函數的詳細信息。顯然,處理函數查找具有AjaxMethod屬性的函數并得到它們的簽名(返回類(lèi)型、名稱(chēng)和參數),從能夠創(chuàng )建必要的客戶(hù)端代理。具體而言,包裝器創(chuàng )建一個(gè)和類(lèi)同名的JavaScript對象,該對象提供代理。換句話(huà)說(shuō),給定一個(gè)帶有Ajax ServerSideAdd方法的服務(wù)器端類(lèi)AjaxFunctions,我們就會(huì )得到公開(kāi)ServerSideAdd函數的AjaxFunction JavaScript對象。如果將瀏覽器指向第二個(gè)腳本標簽的路徑就會(huì )看到這種動(dòng)作。
返回Unicode字符
Ajax .NET包裝器能夠從服務(wù)器向客戶(hù)機返回Unicode字符。為此,數據在返回之前必須在服務(wù)器上用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
服務(wù)器端函數中很可能需要訪(fǎng)問(wèn)會(huì )話(huà)信息。為此,只需要通過(guò)傳遞給Ajax.AjaxMethod屬性的一個(gè)參數告訴Ajax啟用這種功能。
在考察包裝器會(huì )話(huà)能力的同時(shí),我們來(lái)看看其他幾個(gè)特性。這個(gè)例子中我們有一個(gè)文檔管理系統,用戶(hù)編輯的時(shí)候會(huì )對文檔加鎖。其他用戶(hù)可以請求在文檔可用的時(shí)候得到通知。如果沒(méi)有AJAX,我們就只能等待該用戶(hù)再次返回來(lái)檢查請求的文檔是否可用。顯然不夠理想。使用支持會(huì )話(huà)狀態(tài)的Ajax就非常簡(jiǎn)單了。
首先來(lái)編寫(xiě)服務(wù)器端函數,目標是循環(huán)遍歷用戶(hù)希望編輯的documentId(保存在會(huì )話(huà)中)并返回所有已釋放的文檔。
[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)。
現在編寫(xiě)使用該方法的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);">
我們的服務(wù)器端函數在頁(yè)面加載時(shí)調用一次,然后每隔10秒鐘調用一次?;卣{函數檢查響應看看是否有返回值,有的話(huà)則在div標簽中顯示該用戶(hù)可使用的新文檔。
結束語(yǔ)
AJAX技術(shù)已經(jīng)催生了原來(lái)只有桌面開(kāi)發(fā)才具備的健壯而豐富的Web界面。Ajax .NET包裝器讓您很容易就能利用這種新的強大技術(shù)。請注意,Ajax .NET包裝器和文檔仍在開(kāi)發(fā)之中。
聯(lián)系客服