天天看點

深入Atlas系列:Web Sevices Access in Atlas(7) - RTM中的用戶端支援

  本片文章部分内容會基于我之前的文章:《深入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,我們就來看一下它的代碼。如下:

深入Atlas系列:Web Sevices Access in Atlas(7) - RTM中的用戶端支援
深入Atlas系列:Web Sevices Access in Atlas(7) - RTM中的用戶端支援

_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)