Asp.Net 和 AJAX.Net 的區(qū)別
這與您通常使用的任何其他函數(shù)一樣:它需要我們想要獲得的國家/地區(qū)的 ID,并將該請求傳遞給 DAL。唯一的不同是我們已使用 AjaxMethodAttribute 標(biāo)記了該方法。最后剩余的服務(wù)器端步驟是通過調(diào)用 RegisterTypeForAjax 使用 Ajax.NET 來注冊包含上述方法的類(在此情況下,是我們的下面的代碼)。
//C# Ajax.Utility.RegisterTypeForAjax(typeof(Sample)); 'VB.NET Ajax.Utility.RegisterTypeForAjax(GetType(Sample))
我們已基本完成;剩余的就是從 JavaScript 調(diào)用 GetStates 方法和處理響應(yīng)。當(dāng)用戶從國家/地區(qū)列表中選擇新項時,我們想在邏輯上調(diào)用 GetStates。為此,我們將觸發(fā) JavaScript onChange 事件。這樣就稍微更改了我們的 Web 窗體代碼。
<asp:DropDownList onChange="LoadStates(this)" ID="countries" Runat="server" />
JavaScript LoadStates 函數(shù)將負(fù)責(zé)通過由 Ajax.NET 創(chuàng)建的代理發(fā)出異步請求。請記住,默認(rèn)情況下,Ajax.NET 創(chuàng)建的代理的格式為 <RegisteredTypeName>.<ServerSideMethodName>。在我們的示例中,將為 Sample.GetStates。我們還想傳入國家/地區(qū) ID 參數(shù)和完成服務(wù)器端函數(shù)后 Ajax.NET 應(yīng)調(diào)用的回調(diào)函數(shù)。
//JavaScript function LoadStates(countries) { var countryId = countries.options[countries.selectedIndex].value; Sample.GetStates(countryId, LoadStates_CallBack); }
最后一個步驟是處理我們的 LoadStates_CallBack 函數(shù)中的響應(yīng)。Ajax.NET 最有用的功能大概是它支持很多 .NET 類型(我已經(jīng)多次提到這一點)?;仡櫼幌路祷?DataView 的服務(wù)端函數(shù)。JavaScript 知道 DataView 什么?什么也不知道,但是 JavaScript 是面向?qū)ο蟮恼Z言,而且 Ajax.NET 不只能夠創(chuàng)建與 .NET DataView 相似的對象,還能將該函數(shù)返回的值映射到 JavaScript 副本。您應(yīng)該記住 JavaScript DataView 只不過是實際 DataView 的副本,目前除了能夠遍歷行和訪問列值以外不支持其他更多功能(例如設(shè)置 RowFilter 或 Sort 屬性的功能)。
function LoadStates_CallBack(response) { //如果服務(wù)器端代碼出現(xiàn)異常 if (response.error != null) { //我們應(yīng)該能做得更好 alert(response.error); return; } var states = response.value; //如果不是我們所希望的響應(yīng) if (states == null || typeof(states) != "object") { return; } //獲得州下拉列表 var statesList = document.getElementById("states"); statesList.options.length = 0; //重置州下拉列表 //記住,其長度不是 JavaScript 中的 Length for (var i = 0; i < states.length; ++i) { //如命名屬性一樣公開行的列 statesList.options[statesList.options.length] = new Option(states[i].State, states[i].Id); } }
經(jīng)過一些錯誤檢查之后,前面的 JavaScript 獲得州下拉列表,遍歷響應(yīng)的值,并動態(tài)地將選項添加到該下拉列表中。代碼清晰、簡單并與 C# 和 Visual Basic .NET 非常相似。就我個人而言(作為基于服務(wù)器端變量創(chuàng)建了 JavaScript 數(shù)組并將它們鏈接在一起的開發(fā)人員),我還要一段時間才能相信它真的起作用了。
有一個可能不太明顯的主要問題。由于 DropDownList 是在 JavaScript 中動態(tài)創(chuàng)建的,因此它的項不屬于 ViewState,并且不被維護(hù)。這意味著按鈕的 OnClick 事件處理程序需要進(jìn)行一些額外的修改。
'VB.NET Private Sub submit_Click(sender As Object, e As EventArgs) Dim selectedStateId As String = Request.Form(states.UniqueID) '應(yīng)進(jìn)行一些用戶驗證... states.DataSource = DAL.GetCountryStates(Convert.ToInt32(countries.SelectedIndex)) states.DataTextField = "State" states.DataValueField = "Id" states.DataBind() states.SelectedIndex = states.Items.IndexOf(states.Items.FindByValue(selectedStateId)) End Sub
首先,我們不能使用 states.SelectedValue 屬性,而必須使用 Request.Form。其次,如果我們想向用戶重新顯示該列表,需要重新使用相同的數(shù)據(jù)訪問方法綁定州 DropDownList。最后,必須以編程方式設(shè)置選定的值。
示例 2:文檔鎖定程序
對于下一個示例,我們將引入更加完整的功能,然后使用 AJAX 改進(jìn)它。此示例屬于簡單的文檔管理系統(tǒng)。如任何正式的文檔管理系統(tǒng)一樣,我們必須提供并發(fā)管理。即,我們需要一種方法來處理兩個用戶嘗試編輯同一個文檔的問題。我們將通過創(chuàng)建某種類型的鎖定機(jī)制,來使正在編輯的文檔不能再由另一個用戶編輯,從而達(dá)到上述目的。我們將利用 AJAX 讓用戶有更愉快的鎖定機(jī)制體驗。首先,我們將創(chuàng)建用戶嘗試編輯但無法編輯(因為其他用戶正在編輯該文檔)的文檔隊列,當(dāng)這些文檔可用時自動通知用戶。其次,我們將確保當(dāng)用戶關(guān)閉其瀏覽器或?qū)Ш降狡渌恢脮r,解除對文檔的鎖定。后一個功能幫助確保文檔不會永遠(yuǎn)處于鎖定狀態(tài)。為此,在本指南中,我們將跳過與 AJAX 實現(xiàn)不相關(guān)的功能;但是,可下載項目包含所有功能。
首先,當(dāng)用戶嘗試編輯文檔時,我們會嘗試對其建立排它鎖,如果失敗,我們會將此文檔添加到用戶的隊列然后使其返回到主頁。對此處的 AJAX 沒有什么特別之處,但是我們將查看一下代碼,以便給出示例必要的上下文。在用于編輯的 Page 的 OnLoad 事件中,添加以下代碼。
//C# if (!Page.IsPostBack) { //應(yīng)驗證用戶輸入 Document document = GetDocument(Request.QueryString["id"]); //我們擁有此文檔,但不能編輯它! if (!Locker.AcquireLock(document)) { //讓我們將它添加到要查看的用戶文檔列表 User.CurrentUser.AddDocumentToQueue(document.DocumentId); Response.Redirect("DocumentList.aspx"); } //好了,我們擁有此文檔,并且可以編輯它 //... }
關(guān)鍵行的位置是將文檔添加到當(dāng)前用戶的隊列中(這會將文檔添加到會話中)。接下來,我們將創(chuàng)建用戶控件,該控件可以被放置到任何頁上,用于當(dāng)隊列文檔可用時通知用戶。此用戶控件將包含一個 AJAX 方法以及注冊 AJAX 的類所需的代碼。
'VB.NET Private Sub Page_Load(s As Object, e As EventArgs) Handles MyBase.Load Ajax.Utility.RegisterTypeForAjax(GetType(UnlockNotifier)) End Sub '遍歷隊列文檔并檢查它們是否可用 <Ajax.AjaxMethod()> _ Public Function GetUnlockedDocuments() As DocumentCollection '獲得屬于用戶的所有隊列文檔的 ID Dim queuedDocument As ArrayList = User.CurrentUser.DocumentQueue Dim unlocked As DocumentCollection = New DocumentCollection For Each documentId As Integer In queuedDocumentIds '如果隊列文檔不再被鎖定 If Not Locker.IsLocked(documentId) Then unlocked.Add(Document.GetDocumentById(documentId)) End If Next Return unlockedDocuments End Function
現(xiàn)在需要的是使一些 JavaScript 發(fā)出請求并處理響應(yīng)。我們將基于響應(yīng)在要動態(tài)創(chuàng)建的表中放置已發(fā)布的文檔信息(如果有)。為此,我們將開始編寫 HTML。
<div id="notifyBox" style="display:none;"> <b>The following queued documents can now be edited</b> <table cellpadding="5" cellspacing="0" border="0" style="border:1px solid #EEE;" id="notifyTable"> </table> </div>
如果沒有可用的文檔(或是沒有為該用戶列出文檔),我們使用 DIV 標(biāo)記隱藏所有內(nèi)容,用 TABLE 標(biāo)記來顯示結(jié)果。我們將使用輪詢系統(tǒng)來檢查是否存在任何可用的隊列文檔。一般來說,這意味著我們將在稍后一段時間內(nèi)一直調(diào)用服務(wù)器端方法,并顯示結(jié)果。在加載頁面時僅發(fā)生第一次調(diào)用,每隔 X 秒發(fā)生后續(xù)調(diào)用。
<script language="javascript"> window.setTimeout("PollQueue();", 2000); //每隔 2 秒激發(fā)以檢查在具有許多用戶的實際系統(tǒng)中是否發(fā)布了 //隊列文檔,2 秒可能會使服務(wù)器承受 //過高的負(fù)荷。我們甚至可以首先檢查用戶是否 //擁有隊列,但是我們確實需要進(jìn)行一些 //性能測試 function PollQueue() { //UnlockNotifier 是我們使用 Ajax.NET 注冊的類型 //GetUnlockedDocuments 是該類型中的方法,標(biāo)有 //AjaxMethod 屬性 UnlockNotifier.GetUnlockedDocuments(PollQueue_CallBack); //每隔 2 秒調(diào)用其本身 window.setTimeout("PollQueue();", 2000); } </script>
剩下的就是處理響應(yīng)。這與以前示例中的代碼相似。首先,檢查是否存在錯誤,獲得響應(yīng),遍歷可用的文檔,動態(tài)創(chuàng)建 HTML,在這種情況下,向表中添加行和列。
function PollQueue_CallBack(response) { var notifyBox = document.getElementById("notifyBox"); var notifyTable = document.getElementById("notifyTable"); //如果我們無法找到表通知框 if (notifyBox == null || notifyTable == null) { return; } //如果服務(wù)器端代碼出現(xiàn)異常 if (response.error != null) { notifyBox.style.display = "none"; alert(response.error); //我們應(yīng)該能做得更好 return; } var documents = response.value; //如果不是我們所希望的響應(yīng) if (documents == null || typeof(documents) != "object") { notifyBox.style.display = "none"; return; } for (var i = 0; i < notifyTable.rows.length; ++i) { notifyTable.deleteRow(i); } for(var i = 0; i < documents.length; ++i) { var row = notifyTable.insertRow(0); row.className = "Row" + i%2; var cell = row.insertCell(0); cell.innerHTML = documents[i].Title; cell = row.insertCell(1); var date = documents[i].Created; cell.innerHTML = date.getDay() + "/" + date.getMonth() + "/" + date.getYear(); cell = row.insertCell(2); cell.innerHTML = "<a href='DocumentEdit.aspx?id=" + documents[i].DocumentId + "'>edit</a>"; } notifyBox.style.display = "block"; }
我們要看到的最后一個快速改進(jìn)是當(dāng)用戶關(guān)閉瀏覽器、導(dǎo)航到其他鏈接或單擊“后退”按鈕時,將自動解除文檔鎖定。通常,可以通過觸發(fā) JavaScript OnBeforeUnLoad 事件或 OnUnload 事件達(dá)到此目的,這會打開新的小型彈出式窗口,該彈出式窗口在加載頁面時做一些清理然后自行關(guān)閉。您自己可以使用彈出式窗口,但是其他人則不能使用,它將導(dǎo)致彈出式窗口受阻并使文檔永久保持鎖定狀態(tài)。要解決此問題,我們?nèi)孕枰獌蓚€ JavaScript 事件,但是并不是啟動彈出式窗口,而是將通過 AJAX 執(zhí)行服務(wù)器端方法。在用于編輯文檔的頁上(即,放置鎖的頁),我們添加一些簡單的 JavaScript。
<script language="javascript"> //如果用戶關(guān)閉瀏覽器或點擊“后退”按鈕, //確保該文檔會被解除鎖定 window.onbeforeunload = ReleaseLock; function ReleaseLock() { Locker.ReleaseDocument(<%=DocumentID%>); } </script>
在這里,DocumentId 是在后面的代碼中定義和設(shè)置的變量。另外,我們可以在會話中存儲 DocumentId,并在服務(wù)器端 ReleaseDocument 中訪問。通常,ReleaseDocument 從鎖定的文檔列表中刪除文檔。
示例 3:論壇主題搜索
我們要看的最后一個示例是對現(xiàn)有應(yīng)用程序的修改。我首次聽到這個想法是 Josh Ledgard(英文)設(shè)想在 MSDN 論壇(英文)里添加一個功能。目的是嘗試幫助用戶自己找到問題的答案,以及限制重復(fù)發(fā)布的數(shù)量。一般來說,用戶在論壇中提出新問題時,他或她會輸入主題和問題。他們通常都不會先進(jìn)行搜索,來查看是否已經(jīng)提出和回答過該問題。輸入 AJAX。用戶輸入主題(并將 Tab 鍵移出該字段)后,我們基于該主題異步搜索論壇,并適時地向用戶顯示結(jié)果。有時這些結(jié)果會有幫助,有時候則不會。
為使結(jié)果有幫助,我們將修改 asp.NETPRO Reader's Choice Award for Best Forum Application, CommunityServer。可下載的示例中不包含這一部分(或論壇)的代碼,但是您能在 http://communityserver.org/(英文)學(xué)到關(guān)于 CommunityServer 的更多知識,并且可在其中應(yīng)用下面這些代碼片斷。
安裝 CommunityServer 并配置 Ajax.NET(已將引用和處理程序添加到 web.config)后,我們只需要進(jìn)行一些更改就可以獲得所需的功能。首先,我們轉(zhuǎn)到 CommunityServerForums 項目中的 CreateEditPost.cs 文件。將它視為此頁的后面的代碼,其中用戶可以添加新的發(fā)布。下面我們將添加啟用了 AJAX 的函數(shù)。
//C# [Ajax.AjaxMethod()] public static ArrayList Search(string search) { SearchQuery query = new SearchQuery(); query.PageIndex = 0; //獲得前 10 個結(jié)果 query.PageSize = 10; query.UserID = Users.GetUser().UserID; query.SearchTerms = search; return new ForumSearch().GetSearchResults(query).Posts; }
我們能夠利用已經(jīng)在 CommunityServer 中創(chuàng)建的搜索功能,只需要我們的函數(shù)能應(yīng)用它。如往常一樣,類型必須使用 Ajax.NET 注冊。我們將在同一文件的 InitializeSkin 函數(shù)(將其視為 Page_Load)中進(jìn)行此操作。
//C# Ajax.Utility.RegisterTypeForAjax(typeof(CreateEditPost));
在轉(zhuǎn)跳到 JavaScript 之前,我們需要進(jìn)行最后的服務(wù)器端更改。返回到 Ajax.NET 的自定義類(例如我們正在返回的 ArrayList 所包含的 ForumPost)必須標(biāo)有 Serializable 屬性。我們要做的是轉(zhuǎn)到 CommunityServerForums 項目中的 Components/ForumPost.cs 文件,并添加此屬性。
//C# [Serializable] public class ForumPost :Post { ... }
顯示時,我們僅需要修改 CommunityServerWeb 項目中的 Themes/default/Skins/View-EditCreatePost.cs。首先,我們將觸發(fā)主題文本框的 onBlur 事件。
<asp:textbox onBlur="Search(this.value);" id="PostSubject" runat="server" ... />
接著,我們編寫 JavaScript Search 方法,以便調(diào)用服務(wù)器端 Search。
var oldValue = ''; function Search(value) { //不要再次搜索剛搜索過的內(nèi)容 //如果用戶向后或向前移動 Tab 鍵將會發(fā)生 if (value != oldValue) { CreateEditPost.Search(value, Search_CallBack); oldValue = value; } }
最后,剩下的就是處理響應(yīng)。由于上一個示例介紹了在表中顯示結(jié)果的稍微正規(guī)的方式,我們將僅僅創(chuàng)建一些動態(tài)的 HTML,并將它粘貼到虛擬的 DIV 中。
function Search_CallBack(response) { //由于沒有結(jié)果時搜索功能將自動重定向, //因此,我們不能使用 response.error。 var results = response.value; //如果我們沒有獲得結(jié)果 if (results == null) { return; } //我們用于放置結(jié)果的 DIV var someDiv = document.getElementById("someDiv"); var html = ""; for (var i = 0; i < results.length; ++i) { var result = results[i]; html += "<a target=_blank href='" + result.PostID html += "/ShowPost.aspx'>"; html += result.Subject; html += "</a><br />" } someDiv.innerHTML = html; }
通過對 CommunityServer 應(yīng)用程序的三個文件(加上用于配置的 web.config)稍微進(jìn)行修改,我們可以添加一些非常有用的功能。但是,只向現(xiàn)有的應(yīng)用程序添加啟用 AJAX 功能時要小心操作。正在進(jìn)行實際搜索的預(yù)先存在的 ForumSearch 類可能并不是為我們介紹的使用類型設(shè)計的。我們的代碼很可能會導(dǎo)致執(zhí)行一些額外的搜索,影響可能會很顯著。
AJAX 與您
AJAX 如何以及哪里適合您的應(yīng)用程序,以及是否已經(jīng)存在要根據(jù)情況而定。盡管我們已經(jīng)看到使用 Ajax.NET 可以輕松地創(chuàng)建啟用 AJAX 的解決方案,但還存在一些其他的注意事項。一個需要著重關(guān)注的問題是對您的應(yīng)用程序的整體體系結(jié)構(gòu)和可維護(hù)性的影響。AJAX 會進(jìn)一步模糊系統(tǒng)的各層之間的界限,顯著影響顯示層、顯示邏輯層和業(yè)務(wù)層。這不是 AJAX 本身的問題,而是使用方式的問題。只要您知道它很容易導(dǎo)致各層之間的某些沖突,并適當(dāng)操作,就不會產(chǎn)生問題。
使用 AJAX 的應(yīng)用程序更難于維護(hù)嗎?答案主要取決于您已經(jīng)使用的 JavaScript 的數(shù)量,以及您組織和維護(hù)它的好壞程度。很多開發(fā)人員認(rèn)為 JavaScript 難于編寫、測試和調(diào)試(不是因為 JavaScript 本身,而是因為工具支持和開發(fā)人員的知識)。如果您當(dāng)前正在使用 JavaScript 實現(xiàn)鏈接的下拉列表,并切換到 AJAX,您的代碼可能較為容易維護(hù)(Ajax.NET 對 .NET 類型和數(shù)組的支持是重要原因)。但是,如果您使用返回方式來實現(xiàn),現(xiàn)在您將向您的應(yīng)用程序 (JavaScript) 引入嶄新的語言。您將必須處理這樣的情況:存在某些不參與 ViewState 的數(shù)據(jù)(這一點我們在按鈕單擊事件中可以看到)。
另一個需要考慮的是 AJAX 對您的網(wǎng)站可用性的影響。即使 AJAX 最后能創(chuàng)建響應(yīng)更及時的界面,開發(fā)人員仍需要注意兩件事情。首先,很明顯 AJAX 依賴 JavaScript。我們都知道一些用戶禁用 JavaScript,一些標(biāo)準(zhǔn)(例如加拿大政府 Common Look and Feel [考慮加拿大的 508])要求網(wǎng)站使用 JavaScript 來運行,或不使用它來運行。因此,您不應(yīng)假定 AJAX 功能正在運行。如果 AJAX 功能不可用,則應(yīng)使您的應(yīng)用程序退回到更普通的 Web 處理。其次,AJAX 應(yīng)用程序可能不熟悉(即使它具有較高級別)用戶習(xí)慣使用應(yīng)用程序的方式。例如,通過 AJAX 執(zhí)行不同功能的頁可能不以用戶認(rèn)為的方式表現(xiàn)“后退”按鈕、“收藏夾”菜單和其他瀏覽器功能。
結(jié)論
AJAX 不只是令人興奮的即將面世的技術(shù),它是具體的框架,在建立 Web 應(yīng)用程序時可以采用它來解決每天遇到的問題。Ajax.NET 使 ASP.NET 開發(fā)人員輕松掌握 AJAX。我們看到的三個示例和可下載的項目可以幫助您了解如何使用 AJAX 和 Ajax.NET。您還可以利用這些示例來嘗試一些自己的想法。AJAX 不僅可以創(chuàng)建簡潔和強(qiáng)大的應(yīng)用程序,它還可以使您提高客戶滿意度和競爭優(yōu)勢。正在討論的 Atlas 的一些高級概念可能顯著改進(jìn)我們提供的產(chǎn)品。就個人而言,我見過的最佳的 AJAX 實現(xiàn)非常輕便合適。您自己的實現(xiàn)應(yīng)為您的用戶提供同樣積極的體驗。但是,對于某個具體問題,記住 AJAX 可能不是唯一的解決方案,也可能不是最佳的解決方案?,F(xiàn)在,讓我們證明 ASP.NET 社區(qū)是首屈一指的,讓我們收拾屋子吧。
關(guān)于作者
Karl Seguin 將他的大部分時間用在 Microsoft ASP.NET 新聞組,幫助其他開發(fā)人員并尋找和編寫幫助性的主題。當(dāng)他不工作或不提供幫助時,他喜歡無情地清潔 Gnomish 災(zāi)難的 Azeroth。