在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方法的两个重载。
一、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);
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);
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);
}
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 ///
3 ///
4 ///
5 ///
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 }
1 function Sys$Net$_WebMethod$_invokeInternal(params, onSuccess, onFailure, userContext) {
2 ///
3 ///
4 ///
5 ///
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) {
// /
// /
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 );
}
// /
// /
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);
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
{
return function () { ... }
},
get_defaultUserContext: function () // optional
{
return ...
},
get_timeout: function() // optional
{
return ...
},
_get_path: function () // required
{
return ...
}
}
{
get_defaultSuccessedCallback: function () // optional
{
return function () { ... }
},
get_defaultFailedCallback: function () // optional
{
return function () { ... }
},
get_defaultUserContext: function () // optional
{
return ...
},
get_timeout: function() // optional
{
return ...
},
_get_path: function () // required
{
return ...
}
}
另外还有onSuccess和onFailure两个回调函数的签名:
function
onSuccess(result, userContext, fullMethodName)
{
...
}
function onFailure(errorObj, userContext, fullMethodName)
{
...
}
{
...
}
function onFailure(errorObj, userContext, fullMethodName)
{
...
}