AJAX模型基於兩個層次
客戶端應用程序層和服務器應用程序層
在這種模型下
客戶端層向服務器層發送請求
而服務器層向客戶端層返回響應
服務器端點通過URL標識
並通過源(feed)(通常為JSON[JavaScript Object Notation]數據流)向客戶端暴露數據
服務器層只是一個接收調用並將其轉發給應用程序業務邏輯層的外觀
下圖描繪了整個模型
為使ASP
NET AJAX頁面能夠調用遠程服務
該服務必須滿足幾點要求
其中最關鍵的一點與端點和底層平台的位置有關
支持AJAX的服務必須位於調用者所處的域中
這意味著該服務必須是ASP
NET XML Web服務(
asmx端點)
必須以ASP
NET應用程序的形式寄放於同一Web服務器的某個IIS應用程序中
總的來說
對於ASP
NET AJAX應用服務
有
種定義服務器層服務的方式
帶有asmx端點的ASP
NET XML Web服務
帶有svc端點的WCF服務
帶有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
:
ALFKI
Company
:
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應用程序的nfig文件來修改接收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
中的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服務
此外
我們還要在nfig文件中添加一個特殊的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 Core
WebService{
[WebService(Namespace=
)]
[WebServiceBinding(ConformsTo=WsiProfiles
BasicProfile
_
)]
[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客戶端和工具
為此
只需在nfig文件中添加以下設置
<webSevices>
<protocols>
<clear />
</protocols></webServices>
這段簡單的設置能禁用ASP
NET Web服務定義的所有協議(包括SOAP)
使該服務只能響應JSON請求
注意
如果添加這些設置
則不能夠通過浏覽器的地址欄來調用Web服務
以便進行簡單地測試
類似地
我們也不能為URL添加?wsdl後綴來調用WSDL
Web服務方法的定義
客戶端頁面能夠調用Web服務類中帶有WebMethod特性的公共方法
在默認情況下
這些方法要通過HTTP動作POST來調用
以JSON對象的形式返回其值
我們可通過一個可選特性ScriptMethod來更改單個方法的默認設置
ScriptMethod特性帶有
個屬性
見下表
由於涉及安全性和性能問題
因謹慎使用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=
ScriptManager
runat=
server
>
<Services>
<asp:ServiceReference Path=
~/WebServices/TimeService
asmx
/>
</Services></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
;ScriptManager
Services
Add(service)
不論采用哪種方式
為調用Web服務
我們只需通過JavaScript代理類發起調用即可
使用ASP
NET應用程序來承托AJAX Web服務
為啟用ASP
NET AJAX應用程序中的Web服務調用
我們需要在nfig文件中添加以下內容以注冊一個特殊的asmx請求HTTP處理程序
<httpHandlers>
<remove verb=
*
path=
*
asmx
/>
<add verb=
*
path=
*
asmx
type=
System
Web
Script
Services
ScriptHandlerFactory
/>
…</httpHandlers>
該設置已包含在VS
為支持AJAX的Web項目而創建的nfig文件中
處理程序工廠(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(
Core
WebServices
)
Core
WebServices
TimeService = function(){
Core
WebServices
TimeService
initializeBase(this)
this
_timeout =
;
this
_userContext = null;
this
_succceeded = null;
this
_failed = null;}Core
WebServices
TimeService
prototype ={
//調用GetTime方法
GetTime : function(succeededCallback
failedCallback
userContext)
{
//invoke參數分別為
//Web Service URL路徑
//Web Service方法名稱
//
//傳入方法的參數數組
//執行成功回調函數
//執行失敗回調函數
//調用上下文對象
return this
_invoke(Core
WebServices
TimeService
get_path()
GetTime
false
{}
succeededCallback
failedCallback
userContext)
}
GetTimeFormat : function(timeFormat
succeededCallback
failedCallback
userContext)
{
return this
_invoke(Core
WebServices
TimeService
get_path()
GetTimeAsFormat
false
{format:timeFormat}
succeededCallback
failedCallback
userContext)
}}//注冊Core
WebServices
TimeService類
該類繼承於Sys
Net
WebServiceProxyCore
WebServices
TimeService
registerClass(
Core
WebServices
TimeService
Sys
Net
WebServiceProxy)
//創建一個JavaScript代理類實例Core
WebService
TimeService
_staticInstance = new Core
WebServices
TimeService()
在JavaScript中調用WebService方法其實是通過最後創建的JavaScript代理類實現的
Core
WebService
TimeService
GetTime = function(onSuccess
onFailed
userContext){
Core
WebService
TimeService
_staticInstance
GetTime(onSuccess
onFailed
userContext)
}Core
WebService
TimeService
GetTimeFormat = function(onSuccess
onFailed
userContext){
Core
WebService
TimeService
_staticInstance
GetTimeFormat(onSuccess
onFailed
userContext)
}
在這個代理類的定義中帶有幾個公共屬性
path屬性用於定義Web服務的URL
我們可以編程方式更改該屬性值
以便將代理重定向到其他URL
遠程調用的執行
下面是將JavaScript代理與客戶端按鈕點擊關聯的典型方法
<input type=
button
value=
Get Time
onclick=
getTime()
/>
按鈕最好是客戶端按鈕
但也可以是服務器端Button對象生成的提交按鈕
只要將OnClientClick屬性設置為false的JavaScript代碼即可
這會避免它執行默認的回發操作
<asp:Button ID=
Button
runat=
server
Text=
Button
OnClientClick=
getTime()
return false;
/>
getTime函數用於采集必要的輸入數據
並調用代理類中的靜態方法
如果希望為回調或用戶上下文對象賦予默認值
那麼最好在pageLoad函數中進行
因為pageLoad函數會在客戶端頁面ASP
NET AJAX成功初始化後調用
該函數比浏覽器的onload事件更可靠
示例代碼如下
<script language=
javascript
type=
text/javascript
>
function pageLoad()
{
//設置默認的調用失敗回調函數
Core
WebServices
TimeService
set_defaultFailedCallback(methodFailed)
}
function getTime()
{
Core
WebServices
TimeService
GetTimeFormat(
ddd
dd MMMM yyyy [hh:mm:ss]
methodComplete)
}
function methodComplete(results
context
methodName)
{
$get(
Label
)
innerHTML = results;
}
function methodFailed(errorInfo
context
methodName)
{
$get(
Label
)
innerHTML = String
Format(
Execution of method
{
}
failed because of the following:\r\n
{
}
methodName
errorInfo
get_message())
}</script>
由於Web服務調用是以異步方式處理的
因而我們需要回調來處理調用成功和失敗這兩種情況
這兩個回調的簽名類似
function method(results
context
methodName)
下表對各參數做了簡要說明
錯誤處理
failed回調會在服務器上的遠程方法執行期間發生異常時被調用
在這種情況下
HTTP響應會包含HTTP錯誤碼
(內部錯誤)
在客戶端
服務器異常通過JavaScript中的Error對象暴露
該對象會基於從服務器端獲得的消息和堆棧跟蹤而動態創建
Error對象會通過results參數暴露給failed回調
我們可通過Error對象的message和stackTrace屬性來分別讀取收到的消息和堆棧跟蹤
如果我們未指派默認的錯誤回調函數
ASP
NET AJAX會調用自己的默認回調函數
該回調函數會彈出一個帶有服務器異常消息的消息框
為用戶提供反饋
雖然UpdatePanel中提供了異步調用的反饋機制(如UpdateProgress控件)
但對於傳統的遠程方法調用
我們只能自行編寫代碼實現對用戶的反饋
我們可以在遠程方法調用執行前顯示等待消息
GIF動畫或其他內容
function takeaWhile(){
//顯示等待消息
$get(
Feedback
)
innerHTML =
Please
wait …
;
Core
WebServices
MySampleService
VeryLengthyTask(methodCompletedWithFeedback
methodFailedWithFeedback)
}
在completed回調中
我們首先重置用戶界面
然後再進行其他操作
function methodCompletedWithFeedback(results
context
methodName){
$get(
Feedback
)
innerHTML =
;
…}
注意
在發生錯誤時
我們也要清除用戶界面
超時處理
如果發起對asmx Web服務的客戶端調用
則是對asmx的直接調用
對於該請求
ASP
NET運行庫中只有同步處理程序
也就是說
不論客戶端如何檢測當前調用是否正在進行
ASP
NET線程都會被完全阻塞
直到AJAX方法執行完畢
為此
我們可以設置超時時間
Core
WebServices
MySampleService
set_timeout(
)
timeout屬性是全局的
作用於代理類的所有方法
如果請求超時
我們便不會從服務器收到響應
客戶端只能單方面的撤銷執行
From:http://tw.wingwit.com/Article/program/net/201311/13787.html