ASP.NET AJAX(服务器回调)
?????? 如果只用純粹的 js ,你必須彌補 ASP.NET 服務器端抽象和有限的 HTML DOM 之間的鴻溝,這不簡單,沒有 VS 的智能提示和調試工具,編寫無錯的代碼和診斷錯誤都非常困難。由于各種突發事件及實現的差異,編寫能夠在所有現代瀏覽器上都正確運行的腳本代碼更是一項很大的挑戰。
?????? ASP.NET 的客戶端回調機制部分解決了這類問題,它們提供一個服務器端模型,讓你能夠生成某些需要的客戶端代碼(使用 XMLHttpRequest 對象執行異步請求的代碼)。但這個模型還很不完美,接口略顯笨拙,和頁面模型的整合不太平滑且沒有數據類型。你必須自行把傳輸信息序列化為字符串,還必須編寫 js 代碼來接收回調,并反序列化字符串更新頁面。總之:客戶端回調機制是構建具有 Ajax 特性控件的極佳工具,但對設計完整網頁卻不是那么有吸引力。
?
ASP.NET AJAX 簡介
?????? ASP.NET 開發人員還有另外一個選擇。使用 ASP.NET AJAX 工具包,它提供了一些特性,能夠幫助構建 Ajax 風格的頁面。
?????? ASP.NET AJAX 有兩個關鍵部分:客戶端部分和服務器端部分。
?????? 客戶端部分是一組 JavaScript 庫。這些庫在任何方面都沒有綁定到 ASP.NET,非 ASP.NET 人員也可以在自己的 Web 頁面里使用它們。客戶端庫沒有提供太多的特性,不包含任何可以直接拖放到 Web 頁面的預先建立的功能。它們只是建立了對開發 ASP.NET AJAX 頁面的基礎。這個基礎擴展了 js(例如,增加了對繼承的支持),并提供一些基本框架(例如,管理組件生命周期的方法,對常見的數據類型進行操作,執行反射)。
?????? 服務器端部分則在一個更高的層次上工作。它包括那些使用客戶端 js 庫的控件和組件。例如,一個帶有 DragPanel(來自 ASP.NET AJAX 控件工具包)的 Web 表單能讓用戶在瀏覽器里隨意拖動面板,而在后臺,是一些自定義的 js 在工作,這些 js 又使用了客戶端的 ASP.NET AJAX 庫。不過,DragPanel 自動呈現它所需要的一切 js 代碼,這省去了你自己編寫它們的麻煩。
?
?????? 很明顯,ASP.NET AJAX 是 ASP.NET 開發新方向的開始。ASP.NET AJAX 提供的全部特性,大體如下:
- JavaScript 語言擴展:這些擴展使 js 更接近現代的面向對象語言,它們支持命名空間、繼承、接口、枚舉、反射。
- 遠程方法調用:ASP.NET AJAX 頁面可以調用 Web 服務。能夠不執行完整的頁面回發就能從服務器獲取到信息。它和客戶端回調機制解決了同一問題,但它可以在強類型的方法工作,不必把所有數據轉換為單一字符串。
- ASP.NET 服務:這項特性能夠調用服務器來使用兩個 ASP.NET 服務之一(一個使用表單驗證信息、而另一個從當前用戶配置文件獲取數據)
- 局部頁面刷新:新的 UpdatePanel 控件允許定義頁面的一部分,這部分頁面更新時不需要整個頁面回發,最妙的是,你不必編寫任何 js 代碼。
- 預先建立的控件:流行的 ASP.NET AJAX 控件工具包有 30 多個控件和控件擴展器,它們有助于充分利用 ASP.NET AJAX。你可以展開合起控件,添加動態動畫,使控件支持自動完成且可拖放,這類底層的 js 細節不需要你編寫任何代碼。
?
客戶端的 ASP.NET AJAX :腳本庫
?????? ASP.NET AJAX 的客戶端部分依賴于一小組 JavaScript 文件。有兩個辦法部署 ASP.NET AJAX 腳本文件:
- 構建 ASP.NET 3.5 應用程序,它們被嵌入在 System.Web.Extensions.dll 程序集并按需服務。
- 如果是非 ASP.NET 程序或為普通的 HTML 頁面添加客戶端特性,那么可以從 http://www.aps.net/ajax/downloads 單獨下載這些 js 文件,它們是 Microsoft AJAX Library 的一部分。
?????? 如果要仔細查看真正的 JavaScript 代碼,Microsoft AJAX Library 很值得下載。這個下載除了最終的生產版本外,還包含了 3 個核心文件的調試版本。為了減小文件,生成版本去掉了所有的空格和注釋。
?????? 如果下載了 Microsoft AJAX Library,你會發現其實只使用了 3 個核心的文件:MicrosoftAjax.js、MicrosoftAjaxWebForms.js、MicrosoftAjaxTimers.js。除了這些,還有 100 多個小文件,它們用于保存國際化信息。(例如,不同情況的數據格式)
?
?????? 在 ASP.NET 中,你找不到客戶端庫的單獨 JavaScript 文件,因為它被嵌入了程序集 System.Web.Extensions.dll 并為腳本資源提供服務。腳本資源能夠把一個 URL 映射到嵌入在程序集里的資源。類似下面這樣:
<script src="/Ajax/ScriptResource.axd?d=m4ocAED2VQt91uJZddDjK8_mxBry_xp7FrQEEm8C q4HQGsiwPw4ltli3kH0dLyw7XK9rPijWJ-Cp9yfeEzE8MBuoYhCitA3d6lxU0PL7-klAWWEqGEWaKu7Z j3UcCFFNbwZBk3lHslabQGmQuuCJ5A2&t=2a8ce630" type="text/javascript"></script>?????? ASP.NET 有一個腳本資源處理程序,它響應并處理這些請求,它檢查傳入的查詢字符串參數,返回請求的腳本文件。
?
服務器端的 ASP.NET AJAX :ScriptManager
?????? 很明顯,你不會愿意在每個需要 ASP.NET AJAX 的頁面輸入指向腳本資源的長長的 URL。解決辦法是使用一個叫做 ScriptManager 的 ASP.NET 控件。
?????? ScriptManager 是服務器端 ASP.NET AJAX 模型的核心。它是一個在頁面上沒有任何可視界面的 Web 控件。它執行一個主要任務:呈現到 ASP.NET AJAX JavaScript 庫的鏈接。
?????? 使用 ASP.NET AJAX 特性的每個頁面都需要且只需要一個 ScriptManager 實例。
?????? ScriptManager 還執行一些其他重要任務:
- 呈現對其他腳本文件的引用
- 創建能夠從瀏覽器調用 Web 服務的代理
- 管理 UpdatePanel 控件刷新內容的方式
?????? 如果整個網站使用 ASP.NET AJAX 特性,就應該把 ScriptManager 放到一個母版頁里。不過這偶爾會產生一些問題。不同的內容頁可能要以不同的方式配置 ScriptManager(例如添加新腳本和 Web 服務的引用)。遇到這樣的問題時,解決辦法是母版頁中使用 ScriptManager,而內容頁中使用 ScriptManagerProxy。
?
服務器回調
?????? 在 ASP.NET AJAX 里,回調總是通過一個單獨的服務器端方法實現(從技術上而言就是 Web 服務)。這個設計改良了對邏輯的分離,幫助組織代碼。更重要的是,它負責序列化工作。也就是說,你不必想方設法讓自己的方法發送復雜的數據(比如之前的示例使用管道符號分離值)。
?????? 在隨后的內容里,我們創建需要的 Web 服務,并考慮幾種使用它的方式。
?
ASP.NET AJAX 中的 Web 服務
?????? ASP.NET AJAX 執行服務器回調時,客戶端的 js 代碼調用服務器端 Web 服務的一個方法。
?????? Web 方法是一個或多個服務器方法的集合,可以被遠程客戶調用。客戶通過 HTTP 發送請求調用 Web 服務,和頁面回發的過程相似,不同的是請求的主體是要傳送給方法的參數。然后,ASP.NET 創建 Web 服務對象,運行對應的 Web 方法里的代碼,返回結果并釋放 Web 服務對象。請求和響應消息的格式可以變化,傳統上,它是一個叫做 SOAP 的基于 XML 的標準,但在 ASP.NET AJAX 里,它是輕量級的基于文本的 JSON(JavaScript Object Notation),這主要是出于瀏覽器兼容的原因。
?
ASMX 和 WCF Web 服務
?????? .NET 有兩種 Web 服務技術:.asmx 和 WCF。
?????? 同樣可以使用 WCF(Windows Communication Foundation) 服務作為 ASP.NET AJAX 頁面的后端。從概念上說,這種方法與使用普通的 .asmx Web 服務是一樣的。
?????? WCF 是 .asmx Web 服務的繼任者,是一個更加廣泛的平臺,包括了 .asmx Web 服務不支持的一組方案。然而,這些高級的方案對于 ASP.NET AJAX 頁面而言并不可用。從實用的角度而言,這兩種 Web 服務技術都針對 ASP.NET AJAX 頁面提供了完全一致的能力。
?
?????? 雖然 ASP.NET AJAX 回調機制使用 Web 服務,但這是一個特殊的實現,意識到這一點很重要。如果你熟悉 Web 服務,會發現 ASP.NET AJAX 強加了其他一些限制:
- Web 頁面不能調用非 ASP.NET AJAX Web 服務(例如,在其他平臺創建的第三方 Web 服務),這是因為它們不支持 ASP.NET AJAX 所使用的簡單 JSON 模型。
- Web 頁面不能調用其他域(在其他 Web 服務器上)里的 Web 服務。這是因為大多數瀏覽器都禁止跨域使用 XMLHttpRequest 對象,以防止潛在的跨站點腳本攻擊。
?????? 如果你使用 Web 服務面向富客戶端、第三方開發人員或非 .NET 應用程序公開服務器端功能,那么你需要知道 ASP.NET AJAX 里對 Web 服務的應用并不能完全滿足這些需求。有一些辦法可解決這些限制。例如,先調用 Web 應用程序里的某個 Web 方法,然后讓那個 Web 方法調用其他域里的 Web 方法。這項橋接技術之所以能夠工作,是因為 Web 服務器代碼并沒有像瀏覽器那樣的限制,它能夠自由地跨域調用其他 Web 服務。
?
1. 創建 Web 服務
?????? 添加一個新項目,選擇“Web 服務”模板。(如果創建的是一個無項目文件的網站,.asmx 文件將會被放到 Web 應用程序目錄,而對應的 .cs 文件則放在 App_Code 文件夾中,這樣它能夠自動被編譯。)
?????? .asmx 文件沒有什么特別的,如果你打開它,只會發現一行 WebService 指令,它標識代碼的語言、代碼隱藏文件的位置以及類的名字:
<%@ WebService Language="C#" CodeBehind="~/App_Code/TerritoriesService.cs" Class="TerritoriesService" %>?????? 該代碼隱藏類看起來和下面的代碼類似:
[WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.Web.Script.Services.ScriptService] public class TerritoriesService : System.Web.Services.WebService {...}?????? 默認情況下,ScriptService 特性被注釋掉了。創建 ASP.NET AJAX 頁面調用的 Web 服務時,必須使用此特性!
?????? 這個類從 System.Web.Services.WebService 派生,這是 Web 服務的傳統基類。繼承它是出于方便,而不是必須。通過從 WebService 繼承,不必經過靜態的 HttpContext.Current 屬性就能獲得直接訪問內置對象(如 Application、Session、Server、User 等)的便利。
?????? Web 服務類的聲明由 3 個特性進行修飾:
- WebService:設置 Web 服務消息里的 XML 命名空間
- WebServiceBinding:指定 Web 服務支持的標準級別,這只在通過 SOAP 消息調用時才有用
- ScriptService:它配置 Web 服務允許 JavaScript 客戶端的 JSON 調用。
?
2. 創建 Web 方法
?????? 每個 Web 服務都有一個或多個用 WebMethod 特性標注的方法,這個特性使方法能夠被遠程調用。如果添加的方法沒有包括 WebMethod 特性,服務器端的代碼還是可以使用它,但是客戶端的 JavaScript 就不能直接調用它了。
[WebMethod] public string DoSomething() {...}?????? 將方法標記為公有的(并非必須)是出于習慣和清晰性的要求,通常會這樣做。
?
?????? Web 方法有一定的限制。參數值和返回值的類型必須如下表:
| 基本類型 | 基本的 C# 數據類型、string、DateTime 等 |
| 枚舉 | C#里枚舉使用 enum 關鍵字,Web 服務使用枚舉的字符串(非底層整型) |
| 自定義對象 | 任何自定義類或結構的實例,唯一限制是只有公共數據成員和屬性被傳遞 |
| 數組和集合 | 必須是被支持類型的數組,ArrayList 也可以,但特殊集合如 HastTable 不可以。可以使用泛型,但集合中的元素必須能夠被序列化 |
| XmlNode | 這個對象是 XML 文檔某個區域的表示,你可以用它發送任意的 XML |
| DataSet 和 DataTable | 使用它們時被自動轉換為 XML。其他數據對象不支持如 DataRows 等。 |
?
?????? WebMethod 特性接收一組參數,其中大多數和 ASP.NET AJAX 沒有什么關系。但 EnableSession 屬性是一個例外,它的默認值是 false,因此 Web 服務里不能訪問會話狀態。對于非 ASP.NET AJAX 服務,這個默認值是有意義的,因為可能沒有任何會話信息,而客戶端也可能根本沒有保留任何回話 cookie。但對于 ASP.NET AJAX Web 服務,Web 服務調用總是來自 ASP.NET Web 頁面上下文,而它是在當前 Web 應用程序用戶的上下文中執行的,那個用戶有一個活動的會話并且會話 cookie 自動隨 Web 服務調用一起傳遞。
?????? 這個示例讓 Web 方法能夠訪問 Session 對象:
[WebMethod(EnableSession = true)] public void DoSomething() { if (Session["myObject"]!=null) { // Use the object in session state. } else { // Create a new object and store in session state. } }?
?????? 對于下拉列表示例,Web 服務必須提供一個能夠取得某個區域所有地區的方法,下面的代碼演示了這個 Web 服務:
[WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.Web.Script.Services.ScriptService] public class TerritoriesService : System.Web.Services.WebService { [WebMethod] public List<Territory> GetTerritoriesInRegion(int regionID) { SqlConnection conn = new SqlConnection( WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString); SqlCommand cmd = new SqlCommand( "select * from Territories where RegionID=@RegionID", conn); cmd.Parameters.Add(new SqlParameter("@RegionID", SqlDbType.Int, 4)); cmd.Parameters["@RegionID"].Value = regionID; ? List<Territory> territories = new List<Territory>(); try { conn.Open(); SqlDataReader reader = cmd.ExecuteReader(); while (reader.Read()) { territories.Add(new Territory(reader["TerritoryID"].ToString(), reader["TerritoryDescription"].ToString())); } reader.Close(); } catch (SqlException err) { throw new ApplicationException("Data error."); } finally { conn.Close(); } return territories; } }?????? 有一點需要注意,和以前的客戶端回調示例有所不同,這里的 Web 服務返回的是一個強類型的集合。Territory 類相對比較簡單了:
public class Territory { public string ID; public string Description; ? public Territory() { } ? public Territory(string id, string description) { this.ID = id; this.Description = description; } }?
3. 調用 Web 服務
?????? 創建了需要的 Web 服務后,下一步是配置頁面使之知道 TerritoriesService 。這時,需要給頁面添加 ScriptManager 控件,標簽中添加 <services> 節,這個節使用 ServiceReference 元素列出了頁面使用的所有服務以及它們的位置:
<asp:ScriptManager ID="ScriptManager1" runat="server"> <Services> <asp:ServiceReference Path="~/TerritoriesService.asmx" /> </Services> </asp:ScriptManager>?????? 當頁面在服務器端被呈現的時候,ScriptManager 會產生一個 JavaScript 代理。在客戶端代碼里,可使用這個代理啟動你的調用。
?
?????? 這個 Web 表單有兩個列表。第一個列表通過普通的 ASP.NET 數據綁定來填充:
<div style="font-family: Verdana; font-size: small"> Choose a Region, and then a Territory:<br /> <br /> <asp:DropDownList ID="lstRegions" runat="server" Width="210px" DataSourceID="sourceRegions" DataTextField="RegionDescription" DataValueField="RegionID" onChange="GetTerritories(this.value);"> </asp:DropDownList> <asp:DropDownList ID="lstTerritories" runat="server" Width="275px"> </asp:DropDownList> <br /> <br /> <br /> <asp:Button ID="cmdOK" runat="server" Text="OK" Width="50px" OnClick="cmdOK_Click" /><br /> <br /> <asp:Label ID="lblInfo" runat="server"></asp:Label> <asp:SqlDataSource ID="sourceRegions" runat="server" ProviderName="System.Data.SqlClient" ConnectionString="<%$ ConnectionStrings:Northwind %>" SelectCommand="SELECT 0 As RegionID, '' AS RegionDescription UNION SELECT RegionID, RegionDescription FROM Region"> </asp:SqlDataSource> </div>?????? 有趣的是,它使用了 onchange 特性與一個客戶端事件處理程序進行連接。當用戶選擇了一個新的地區時,js 函數 GetTerritories() 被觸發,并且當前的列表值會被傳遞進去。
?????? 這里是 GetTerritories() 函數的 js 代碼:
function GetTerritories(regionID) { TerritoriesService.GetTerritoriesInRegion(regionID, OnRequestComplete, OnError); }?????? 應該可以發現在調用 Web 服務時,客戶端語法和 .NET 語法是不同的。客戶端代碼不需 new 出實例而是直接使用一個現成的代理對象,這個代理對象和 Web 服務類具有相同的名稱。
?
?????? 客戶端的 Web 服務調用是異步的,因此除了要傳遞原始 Web 方法的參數外,還需提供回調函數指定用于處理返回的結果,在本例中是 OnRequestComplete。此外,還可以添加另一個引用用于指向發生錯誤時要使用的函數,這里是 OnError():
function OnRequestComplete(result) { var lstTerritories = document.getElementById("lstTerritories"); lstTerritories.innerHTML = ""; ? for (var n = 0; n < result.length; n++) { var option = document.createElement("option"); option.value = result[n].ID; option.innerHTML = result[n].Description; lstTerritories.appendChild(option); } } ? function OnError(result) { var lbl = document.getElementById("lblInfo"); lbl.innerHTML = "<b>" + result.get_message() + "</b>"; }?????? 這段代碼唯一值得注意的部分是它能直接利用返回結果工作而不需要任何額外的反序列化工作!Web 方法返回 Territory 對象的泛型列表,ASP.NET AJAX 創建 Territory 對象的定義,然后用數組返回完整的列表,這樣 js 代碼就能夠循環數組并檢查每個項的 ID 和 Description 屬性。
?????? OnError() 方法接收一個 error 對象,包含接收錯誤文本的 get_message() 方法以及返回錯誤在哪發生的調用棧的 get_stacktrace() 方法。
?
?????? 這里有個小技巧,除了使用 document.getElementById() 方法外,還可以使用 ASP.NET AJAX 提供的別名 $get 執行同樣的功能:
var lstTerritories = $get("lstTerritories"); // 這是 ASP.NET AJAX 頁面中常見的約定
?
?????? 現在這個示例能夠和以前曾介紹過的客戶端回調的版本一樣工作。區別是這個版本使用強類型的 Web 方法,沒有擾人的字符串序列化代碼,不需要添加和動態插入任何獲取回調引用的服務器端代碼。相反,可以直接使用直觀的代理,以提供 Web 服務的訪問。
?????? 這里演示了 ASP.NET AJAX 版本的客戶端回調模型。雖然這和 ASP.NET 客戶端回調機制的作用相同,但 ASP.NET AJAX 版本提供了一個基于 Web 服務構建的更健壯的基礎。但無論這兩種技術的哪一種,都必須編寫 JavaScript 代碼來更新頁面!
?
在頁面里放置 Web 方法
?????? 大多情況下,應該創建獨立的 Web 服務以處理 ASP.NET AJAX 回調,頁面更清晰,代碼更方便調試和完善。不過,有時可能會碰到 Web 方法只為特定的頁面設計且不被應用程序的其他部分重用的情況。這種情況下,你可以為每個頁面創建專門的 Web 服務,或者把 Web 服務代碼轉移到頁面里。
?????? 把 Web 方法代碼放到頁面中很容易,其實,所要做的只是剪切和粘帖:
?????? PageMethods 對象公開添加到當前 Web 頁面的所有 Web 方法。把 Web 方法放到頁面的一個好處是該方法不再通過 .asmx 文件公開添加。這樣,它就不是公共 Web 服務的一部分了,別人也就不容易發現這個方法。如果你希望向那些好奇的用戶隱藏某些 Web 服務,它就很有吸引力了。
?????? 另一個將 Web 方法放到頁面類里的原因是為了讀取頁面中視圖狀態或控件的值。頁面方法被觸發時,會發生獨立的頁面生命周期。當然,不要修改頁面的細節,因為頁面還沒有被呈現,所做的一切改變都會被直接忽略。
?
注意:
?????? 無論 Web 方法在頁面里還是專門的 Web 服務里,對程序的安全沒有什么區別。雖然在頁面里可以向普通的用戶隱藏它,但是真正的攻擊者會首先檢查頁面的 HTML 代碼,其中包含對 JavaScript 代理的引用。惡意用戶可以通過 JavaScript 代理調用 Web 方法。因此 Web 方法總應該實現和 Web 頁面同樣的安全策略。例如,要驗證收到的輸入、不向未經驗證的用戶返回敏感信息、數據庫訪問應該使用參數化命令以避免 SQL 注入攻擊。
總結
以上是生活随笔為你收集整理的ASP.NET AJAX(服务器回调)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 查看一个结构体成员的方法
- 下一篇: 元和少样本学习总结