本片文章部分内容會基于我之前的文章:《深入Atlas系列:Web Sevices Access in Atlas(1) - 用戶端支援》。
在RTM版本中,用戶端通路Web Services的方法發生了改變。在《深入Atlas系列:Web Sevices Access in Atlas(1) - 用戶端支援》和它對應的示例《深入Atlas系列:Web Sevices Access in Atlas示例(1) - 特别的通路方式》中我們知道了從用戶端通路Web Services的基礎類庫以及方法。它們是:
Sys.Net.ServiceMethod類。
Sys.Net.ServiceMethod.invoke方法的兩個重載。
在RTM版本中,用戶端通路Web Services的基礎類庫發生了一些改變,并直接影響到了它們的使用方式。對于自己寫ASP.NET AJAX元件(例如ExtenderControl)的朋友們來說,了解這部分改變是非常重要的。
一、Sys.Net.ServiceMethod -> Sys.Net._WebMethod
在CTP版本中,Sys.Net.ServiceMethod是用戶端通路Web Services的基礎類,它繼承于Sys.Net.WebMethod。同樣繼承于Sys.Net.WebMethod的還有Sys.Net.PageMethod,它用于通路PageMethod,這個類在RTM版本中被取消了。現在,用戶端用于通路Web Service的類已經變成了Sys.Net._WebMethod(從命名上來看,很明顯它不希望使用者直接使用這個類),而且在使用上也有了很大的變化。
我們先來回憶一下CTP中Sys.Net.ServiceMethod的使用方式。如下:
var serviceMethod = new Sys.Net.ServiceMethod(url, methodName, appUrl);
serviceMethod.invoke(
parameters,
onMethodComplete,
onMethodTimeout,
onMethodError,
onMethodAborted,
userContext,
timeoutInterval,
priority);
而在RTM版本中,Sys.Net._WebMethod的使用方式如下:
var method = new Sys.Net._WebMethod(proxy, methodName, fullName, useGet);
method._execute(params, onSuccess, onFailure, userContext);
CTP版本中Sys.Net.ServiceMethod中的invoke方法還有一個重載,在RTM版本中就不存在了。在Sys.Net._WebMethod的構造函數中,出現了一個“proxy”參數,這是什麼呢?我們來看一下method._execute方法的代碼。如下:
function Sys$Net$_WebMethod$_execute(params) {
return this._invokeInternal.apply(this, arguments);
}
不知道各位朋友看到這段代碼的時候感覺如何?真正工作的代碼其實是_invokeInternal方法,_execute方法在這裡根本沒有起什麼作用。其實它們兩個的關系似乎就相當于CTP版本中Sys.Net.WebMethod類中的invoke和_invoke方法,隻是_execute并沒有起到一個“調整參數”的作用,是以CTP裡的“重載”也就不複存在了。莫非在以後的版本中,_execute方法真的也會提供一個方法重載?
既然真正工作的代碼是_invokeInternal,我們就來看一下它的代碼。如下:

_invokeInternal方法
1 function Sys$Net$_WebMethod$_invokeInternal(params, onSuccess, onFailure, userContext) {
2 /// <param name="params"></param>
3 /// <param name="onSuccess" type="Function" mayBeNull="true" optional="true"></param>
4 /// <param name="onFailure" type="Function" mayBeNull="true" optional="true"></param>
5 /// <param name="userContext" mayBeNull="true" optional="true"></param>
6 var e = Function._validateParams(arguments, [
7 {name: "params"},
8 {name: "onSuccess", type: Function, mayBeNull: true, optional: true},
9 {name: "onFailure", type: Function, mayBeNull: true, optional: true},
10 {name: "userContext", mayBeNull: true, optional: true}
11 ]);
12 if (e) throw e;
13
14 // 得到fullMethodName,它的作用隻是為了“顯示”和“提示”之用,
15 // 使用者可以自己指定。
16 var methodName = this._fullMethodName;
17
18 // 如果沒有指定userContext和回調函數,将從proxy中獲得。
19 if (onSuccess === null || typeof onSuccess === 'undefined')
20 onSuccess = this._proxy.get_defaultSucceededCallback();
21 if (onFailure === null || typeof onFailure === 'undefined')
22 onFailure = this._proxy.get_defaultFailedCallback();
23 if (userContext === null || typeof userContext === 'undefined')
24 userContext = this._proxy.get_defaultUserContext();
25
26 // 建立一個新的WebRequest
27 var request = new Sys.Net.WebRequest();
28
29 // 添加header
30 this.addHeaders(request.get_headers());
31 // 設定URL
32 request.set_url(this.getUrl(params));
33
34 if (!params) params = {};
35
36 // 添加body
37 request.set_body(this.getBody(params));
38 // 添加onComplete回調函數
39 request.add_completed(onComplete);
40 // 從proxy中獲得逾時時間并設定
41 var timeout = this._proxy.get_timeout();
42 if (timeout > 0) request.set_timeout(timeout);
43 // 執行request方法
44 request.invoke();
45
46
47 function onComplete(response, eventArgs) {
48 if (response.get_responseAvailable()) {
49 var statusCode = response.get_statusCode();
50 var result = null;
51
52 try {
53 var contentType = response.getResponseHeader("Content-Type");
54
55 // 根據不同的contentType調用response的不同方法,
56 // 以獲得不同的結果
57 if (contentType.startsWith("application/json")) {
58 result = response.get_object();
59 }
60 else if (contentType.startsWith("text/xml")) {
61 result = response.get_xml();
62 }
63 else {
64 result = response.get_responseData();
65 }
66 }
67 catch (ex) {}
68
69 // 如果statusCode表示錯誤,或者result為WebServiceError對象
70 if (((statusCode < 200) || (statusCode >= 300))
71 || Sys.Net.WebServiceError.isInstanceOfType(result)) {
72 // 如果使用者定義了onFailure回調函數,則使用
73 if (onFailure) {
74 if (!result || !Sys.Net.WebServiceError.isInstanceOfType(result)) {
75 result = new Sys.Net.WebServiceError(
76 false ,
77 String.format(Sys.Res.webServiceFailedNoMsg, methodName),
78 "", "");
79 }
80 result._statusCode = statusCode;
81 onFailure(result, userContext, methodName);
82 }
83 else { // 否則使用alert提示
84 var error;
85 if (result) {
86 error = result.get_exceptionType() + "-- " + result.get_message();
87 }
88 else {
89 error = response.get_responseData();
90 }
91
92 alert(String.format(Sys.Res.webServiceFailed, methodName, error));
93 }
94 }
95 else if (onSuccess) {
96 // 如果定義了onSuccess回調函數,則使用
97 onSuccess(result, userContext, methodName);
98 }
99 }
100 else {
101 var msg;
102 if (response.get_timedOut()) {
103 // 逾時了
104 msg = String.format(Sys.Res.webServiceTimedOut, methodName);
105 }
106 else {
107 // 出錯
108 msg = String.format(Sys.Res.webServiceFailedNoMsg, methodName)
109 }
110 if (onFailure) {
111 // 如果定義了onFailure回調函數,則使用
112 onFailure(
113 new Sys.Net.WebServiceError(response.get_timedOut(), msg, "", ""),
114 userContext, methodName);
115 }
116 else {
117 alert(msg);
118 }
119 }
120 }
121
122 return request;
123 }
上面代碼中onComplete的邏輯和《深入Atlas系列:用戶端網絡通路基礎結構(上) - WebRequest的工作流程與生命周期》中的模版代碼非常的相似。可以看出,隻有在得到結果時,才會使用onSuccess回調函數,否則無論Timeout,Error還是Abort都會使用onFailure回調函數。其實這段代碼在處理錯誤時有個問題:請注意代碼的第84-90行,這是使用者沒有提供onFailure回調函數時response出錯時的處理。在這段代碼裡,預設了result是Error對象。這是一個一點道理也沒有的假設。試想,如果伺服器傳回了503 Service Unavailable,目前的邏輯會使使用者得到一個Javascript錯誤。按理應該給使用者一個正确的提示,那麼我們該怎麼辦?
由于這段代碼被寫死在類庫裡,我們無法輕易修改,目前我隻想到一個頗為複雜的方法:重新定義一個WebRequestExecutor,如果遇到了“(statusCode < 200) || (statusCode >= 300)”并且contentType不是“application/json”的時候,則構造一個假的response對象,設定其contentType為“application/json”,并使其body為一個能夠構造出Sys.Net.WebServiceError對象的JSON字元串。不得不感歎,這真是一個“令人發指”的錯誤。
萬幸的是,如果我們提供了onFailure回調函數,就不會執行這段邏輯了,這才是最友善而且最正确的做法。
在上面的代碼中使用了RTM中新增的proxy,關于它的使用還需要再看一個方法。細心的朋友應該發現了RTM中的WebMethod構造函數并沒有接受一個url作為參數,因為它會從proxy中獲得。我們來看一下Sys.Net._WebMethod的getUrl方法。代碼如下:
function Sys$Net$_WebMethod$getUrl(params) {
/// <param name="params"></param>
/// <returns type="String"></returns>
var e = Function._validateParams(arguments, [
{name: "params"}
]);
if (e) throw e;
if (!this._useGet || !params) params = {};
return Sys.Net.WebRequest._createUrl(this._proxy._get_path() + "/js/" + this._methodName, params );
Sys.Net.WebRequest._createUrl靜态方法的作用是構造一個url,它會根據第二個參數聲稱Query String,并拼接在第一個參數上。
二、Sys.Net.ServiceMethod.invoke -> Sys.Net._WebMethod._invoke
很顯然,以前的Sys.Net.ServiceMethod.invoke靜态方法也會發生改變。在RTM中,與之對應的方法變成了Sys.Net._WebMethod._invoke,使用方法如下:
Sys.Net._WebMethod._invoke(
proxy,
methodName,
fullName,
useGet,
params
onSuccess,
onFailure,
userContext);
與CTP中的實作類似,它會構造一個Sys.Net._WebMethod對象,并調用它的_execute方法。代碼非常短也非常簡單,在這裡就不進行“分析”了。需要注意的是,由于Sys.Net._WebMethod._execute方法取消了“重載”,是以Sys.Net._WebMethod._invoke方法也隻有一種使用方法了。
最後,我們再來總結一下一些參數的定義。
首先是proxy,它提供了預設的onSuccess回調函數、onFailure回調函數以及userContext和timeout的定義,另外也需要提供Web Services的路徑:
proxy =
{
get_defaultSuccessedCallback: function() // optional
{
return function() { ... }
},
get_defaultFailedCallback: function() // optional
get_defaultUserContext: function() // optional
return ...
get_timeout: function() // optional
_get_path: function() // required
}
另外還有onSuccess和onFailure兩個回調函數的簽名:
function onSuccess(result, userContext, fullMethodName)
...
function onFailure(errorObj, userContext, fullMethodName)