服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > 专题栏目 > WEB2.0新技术 > 查看文档

深入atlas系列之客户端支持

  atlas提供了强大而灵活的服务器端web services访问能力。这对于客户端ajax开发提供了绝好的条件,这几乎也是任何ajax框架必备的功能。因为只要有了它,就能轻松地以ajax方式与服务器端进行交互,而其他多样的页面操作自然可以由开发人员尽情开发。对于部分喜欢自己动手的开发人员来说,这甚至是他们仅仅需要的支持。

  atlas提供了强大而灵活的服务器端web services访问能力。这对于客户端ajax开发提供了绝好的条件,这几乎也是任何ajax框架必备的功能。因为只要有了它,就能轻松地以ajax方式与服务器端进行交互,而其他多样的页面操作自然可以由开发人员尽情开发。对于部分喜欢自己动手的开发人员来说,这甚至是他们仅仅需要的支持。

  从这篇文章开始,我会从实现角度剖析atlas对于web services的支持,希望能够帮助大家更深入地理解,更灵活地使用atlas提供的这一功能。

  在atlas中,对于web services的访问,其实都是通过sys.net.servicemethod类来实现的。我们先通过uml来看一下sys.net.servicemethod以及其其他一些类的关系。

点击放大此图片

  sys.net.webmethod类是sys.net.servicemethod和sys.net.pagemethod的父类。后者用于访问写于页面中使用webmethodattribute标注的方法,不在这篇文章的讨论范围内。sys.net.webmethod内定义了五个抽象函数:get_methodname、addheaders、get_url、get_body和get_appurl,其作用应该相当地显而易见。而继承sys.net.webmethod的类,例如sys.net.servicemethod,则提供了这五个抽象函数的实现。

  sys.net.webmethod类有一个非常有趣的函数“invoke”。从uml图中会发现,它居然提供了一个javascript中没有的功能:“函数重载(overload)”!至于它是如何实现,该如何使用,稍后将结合代码进行详细介绍。

  那么就进入代码分析阶段,先从sys.net.webmethod的结构看起,从一个atlas类的大致结构可以看出该类的成员定义和“构造函数”的实现。

  sys.net.webmethod结构:

1 sys.net.webmethod = function() {
2  //抽象成员定义
3  this.addheaders = function.abstractmethod;
4  this.get_appurl = function.abstractmethod;
5  this.get_url = function.abstractmethod;
6  this.get_body = function.abstractmethod;
7
8  //最后这个定义是我补上的,很明显代码里缺少了这个定义
9  this.get_methodname = function.abstractmethod;
10
11  this.invoke = function(params) {
12   ……
13  }
14
15  this._invoke = function(params, onmethodcomplete, onmethodtimeout,
16 onmethoderror, onmethodaborted, usercontext, timeoutinterval, priority, usegetmethod) {
17
18   ……
19
20   function oncomplete(response, eventargs) {
21    ……
22   }
23
24   function ontimeout(request, eventargs) {
25    ……
26   }
27
28   function onaborted(request, eventargs) {
29    ……
30   }
31
32   ……
33  }
34 }
35 sys.net.webmethod.registerabstractclass('sys.net.webmethod');

  可以见到五个抽象函数定义,在this._invoke函数是真正产生请求的地方,里面还有数个用于引发事件的方法。整个类的结构非常简单。

  接下来我们来仔细分析一下this.invoke函数。

  this.invoke函数分析

1 // 参数params是一个dictionary,用key - value的方式
2 // 保存即将传递给web services的参数
3 this.invoke = function(params) {
4  var numofparams = arguments.length;
5
6  // 如果有两个参数,并且第二个参数不是一个函数,
7  // 则说明函数调用时参数是这样的:
8  // this.invoke(params, settings);
9  if (numofparams == 2 && arguments[1] && typeof(arguments[1]) != 'function') {
10
11  // 构造一个数组,用于获得每个key的index
12  var expectedparamnames =
13   ["onmethodcomplete", "onmethodtimeout", "onmethoderror",
14   "onmethodaborted", "usercontext", "timeoutinterval",
15   "priority", "usegetmethod"];
16
17  // 传入的第二个参数settings
18  var paramcontainer = arguments[1];
19
20  // 将要传递给this._invoke函数的参数数组
21  var newparams = new array(expectedparamnames.length + 1);
22  // 第一个参数就是params
23  newparams[0] = params;
24
25  // 枚举settings的每个key
26  for (var paramname in paramcontainer) {
27   // 由于传递给this._invoke时参数需要按照顺序顺序,
28   // 所以必须获得当前key的index。
29   var index = expectedparamnames.indexof(paramname);
30
31   // 如果setting里有个key是不需要的,那么抛出error
32   if (index < 0) {
33    throw error.createerror(
34     string.format(
35      "'{0}' is not a valid argument. it should be one of {1}",
36      paramname, expectedparamnames
37     )
38    );
39   }
40
41   // 将参数放在数组合适的位置上
42   newparams[index + 1] = paramcontainer[paramname];
43  }
44
45  // 将准备好的参数数组传递给this._invoke调用
46  return this._invoke.apply(this, newparams);
47 }
48
49 // 还有一种调用方式的参数和this_invoke的参数完全相同
50 return this._invoke.apply(this, arguments);
51 }

  可以看到,这就是就是this.invoke“函数重载(overload)”的实现方式。到底是故意如此设计还是在后续开发时为了兼容,可能就不得而知了。不过也就是说,我们现在有中参数传递可以使用。

  第一种是:

  this.invoke第一种调用方式:

1 this.invoke(
2 {
3  param1 : value1,
4  param2 : value2,
5  ……
6 },
7 {
8  onmethodcomplete : ……,
9  onmethodtimeout : ……,
10  onmethoderror : ……,
11  onmethodaborted : ……,
12  usercontext : ……,
13  timeoutinterval : ……,
14  priority : ……,
15  usegetmethod : ……
16 });

  第二种是:

  this.invoke第二种调用方式:

1 this.invoke(
2 {
3  param1 : value1,
4  param2 : value2,
5  ……
6 },
7 onmethodcomplete,
8 onmethodtimeout,
9 onmethoderror,
10 onmethodaborted,
11 usercontext,
12 timeoutinterval,
13 priority,
14 usegetmethod);

  关于参数的含义,请参照this._invoke函数的分析。

  接下来分析this._invoke的代码,这才是真正工作的代码。

  this._invoke函数分析:

1 // 参数定义:
2 // params:一个dictionary,用key - value的方式保存即将传递给web services的参数
3 // onmethodcomplete:调用完成时使用的回调函数
4 // onmethodtimeout:请求超时时使用的回调函数
5 // onmethoderror:web services发生错误(例如抛出异常)时使用的回调函数
6 // onmethodaborted:请求被取消是使用得回调函数
7 // usercontext:用户提供的任意参数,会在回调函数被执行时作为参数使用
8 // timeoutinterval:超时前所等待的时间,number类型
9 // priority:优先级,sys.net.webrequestpriority枚举类型
10 // usegetmethod:是否使用http get方法,boolean类型
11 this._invoke = function(params, onmethodcomplete,
12 onmethodtimeout, onmethoderror, onmethodaborted, usercontext,
13 timeoutinterval, priority, usegetmethod) {
14
15 // 检测参数类型是否正确
16 window.debug.validateparameters("webmethod.invoke", arguments,
17 [
18 ['params', object, true],
19 ['onmethodcomplete', function, true],
20 ['onmethodtimeout', function, true],
21 ['onmethoderror', function, true],
22 ['onmethodaborted', function, true],
23 ['timeoutinterval', number, true],
24 ['priority', number, true],
25 ['usegetmethod', boolean, true]
26 ]);
27
28 // 使用sys.net.webrequest对象进行ajax请求
29 var request = new sys.net.webrequest();
30
31 // 使用子类的addheaders实现添加header
32 this.addheaders(request.get_headers());
33 // 使用子类的实现set_url实现设置url
34 request.set_url(this.get_url(params, usegetmethod));
35 // 使用子类的实现set_appurl实现设置appurl
36 request.set_appurl(this.get_appurl());
37
38 // 为了添加body,param不能为null
39 if (params == null) {
40  params = {};
41 }
42
43 // 使用子类的set_body实现设置body
44 request.set_body(this.get_body(params, usegetmethod));
45 // 将oncomplete函数注册给sys.net.webrequest的complete事件
46 request.completed.add(oncomplete);
47 // 将ontimeout函数注册给sys.net.webrequest的timeout事件
48 request.timeout.add(ontimeout);
49 // 将onaborted函数注册给sys.net.webrequest的aborted事件
50 request.aborted.add(onaborted);
51
52 // 如果提供了timeoutinterval那么设置它
53 if (timeoutinterval) {
54  request.set_timeoutinterval(timeoutinterval);
55 }
56
57 // 如果priority不是sys.net.webrequestpriority.high的话,
58 // 则设置webrequest的priority属性
59 if (priority >= 0) {
60  request.set_priority(priority);
61 }
62
63 // 获得methodname,为后面的onxxxx方法提供信息
64 var methodname = this.get_methodname();
65
66 request.invoke();
67
68 function oncomplete(response, eventargs) {
69  ……
70 }
71
72 function ontimeout(request, eventargs) {
73  ……
74 }
75
76 function onaborted(request, eventargs) {
77  ……
78 }
79
80 //返回sys.net.webrequest对象,一般没有什么作用
81 return request;
82 }

  可以看出,sys.net.webmethod是使用sys.net.webreqeust来发出ajax请求的。在atlas应用中如果需要使用ajax请求的话,应该全部使用sys.net.webrequest,这个类不仅对于xmlhttprequest进行了良好的封装,另外它使用了sys.net._webrequestmanager对于所有请求进行了全局地控制,使用了浏览器和http协议的特性,提高了请求的效率。这一点几乎是微软介绍atlas时都会着重强调的一点。

  在创建了sys.net.webrequest对象后,并不是将用户传入的那些回调函数直接注册给它的事件,而是使用了sys.net.webmethod里的onxxxx,进行了进一步的处理,代码如下:

  onxxx函数分析:

1 function oncomplete(response, eventargs) {
2  // 获得status code
3  var statuscode = response.get_statuscode();
4
5  var result = null;
6
7
8  try {
9   // 尝试将data序列化成对象
10  result = response.get_object();
11 }
12 catch (ex) { // 失败了,说明不是传出对象
13  try {
14   // 获得xml
15   result = response.get_xml();
16  }
17  catch (ex) { }
18 }
19
20 // 如果没有成功(statuscode不是2xx),或者
21 // result为sys.net.methodrequesterror类型,
22 // 表示web services出错了(例如抛出了exception)
23 if (((statuscode < 200) || (statuscode >= 300)) ||
24 sys.net.methodrequesterror.isinstanceoftype(result)) {
25
26  // 如果提供了onmethoderror回调函数,那么执行
27  if (onmethoderror) {
28   onmethoderror(result, response, usercontext);
29  }
30  else {
31   // 没有提供onmethoderror的话那么就trace
32   debug.trace("the server method '" + methodname + "' failed with the following error:");
33
34   if (result != null) {
35    debug.trace(result.get_exceptiontype() + ": " + result.get_message());
36   }
37   else {
38    debug.trace(response.get_data());
39   }
40  }
41 }
42 else if (onmethodcomplete) { // 调用成功了
43  // 如果提供了onmethodcomplete回调函数,那么执行
44  onmethodcomplete(result, response, usercontext);
45 }
46 }
47
48 function ontimeout(request, eventargs) {
49  if (onmethodtimeout) {
50   onmethodtimeout(request, usercontext);
51  }
52 }
53
54 function onaborted(request, eventargs) {
55  if (onmethodaborted) {
56   onmethodaborted(request, usercontext);
57  }
58 }

  在oncomplete方法中,会查看status code。在http 1.x中,2xx代表了success(关于完整status code的描述,请参考http://www.w3.org/protocols/http/htresp.html)。由此可以得知该如何调用用户提供的回调函数。在调用回调函数时会将用户提供的usercontext作为参数传入,这种做法在异步调用中被经常使用,例如.net framework中delegate的异步调用。

  至此,sys.net.webmethod就被解释完了,并不复杂,甚至我觉得我的解释有些累赘。不过它提供的方法非常重要,是客户端访问服务器端函数的核心(客户端方面)。而调用web services,则需要sys.net.servicemethod这个sys.net.webmethod的子类来提供那五个抽象函数的具体实现,分析如下:

  sys.net.servicemethod代码分析:

1 // 如果要访问的web services是http://www.sample.com/abc/ws.amsx中
2 // helloworld方法的话,则
3 // url:web services的地址,以上例为"/abc/ws.amsx"
4 // methodname:方法名,以上例为"helloworld"
5 // appurl:该web应用程序的url,以上例(可能,也有可能不是)为"http://www.sample.com/"
6 sys.net.servicemethod = function(url, methodname, appurl) {
7 sys.net.servicemethod.initializebase(this);
8
9 this.get_methodname = function() { return methodname; }
10
11 // header:一个以dictionary,用key - value的方式
12 // 表示header的对象,object类型
13 this.addheaders = function(headers) {
14  // content-type设为"application/json",
15  // 表示要通过json方式来传递数据
16  headers['content-type'] = 'application/json';
17 }
18
19 // param:一个dictionary,用key - value的方式
20 // 保存即将传递给web services的参数。
21 // usegetmethod:是否使用http get方法,boolean类型
22 this.get_url = function(params, usegetmethod) {
23  // 如果不是用http get方法,或者没有提供params,
24  if (!usegetmethod || !params)
25  {
26   // 将params设为空对象,params将被作为query string添加在
27   // url上,自然使用http post方法时不需要那些query string
28   params = {};
29  }
30
31  // 添加一个mn,值为methodname,
32  // 这句代码等价于params["mn"] = methodname;
33  params.mn = methodname;
34  // 调用sys.net.webrequest.createurl方法获得url,
35  // 将params作为query string放在url后。
36  // 在这里,正常情况下只有mn一个参数。
37  var fullurl = sys.net.webrequest.createurl(url, params);
38  // 删除mn
39  delete params.mn;
40  return fullurl;
41 }
42
43 this.get_body = function(params, usegetmethod) {
44  // 如果使用http get方法,则不用body
45  if (usegetmethod) return null;
46
47  // 将params序列化作为body
48  var body = sys.serialization.json.serialize(params);
49
50  // 如果是空对象,则返回空body。
51  if (body == "{}") return "";
52
53  return body;
54 }
55
56 this.get_appurl = function() {
57  return appurl;
58 }
59 }
60 sys.net.servicemethod.registerclass('sys.net.servicemethod', sys.net.webmethod);

  对于传入参数url和appurl,可能需要重新解释一下。如果url传入的是相对路径,则appurl可以为null。

  为了有更深的理解,我们来看一个例子:

  ws.asmx代码:

1 <%@ webservice language="c#" class="ws" %>
2
3 using system;
4 using system.web;
5 using system.web.services;
6 using system.web.services.protocols;
7
8 [webservice(namespace = "http://tempuri.org/")]
9 [webservicebinding(conformsto = wsiprofiles.basicprofile1_1)]
10 public class ws : system.web.services.webservice {
11
12 [webmethod]
13 public object[] helloworld(int param1) {
14  return new object[] { "you passed parameter: " + param1, datetime.now };
15 }
16 }

  helloworld函数接受一个整数作为参数,返回一个数组。第一个元素为一个字符串,第二个元素为服务器当前时间。

  default.aspx文件代码:

1 <%@ page language="c#" %>
2 <!doctype html public "-//w3c//dtd xhtml 1.0 transitional//en" "http://www.w3.org/tr/xhtml1/dtd/xhtml1-transitional.dtd">
3
4 <html xmlns="http://www.w3.org/1999/xhtml" >
5 <head runat="server">
6 <title>web service call by sys.net.servicemethod</title>
7 <script language="javascript">
8 function invoke()
9 {
10  var params = { "param1" : math.round(math.random() * 100) };
11  var method = new sys.net.servicemethod("ws.asmx", "helloworld", null);
12  
13  method.invoke(params, onmethodcomplete);
14 }
15
16 function onmethodcomplete(result, response, usercontext)
17 {
18  document.getelementbyid("display").innerhtml =
19  result[0] + "<br />" + "server time: " + result[1];
20 }
21 </script>
22 </head>
23 <body style="font-family:arial;">
24 <form id="form1" runat="server">
25 <div>
26 <atlas:scriptmanager runat="server" id="scriptmanager1" enablescriptcomponents="true" />
27
28 <div id="display"></div>
29
30 <input onclick="invoke();" value="invoke" type="button" />
31 </div>
32 </form>
33 </body>
34 </html>

  在这里使用的是invoke函数的第二种调用方法,传入一个随机数作为参数,并将信息显示在页面上。效果如下:

  我们打开fiddler,看看具体的请求如何,请注意红色框出的地方:

  request:

  request body:

  response body:

  是不是和我们预料的完全相同?有了json,我们可以非常方便地构造和表示一个客户端对象,atlas在客户端和服务器端都提供了非常强大的json serializer。这些方法可以应用在任何需要场合,即使脱离了atlas。

  到现在为止,已经将atlas以ajax方式调用web services的客户端基础代码分析完了。但是这其实还远远不够,有了客户端代码,至少还需要服务器端的支持。那么在服务器端atlas又是如何提供以ajax方式调用web services方法的功能呢?

扫描关注微信公众号