|
|
本片文章部分内容会基于我之前的文章:《深入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 { 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) { ... }
Feedback
我用ProfileService,那个Error连alert提示都没有,不开fiddler根本看不到,实在让人无奈。为了调试ProfileService什么情况下能用,什么情况下会有存取问题,明明是普通的服务器端错误,也不惜用fiddler来看。
@Cat Chen
这是因为ProfileService的实现所致,它并不会把您提供的onFailureCallback直接交给Sys.Net._WebMethod._invoke,而是使用了自己的_onLoadFailed,在这个方法里它设法察看您传入的onFailureCallback和它的defaultFailureCallback,只有当其中一个不为null时才会执行,否则就会“无声无息”。:)
//其实Fiddler应该已经是必备工具了。:)
花太多时间在源代码上没有太大价值吧,这个框架的代码不像当初prototype.js那样让人耳目一新,学着怎么用就行了,要不是它将成为ASP.NET的内部成员,都没有太大的用处,做做内部网还好,真做对外的public的网站,这么臃肿的一个东西,会让你死得很难看的。
呵呵,一点小看法,其实是希望看到一些使用上,扩展上的例子
@THIN
其实这些是扩展的基础,我不会把所有的代码都分析一遍的啊。
另外我不认为现在的ASP.NET很臃肿,现在的MicrosoftAjax.js的Release版本只有67K,MicrosoftAjaxWebForms.js也只有32K。这个体积对于一个AJAX应用来说不算什么吧,还没有包括Cache。另外运行效率现在也已经有了很显著的提高了,因此您说的“只能做做内部应用”也不甚妥当。您说的可能是对以前的ATLAS的评价,现在的ASP.NET AJAX已经变了。
@Jeffrey Zhao
我做了一个Extender,打开有六个资源文件,
不过对它的Animation的模式很喜欢
js那么有个性的语言,弄得跟别的语言一样,实在不爽,呵呵,期待你多写些应用和扩展上的文章
@THIN
怎么会有那么多的资源文件?
其实ASP.NET AJAX是创造出了一个功能强大的模型,并没有对JS的特性作任何的限制。我也喜欢无谓的封装,但是不觉得现在的模型很好用吗?ASP.NET AJAX铺了一条路,但是没有设置栏杆。
其实我一直在写一些基础,然后步步深入,您觉得在我这个系列里应该写什么样的应用和扩展上的文章呢?:)
/// <param name="params"></param>这样的注释你怎么加上去的,难道还能用NDOC生成文档?
arguments是全局变量?从哪儿来的呀?
atlas调用web服务使用了队列没有?你页面上发送了100多个请求,它给你排队,然后哪个请求回来了,也放到一个接收队列里,然后有个线程读取接收队列并给出提示?
proxy是啥意思?
Animation是啥?
太厉害了你,啥都知道,我回头再从头看一遍你的文章,估计每次看都会有新的收获,也就是有新的问题,哈哈
@蛙蛙池塘
这些是Beta2的标准,不能用NDoc,不过可以在下一代的VS里支持。
arguments是JS的方法被调用时,可以得到的“真实”参数的数组。
Atlas发出请求没有使用队列,要有的话也是浏览器自动作的,浏览器限制了对于每个Domain只能有2个连接同时传输数据。
Proxy就是代理,不过这里是作为Web Service访问的辅助对象用的。Extender里的Animation是指它提供的动画效果。:)
@蛙蛙池塘
从这里开始,就是RTM的东西,和正式版会非常接近。:)
|