ASP.NET 3.5核心编程学习笔记:AJAX Web Service的调用

来源:互联网 发布:采购软件有哪些 编辑:程序博客网 时间:2024/06/05 15:49

  AJAX模型基於兩個層次--客戶端應用程序層和服務器應用程序層。在這種模型下,客戶端層向服務器層發送請求,而服務器層向客戶端層返迴響應。服務器端點通過URL標識,並通過源(feed)(通常為JSON[JavaScript Object Notation]數據流)向客戶端暴露數據。服務器層只是一個接收調用並將其轉發給應用程序業務邏輯層的外觀。下圖描繪了整個模型:

  為使ASP.NET AJAX頁面能夠調用遠程服務,該服務必須滿足幾點要求,其中最關鍵的一點與端點和底層平台的位置有關。支持AJAX的服務必須位於調用者所處的域中。這意味著該服務必須是ASP.NET XML Web服務(.asmx端點),必須以ASP.NET應用程序的形式寄放於同一Web服務器的某個IIS應用程序中。

  總的來說,對於ASP.NET AJAX應用服務,有3種定義服務器層服務的方式:

  1. 帶有asmx端點的ASP.NET XML Web服務。

  2. 帶有svc端點的WCF服務。

  3. 帶有aspx端點的頁面方法,這些方法定義在與主調頁面相同的頁面中。

  “服務(Service)”這個詞往往被誤用。在AJAX中,服務指的是隸屬於應用程序的代碼(位於應用程序的域中),用於向客戶端暴露相應的功能。從根本上講,AJAX應用程序使用的服務一般不通過簡單對象訪問協議(SOAP)進行通信(而是使用JSON),不必是面向服務架構(SOA)中自治的服務。它們與自身所處平台和域綁定。因此,不能稱這裡的服務為WS-*Web服務和SOA服務。

REST服務

  針對AJAX應用程序的服務圍繞著暴露給Web客戶端數據和資源。二者可通過HTTP獲取,要求客戶端通過URL(也可以有HTTP標頭)來訪問數據和命令操作。客戶端與服務的交互是通過GET、POST、PUT和DELETE這樣的動作來完成。換言之,URL用於描述所要獲取的資源,HTTP動作用於描述對資源執行的操作。這類交互過程中交換的數據由簡單的格式表示,甚至可以用聯合格式(如RSS和ATOM)表示。

  具有這些特性的服務為具象狀態傳輸(Representational State Transfer,REST)。

數據的序列化

  AJAX調用包含作為參數傳給被調服務方法的數據及作為輸出返回的數據。這些數據是如何序列化的?

  通信雙方都能理解的序列化格式為JavaScript對象表示法(JSON)。 JSON是一種基於文本的格式,專門用於在不同層次間傳遞對象的狀態。 JavaScript支持JSON,可通過JavaScript的eval函數將JSON兼容的字符串轉換為JavaScript對象。然而,如果JavaScript字符串代表自定義對象的狀態,那麼開發者應確保具有相應類的定義。

  ASP.NET AJAX網絡堆棧要負責為每個遠程傳遞的對象創建JSON字符串。在服務器端,通過專門的格式化程序類接收數據,並通過.NET反射來填充與之匹配的託管類。在返回時,.NET託管類會被序列化為JSON字符串,並發送給客戶端。腳本管理器會確保引用這些JSON字符串的類(Web服務代理類)存在於客戶端。

  下面給出一個描述對象狀態的JSON格式的示例:

{ "ID":< /SPAN>"ALFKI", "Company< SPAN style="COLOR: #800000">":"Alfred Futterkiste"}

  這個字符串說明該對像有兩個屬性:ID和Company,存儲的是以字符串的形式序列化的值。如果某個屬性被賦予一個非基本類型的值(如自定義對象),那麼該值會以遞歸方式序列化為JSON。

JSON與XML

  相比XML,JSON更精煉,更適合JavaScript語言。

應用程序特定的Web服務

  在默認情況下,ASP.NET Web服務收發的是SOAP數據包(而不是JSON數據包),通過Web服務描述語言(Web Services Description Language,WSDL)文檔來暴露其協定。 AJAX應用程序上下文中的ASP.NET XML Web服務是如何工作的呢?

  可以通過ASP.NET AJAX應用程序的web.config文件來修改接收asmx請求的HTTP處理程序,將這些調用重定向給能夠理解JSON流的HTTP處理程序。這意味著ASP.NET XML Web服務可以是一種雙重的服務,即可接受和處理SOAP請求,也可針對JSON請求。在配置層,我們可以禁用SOAP支持,並隱藏用於對外公開該服務功能的WSDL文件。

  如果要使用支持JSON的ASP.NET Web服務,則需要刪除XML,因為在調用ASP.NET Web服務時,我們不處理SOAP和XML。針對AJAX應用程序的ASP.NET Web服務不採用SOAP消息。

遠程編程接口的定義

  協定(contract)用於定義服務器端端點暴露給調用者的內容。如果希望以ASP.NET Web服務的形式實現,則不嚴格要求存在實際的協定。但如果ASP.NET 3.5中的WCF服務,那麼協定就必須存在。總而言之,以接口形式設計的公共API會使代碼更整潔。在實現該接口的類創建完畢後,有關服務器API接口的工作就結束了。這樣我們就可以發布這個遠程API,並使ASP.NET AJAX運行庫來管理來自客戶端的調用。

  對於ASP.NET Web服務,我們通過純粹的接口來定義協定,使該接口包含與服務器API有關的方法和屬性。下面給出一個簡單的服務:

using System;
public interface ITimeService
{
DateTime GetTime();
string GetTimeFormat(string format);
}

  這兩個方法構成了可以在客戶端調用的服務器API。

實現已約定的接口

  ASP.NET Web服務通常通過派生自基類WebService的.NET類來實現:

using System.Web.Services;
public class TimeService : WebService, ITimeService
{
...
}

  注意,沒有必要一定要從基類WebService派生,這個基類主要用於直接訪問一些常用的ASP.NET對象(如Application和Session)。如果不需要直接訪問這些ASP.NET內部的對象,即使不從WebService類派生也能創建ASP.NET Web服務。這種情況下,我們可通過HttpContext對象來間接地使用ASP.NET內部的對象。

協定的發布

  從本質來說,發布給定的服務器協定,就是生成一個嵌在頁面中的腳本能夠調用的JavaScript代理類。如果服務器API通過Web服務實現,我們要向ASP.NET AJAX頁面的腳本管理器註冊該Web服務。此外,我們還要在web.config文件中添加一個特殊的asmx請求HTTP處理程序。

Web服務的遠程調用

  Web服務提供了服務器端代碼的宿主環境,以便在響應客戶端的操作時進行調用。服務中的Web方法指向應用程序特定的代碼。

AJAX Web服務的創建

  為ASP.NET AJAX應用程序定制的Web服務比其他ASP.NET Web服務要小。 ASP.NET AJAX Web服務與傳統的ASP.NET XML Web服務間存在兩方面的差異。

  首先,若使用ASP.NET AJAX Web服務,那麼為滿足特定應用程序的需要,我們要設計ASP.NET AJAX Web服務的協定,而不是配置公共服務的行為。目標應用程序就是Web服務的宿主。其次,我們必須使用一個新的特性(attribute)來聲明這種Web服務的類,而在常規的ASP.NET XML Web服務中這是不允許的。

  最終的效果是,ASP.NET AJAX Web服務可能有兩套公共接口:一套是基於JSON對接口,由宿主ASP.NET AJAX應用程序使用;另一套是基於SOAP的接口,暴露給客戶端,任何平台都能訪問該服務的URL。

ScriptService特性(attribute)

  為創建ASP.NET AJAX Web服務,第一步是要建立標準的ASP.NET Web服務項目,隨後導入System.Web.Script.Services命名空間:

using System.Web.Script.Services;

namespace Core35.WebService
{
[WebService(Namespace
="http://Core35.book/")]
[WebServiceBinding(ConformsTo
=WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class TimeService : System.Web.Services.WebService, ITimeService
{
...
}
}

  ScriptService特性是使ASP.NET XML Web服務與ASP.NET AJAX Web服務間產生差異的關鍵。該特性指出,該服務旨在接受來自基於JavaScript客戶端代理的調用。

阻塞SOAP客戶端

  一旦創建AJAX Web服務,便可以ASMX資源的形式發布它。默認情況下,它會有公共的URL,能夠由AJAX客戶端調用,同時也能被SOAP客戶端和工具發現和使用。但我們可禁用SOAP客戶端和工具。為此,只需在web.config文件中添加以下設置:

< webSevices>
<protocols>
<clear />
</protocols>
</webServices>

  這段簡單的設置能禁用ASP.NET Web服務定義的所有協議(包括SOAP),使該服務只能響應JSON請求。

  注意,如果添加這些設置,則不能夠通過瀏覽器的地址欄來調用Web服務,以便進行簡單地測試。類似地,我們也不能為URL添加?wsdl後綴來調用WSDL。

Web服務方法的定義

  客戶端頁面能夠調用Web服務類中帶有WebMethod特性的公共方法。在默認情況下,這些方法要通過HTTP動作POST來調用,以JSON對象的形式返回其值。我們可通過一個可選特性ScriptMethod來更改單個方法的默認設置。

  ScriptMethod特性帶有3個屬性,見下表:

  由於涉及安全性和性能問題,因謹慎使用ScriptMethod特性,下面的代碼使用了該特性,但未修改默認設置:

[ WebMethod]
[ScriptMethod]
public DateTime GetTime()
{
...
}

  WebMethod特性是必選的,而ScriptMethod特性是可選的。

AJAX Web服務的註冊

  為在客戶端發起對ASP.NET Web服務的調用,我們只需要XMLHttpRequest、目標Web服務的URL和JSON流的管理功能。為方便起見,所有功能都包裝在映射到遠程編程接口的JavaScript代理類中。該代理類會由ASP.NET AJAX框架自動生成,並註入到客戶端。

  為使內建的引擎生成所需的JavaScript代理和輔助類,我們應在需要AJAX Web服務的頁面中,向腳本管理器控件註冊該Web服務:

<asp:ScriptManager ID=< SPAN style="COLOR: #800000">"ScriptManager1" runat="server">
<Services>
<asp:ServiceReference Path="~/WebServices/TimeService.asmx" />
< /Services>< BR></asp:ScriptManager>

  對於每個要綁定到頁面的Web服務,我們添加一個ServiceReference標籤,將Path屬性設為對應asmx資源的URL。對於每個服務引用,都會在客戶端自動生成一個額外的<script>塊。該腳本的URL指向一個系統HTTP處理程序,在內部調用以下URL:

  ~/WebServices/TimeService.asmx/js

  追加到Web服務URL的/js後綴指示ASP.NET AJAX運行庫為指定的Web服務生成JavaScript代理類。如果頁面處於調試模板,該後綴會被改為/jsdebug。

  默認情況下,JavaScript代理通過<script>標籤連接到頁面,這樣就需要單獨下載。通過將ServiceReference對象InlineScript屬性設置為true,我們還可以將任何所需的腳本併入當前頁面。如果啟用瀏覽器緩存,且多個Web頁面使用相同的服務引用,那麼默認值false更合適。在這種情況下,不論多少頁面需要這個代理類,都只需執行一次請求。將InlineScript屬性設為true會降低網絡請求數,但會多佔用一定的帶寬。

  如果以編程方式註冊AJAX Web服務,我們使用類似以下的代碼:

ServiceReference service = new ServiceReference();
service.Path
= "~/WebServices/TimeService.asmx" ;
ScriptManager1.Services.Add(service);

  不論採用哪種方式,為調用Web服務,我們只需通過JavaScript代理類發起調用即可。

使用ASP.NET應用程序來承托AJAX Web服務

  為啟用ASP.NET AJAX應用程序中的Web服務調用,我們需要在web.config文件中添加以下內容以註冊一個特殊的asmx請求HTTP處理程序:

<httpHandlers>
<remove verb="*" path="*.asmx" />
<add < /SPAN>verb="*" path ="*.asmx"
type
="System.Web.Script.Services.ScriptHandlerFactory" />
...
</httpHandlers>

  該設置已包含在VS2008為支持AJAX的Web項目而創建的web.config文件中。

  處理程序工廠(System.Web.Script.Services.ScriptHandlerFactory類)會選擇負責處理給定類型請求的HTTP處理程序,且能通過Web服務調用中的腳本來識別JSON調用。基於JSON的請求由特殊的HTTP處理程序處理,而常規的SOAP調用會穿越ASP.NET管道。

AJAX Web服務的調用

  被引用的ASP.NET AJAX Web服務暴露給JavaScript代碼的類名與服務器類名相同。代理類採用單例模式,暴露了外界調用的靜態方法,無需實例化。

JavaScript代理類

  以上述的timeservice.asmx生成的JavaScript代理類為例,讓我們看看它的代碼:

Type.registerNamespace('Core35.WebServices');
Core35.WebServices.TimeService
= function()
{
Core35.WebServices.TimeService.initializeBase (
this);
this._timeout = 0;
this._userContext = null;
this._succceeded = < /SPAN>null;
this ._failed = null;
}
Core35.WebServices.TimeService.prototype
=
{
//調用GetTime方法< BR> GetTime : function(succeededCallback, failedCallback, userContext)
{
//invoke參數分別為: //Web Service URL路徑 //Web Service方法名稱 // //傳入方法的參數數組 //執行成功回調函數 //執行失敗回調函數 //調用上下文對象

return this._invoke(Core35.WebServices.TimeService.get_path(),
'GetTime' ,
false,
{},
succeededCallback,
failedCallback,
userContext);
},
GetTimeFormat :
function< SPAN style="COLOR: #000000">(timeFormat, succeededCallback, failedCallback, userContext)
{
return this._invoke(Core35.WebServices.TimeService.get_path() ,
'GetTimeAsFormat',
false,
{format:timeFormat},
succeededCallback,
failedCallback,
userContext);
}
}
//註冊Core35.WebServices.TimeService類,該類繼承於Sys.Net.WebServiceProxy
Core35.WebServices.TimeService.registerClass(
'Core35.WebServices.TimeService',< BR> Sys.Net.WebServiceProxy);//創建一個JavaScript代理類實例
Core35.WebService.TimeService._staticInstance
= new Core35.WebServices.TimeService();

  在JavaScript中調用WebService方法其實是通過最後創建的JavaScript代理類實現的:

Core35.WebService.TimeService.GetTime  = function(onSuccess, onFailed, userContext)
{
Core35.WebService.TimeService._staticInstance.GetTime(onSuccess, onFailed, userContext);
}
Core35.WebService. TimeService.GetTimeFormat
= function (onSuccess, onFailed, userContext)
{
Core35.WebService.TimeService._staticInstance.GetTimeFormat(onSuccess, onFailed, userContext);
}

  在這個代理類的定義中帶有幾個公共屬性:

  path屬性用於定義Web服務的URL,我們可以編程方式更改該屬性值,以便將代理重定向到其他URL。

遠程調用的執行

  下面是將JavaScript代理與客戶端按鈕點擊關聯的典型方法:

<input type="button" value="Get Time" onclick="getTime()" />< /PRE>

  按鈕最好是客戶端按鈕,但也可以是服務器端Button對像生成的提交按鈕,只要將OnClientClick屬性設置為false的JavaScript代碼即可,這會避免它執行默認的回發操作:

<asp:Button ID="Button1" runat="server" Text="Button" OnClientClick="getTime(); return false;" />

  getTime函數用於採集必要的輸入數據,並調用代理類中的靜態方法。如果希望為回調或用戶上下文對象賦予默認值,那麼最好在pageLoad函數中進行。因為pageLoad函數會在客戶端頁面ASP.NET AJAX成功初始化後調用,該函數比瀏覽器的onload事件更可靠。示例代碼如下:

<script language="javascript" type="text/javascript">
function pageLoad()
{
//設置默認的調用失敗回調函數
Core35.WebServices.TimeService.set_defaultFailedCallback(methodFailed);
}
function getTime()
{
Core35.WebServices.TimeService .GetTimeFormat(
"ddd, dd MMMM yyyy [hh:mm:ss] ", methodComplete);
}
function methodComplete(results, context, methodName)
{
$get(
"Label1" ).innerHTML = results;
}
function methodFailed(errorInfo, context, methodName)
{
$get(
"Label1").innerHTML = String.Format("Execution of method '{0}' failed because of the following:/r/n'{1}'",
methodName, errorInfo.get_message());
}
</script>

  由於Web服務調用是以異步方式處理的,因而我們需要回調來處理調用成功和失敗這兩種情況。這兩個回調的簽名類似:

function method(results, context, methodName )

  下表對各參數做了簡要說明:

錯誤處理

  failed回調會在服務器上的遠程方法執行期間發生異常時被調用。在這種情況下,HTTP響應會包含HTTP錯誤碼500(內部錯誤)。

  在客戶端,服務器異常通過JavaScript中的Error對象暴露,該對象會基於從服務器端獲得的消息和堆棧跟踪而動態創建。 Error對象會通過results參數暴露給failed回調。我們可通過Error對象的message和stackTrace屬性來分別讀取收到的消息和堆棧跟踪。

  如果我們未指派默認的錯誤回調函數,ASP.NET AJAX會調用自己的默認回調函數,該回調函數會彈出一個帶有服務器異常消息的消息框。

為用戶提供反饋

  雖然UpdatePanel中提供了異步調用的反饋機制(如UpdateProgress控件),但對於傳統的遠程方法調用,我們只能自行編寫代碼實現對用戶的反饋。

  我們可以在遠程方法調用執行前顯示等待消息、GIF動畫或其他內容:

function takeaWhile()
{
//顯示等待消息
$get("Feedback") .innerHTML = "< /SPAN>Please, wait ...";
Core35.WebServices.MySampleService.VeryLengthyTask(methodCompletedWithFeedback, methodFailedWithFeedback);
}

  在completed回調中,我們首先重置用戶界面,然後再進行其他操作:

function methodCompletedWithFeedback(results, context, methodName)
{
$get(
"Feedback").innerHTML = "";
...
}

  注意,在發生錯誤時,我們也要清除用戶界面。

超時處理

  如果發起對asmx Web服務的客戶端調用,則是對asmx的直接調用。對於該請求,ASP.NET運行庫中只有同步處理程序。也就是說,不論客戶端如何檢測當前調用是否正在進行,ASP.NET線程都會被完全阻塞,直到AJAX方法執行完畢。為此,我們可以設置超時時間:

Core35.WebServices.MySampleService.set_timeout(3000);

  timeout屬性是全局的,作用於代理類的所有方法。

  如果請求超時,我們便不會從服務器收到響應,客戶端只能單方面的撤銷執行。

原创粉丝点击