注意:部分内容已经过期,请结合《深入Atlas系列:Web Sevices Access in Atlas(7) - RTM中的客户端支持》阅读此文。
在《深入Atlas系列:Web Sevices Access in Atlas(1) - 客户端支持》
里我们分析了Atlas客户端以AJAX方式访问Web
Services方法所使用的基础代码,那就是Sys.Net.ServiceMethod,它提供了对于Web
Service方法访问的封装。有了它,我们可以很方便地访问Web
Services方法。但是在Atlas中,我们有更加方便的访问方式,在这篇文章里,我们就来讨论一下这些方法。
一、使用Sys.Net.ServiceMethod.invoke静态方法访问Web Services
如果阅读了Atlas代码之后,可以发现在客户端一些需要访问Web
Services方法的类,例如AutoCompleteBehavior,它们在访问Web
Services方法的时候并没有直接使用Sys.Net.ServiceMethod,而是调用了Sys.Net.ServiceMethod类的静态
方法invoke。在以后的文章中我会分析Atlas客户端对Web
Service方法提供代理的实现,届时也能发现,事实上在代理内部,使用的也是该静态方法。这个静态方法相当简单,不过我们还是来分析一下吧。代码如
下:
 Sys.Net.ServiceMethod.invoke静态方法分析 1 // methodURL:Web Services文件URL 2 // methodName:Web Services方法名 3 // appUrl:Web应用程序路径 4 Sys.Net.ServiceMethod.invoke = function(methodURL, methodName, appUrl) { 5 6 // 使用参数构造一个Sys.Net.ServiceMethod对象 7 var method = new Sys.Net.ServiceMethod(methodURL, methodName, appUrl); 8 9 // 准备使用上面method对象的参数数组 10 var callMethodArgs = new Array(); 11 12 // 从第4个参数开始,依次放入新的参数数组 13 for (var i = 3; i < arguments.length; i++) 14 { 15 callMethodArgs[i-3] = arguments[i]; 16 } 17 18 // 使用准备好的参数数组调用method对象的invoke方法 19 return method.invoke.apply(method, callMethodArgs); 20 }
代码如想象中的简单,只是在静态方法内部构造一个Sys.Net.ServiceMethod类的对象,并调用它的invoke方法。由于Sys.Net.ServiceMethod的invoke方法提供了“神奇”的“函数重载”(详细信息请见《深入Atlas系列:Web Sevices Access in Atlas(1) - 客户端支持》的分析),因此Sys.Net.ServiceMethod.invoke静态方法也能通过两种方式调用,如下:
第一种是:
 Sys.Net.ServiceMethod.invoke静态方法分析 1 Sys.Net.ServiceMethod.invoke( 2 methodURL, 3 methodName, 4 appurl, 5 { 6 param1 : value1, 7 param2 : value2, 8 …… 9 }, 10 { 11 onMethodComplete : ……, 12 onMethodTimeout : ……, 13 onMethodError : ……, 14 onMethodAborted : ……, 15 userContext : ……, 16 timeoutInterval : ……, 17 priority : ……, 18 useGetMethod : …… 19 });
第二种是:
 Sys.Net.ServiceMethod.invoke静态方法分析 1 Sys.Net.ServiceMethod.invoke( 2 methodURL, 3 methodName, 4 appurl, 5 { 6 param1 : value1, 7 param2 : value2, 8 …… 9 }, 10 onMethodComplete, 11 onMethodTimeout, 12 onMethodError, 13 onMethodAborted, 14 userContext, 15 timeoutInterval, 16 priority, 17 useGetMethod);
于是,以后就能使用这种比较简洁的方式访问Web Services方法了。
二、使用Declarative Syntax访问Web Services方法
深入代码可以发现各种有效的使用方式,尤其在现在这样文档极其匮乏的时期。事实上,对于使用Declarative Syntax访问Web Services的描述才是这篇文章的重点。仔细Atlas代码之后,可以发现在这样一个类:
1 Sys.Net.ServiceMethodRequest = function() { 2 …… 3 } 4 Sys.Net.ServiceMethodRequest.registerClass('Sys.Net.ServiceMethodRequest', Sys.Component); 5 Sys.TypeDescriptor.addType('script', 'serviceMethod', Sys.Net.ServiceMethodRequest);
在我之前的文章《使用Atlas创建自己的Client Control》
里简单分析了一点:使用Sys.TypeDescriptor.addType方法能够为一个类提供Declarative
Syntax的支持。关于这一点的具体实现方式已经超出了现在这篇文章的讨论范围,但是我会在“深入Atlas系列”的后续文章里从实现角度具体分析
Atlas对于Xml Scripts的解析方式,敬请留意。:)
对于支持Declarative
Syntax的类,其最重要的方法应该就是this.getDescriptor了(确切的说,这个是
Sys.ITypeDescriptorProvider接口的方法。对于没有实现该接口的类,Atlas在识别其成员时使用的是别的方式。不过由于
Sys.Component实现了Sys.ITypeDescriptorProvider接口,因此我们如果继承了Sys.Component,只要重
载getDescriptor方法就可以了)。它提供了对于类成员的描述,Atlas在进行操作时会频繁使用这些信息,因此该方法非常重要。我们来看一下
它在Sys.Net.ServiceMethodRequest中的实现:
 getDescriptor方法实现 1 this.getDescriptor = function() { 2 var td = Sys.Net.ServiceMethodRequest.callBaseMethod(this, 'getDescriptor'); 3 4 // --- 属性 --- 5 // Web Services的URL 6 td.addProperty('url', String); 7 // Web应用程序URL 8 td.addProperty('appUrl', String); 9 // Web Services方法名 10 td.addProperty('methodName', String); 11 // 参数字典,将在后面具体讨论,只度 12 td.addProperty('parameters', Object, true); 13 // response对象,在调用以后可以获得,只读 14 td.addProperty('response', Sys.Net.WebRequestExecutor, true); 15 // 调用后的结果,制度 16 td.addProperty('result', Object, true); 17 // 超时间隔 18 td.addProperty('timeoutInterval', Number); 19 // 优先级,按理应该是Sys.Net.WebRequestPriority枚举类型 20 td.addProperty('priority', Number); 21 22 // --- 方法 --- 23 // invoke方法,调用该Web Service方法 24 td.addMethod('invoke'); 25 // abort,取消该Web Service方法调用 26 td.addMethod('abort'); 27 28 // --- 事件 --- 29 // completed事件,在调用完成后触发 30 td.addEvent('completed', true); 31 // timeout事件,在调用超时后触发 32 td.addEvent('timeout', true); 33 // error事件,在调用出错时触发 34 td.addEvent('error', true); 35 // aborted事件,在调用被取消时触发 36 td.addEvent('aborted', true); 37 38 return td; 39 } 40 Sys.Net.ServiceMethodRequest.registerBaseMethod(this, 'getDescriptor');
以上就是Sys.Net.ServiceMethodRequest类的成员描述,应该说还是相当直观的。
接着开始分析Sys.Net.ServiceMethodRequest类的主要成员,一些属性的get/set方法就忽略了。其实整个类唯一作用较大的方法就是this.invoke。代码如下:
 this.invoke方法分析 1 this.invoke = function(userContext) { 2 // 如果一个Request正在生效,则退出 3 if (_request != null) { 4 return false; 5 } 6 7 // 构造一个Sys.Net.ServiceMethod对象 8 var serviceMethod = new Sys.Net.ServiceMethod(_url, _methodName, _appUrl); 9 // 将Web Services方法参数字典和每个回调函数作为参数传入invoke方法 10 _request = serviceMethod.invoke(_parameters, onMethodComplete, onMethodTimeout, 11 onMethodError, onMethodAborted, this , 12 _timeoutInterval, _priority); 13 14 function onMethodComplete(result, response, target ) { 15 // 调用完成,将_request设为null 16 _request = null; 17 _userContext = userContext; 18 _response = response; 19 _result = result; 20 // 触发completed事件 21 target.completed.invoke(target, Sys.EventArgs.Empty); 22 } 23 24 function onMethodError(result, response, target ) { 25 // 调用完成,将_request设为null 26 _request = null; 27 _userContext = userContext; 28 _response = response; 29 _result = result; 30 // 触发error事件 31 target.error.invoke(target, Sys.EventArgs.Empty); 32 } 33 34 function onMethodTimeout(request, target ) { 35 // 调用完成,将_request设为null 36 _request = null; 37 _userContext = userContext; 38 // 触发timeout事件 39 target.timeout.invoke(request, Sys.EventArgs.Empty); 40 } 41 42 function onMethodAborted(request, target ) { 43 // 调用完成,将_request设为null 44 _request = null; 45 _userContext = userContext; 46 // 出发abort事件 47 target.aborted.invoke(request, Sys.EventArgs.Empty); 48 } 49 50 return true; 51 }
整个Sys.Net.ServiceMethodRequest类的代码就分析完了!看得出来它只是对
Sys.Net.ServiceMethod类进行了一个封装,但是就是这种简单的封装就能提供Declarative
Syntax支持!原因就在于Atlas的代码已经对于解析Xml Scripts和识别一个类做了非常多的工作,我们当然就省事了。
不过在这个类中,有一个非常有意思的成员,那就是parameters属性。它是个只读属性,返回一个对象作为参数字典,我们能够对其以key -
value的方式进行设置。事实上我们不是第一次遇到这个东西了。在最常用的Action之一:Sys.InvokeMethodAction就有该参
数,该参数允许这样使用:
<parameters param1="value1" param2="value2" ... />
很自然,我们的parameters属性也能如此使用。Atlas在解析Xml时对于这样的情况,会将Xml属性名和值以key - value的形式存放在该parameters对象中。
但是,这远远不够!我们为什么要在getDescripter方法中给出该属性的定义?目的不是为了给我们的代码调用(只要存在我们代码就能访问,根本
无须通过这个方法获得类成员),而是为了让Declarative
Syntax识别!有了类成员的描述,我们就能使用各种Action,还有Atlas的特色之一:Binding。
对了,大家应该也
已经想到了,我们可以将parameters属性和“别的什么”绑定起来啊。但是先别高兴太早,因为parameters是只读属性,按照普通的方法无法
将一个值赋于该属性。但是Atlas想到了这一点,它的Binding能够处理这种情况。在后面的例子中可以看到,我们可以如此使用Binding。
<bindings> <binding dataContext="txtName" dataPath="text" property="parameters" propertyKey="name" /> <binding dataContext="txtAge" dataPath="text" property="parameters" propertyKey="age" /> </bindings>
这里使用到了目前文档中不曾记载的Binding使用方式,它用到了propertyKey。Atlas在处理Binding时,如果发现用
户提供了propertyKey,则首先使用property的get方法获得这个对象的属性的值,然后再把propertyKey的值作为key,以
key - value的方式设置该属性。这个做法简直就是为了parameters这样的属性量身定制的!
Sys.Net.ServiceMethodRequest类的分析到这里应该就足够了。接下来,我们通过一个例子来具体看一下该如何使用Declarative Syntax调用Web Services方法。
三、使用Declarative Syntax访问Web Services方法范例
首先,我们定义所需要使用的Web Services方法和类:
 EmployeeService.asmx文件代码 1 [WebService(Namespace = "http://tempuri.org/")] 2 [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] 3 public class SampleService : System.Web.Services.WebService { 4 5 [WebMethod] 6 public List<Employee> AddEmployee(string name, int age) 7 { 8 Employee.AllEmployees.Add(new Employee(name, age)); 9 return Employee.AllEmployees; 10 } 11 } 12 13 public class Employee 14 { 15 public static List<Employee> AllEmployees = new List<Employee>(); 16 17 public string Name; 18 19 public int Age; 20 21 public Employee() { } 22 23 public Employee(string name, int age) 24 { 25 this.Name = name; 26 this.Age = age; 27 } 28 }
每次调用AddEmployee方法会在List中增加一个Employee对象,并输出整个Employee对象。
接下来是HTML:
 HTML代码 1 <atlas:ScriptManager runat="server" ID="ScriptManager1" /> 2 3 Name: <input type="text" id="txtName" /><br /> 4 Age: <input type="text" id="txtAge" /><br /> 5 <input type="button" value="invoke" id="btnInvoke" /> 6 <hr /> 7 8 <div id="listView"></div> 9 10 <!-- Layout Elements --> 11 <div style="display: none;"> 12 <!-- Layout Template --> 13 <table id="layoutTemplate" border="1" cellpadding="0" cellspacing="0"> 14 <thead> 15 <tr> 16 <td>Name</td> 17 <td>Age</td> 18 </tr> 19 </thead> 20 <!-- Repeat Template --> 21 <tbody id="itemTemplateParent"> 22 <!-- Repeat Item Template --> 23 <tr id="itemTemplate"> 24 <td><span id="lblName" /></td> 25 <td><span id="lblAge" /></td> 26 </tr> 27 </tbody> 28 </table> 29 <!-- Empty Template --> 30 <div id="emptyTemplate"> 31 No Data 32 </div> 33 </div>
这个范例的目的是在两个文本框(txtName和txtAge)里填写一个Employee的姓名和年龄,然后点击按钮btnInvoke之
后会调用Web
Services方法增加一个Employee并得到一个Employee列表,然后显示在页面上。在这里,我会使用Atlas中的
Sys.UI.Data.ListView控件来显示Employee列表。关于该控件的使用方式,可以参考Dflying兄的文章《使用ASP.NET Atlas ListView控件显示列表数据》。
然后就是最重要的Atlas Xml Scripts了:
 Atlas Xml Scripts 1 <script type="text/xml-script"> 2 <page xmlns:jeffz="http://www.jeffzlive.net"> 3 <components> 4 <button id="btnInvoke"> 5 <click> 6 <invokeMethod target="employeeService" method="invoke" /> 7 </click> 8 </button> 9 10 <textBox id="txtName" /> 11 <textBox id="txtAge" /> 12 13 <serviceMethod id="employeeService" url="EmployeeService.asmx" methodName="AddEmployee" completed="onComplete"> 14 <bindings> 15 <binding dataContext="txtName" dataPath="text" property="parameters" propertyKey="name" /> 16 <binding dataContext="txtAge" dataPath="text" property="parameters" propertyKey="age" /> 17 </bindings> 18 </serviceMethod> 19 20 <listView id="listView" itemTemplateParentElementId="itemTemplateParent"> 21 <layoutTemplate> 22 <template layoutElement="layoutTemplate" /> 23 </layoutTemplate> 24 <itemTemplate> 25 <template layoutElement="itemTemplate"> 26 <label id="lblName"> 27 <bindings> 28 <binding dataPath="Name" property="text" /> 29 </bindings> 30 </label> 31 <label id="lblAge"> 32 <bindings> 33 <binding dataPath="Age" property="text" /> 34 </bindings> 35 </label> 36 </template> 37 </itemTemplate> 38 <emptyTemplate> 39 <template layoutElement="emptyTemplate"/> 40 </emptyTemplate> 41 </listView> 42 </components> 43 </page> 44 </script>
我们关注一下最重要的<serviceMethod />使用吧,正如之前所提到的,我将txtName的值与name参数绑定起来,并且将txtAge的值与age参数绑定起来,就是这么简单。
嗯,先不急着运行,是不是看出什么问题来了?对,我们为什么没有将employeeService的result属性和listView的data属性
绑定起来的呢?否则我们如何获得数据呢?其实我也想,这可以说是Sys.Net.ServiceMethodRequest的一个Bug:它在
result更新是不会调用this.raisePropertyChanged方法!这样Binding怎么可能收到result更新的信息呢?对于这
点我也相当无语。没有办法我们只能响应completed事件,让它调用onComplete这个javascript方法了。onComplete方法
代码如下:
 onComplete方法 1 <script type="text/javascript"> 2 function onComplete(sender, args) 3 { 4 $("listView").control.set_data(sender.get_result()); 5 } 6 </script>
代码非常简单,就这样起效果了。我们来看一下使用吧:
首先打开页面,会看到显示为No Data:

在文本框内输入信息并点击按钮,则可以添加一个Employee,反复多次则添加多个:

四、开发自己的Componet,完全使用Declarative Syntax访问Web Services方法
必须借助于Javascript才能完成任务,是不是总是觉得心理有点别扭?至少我是这样的。而且除此之外,
Sys.Net.ServiceMethodRequest还有一个不合理的地方:它的Priority属性类型是Number,在写Xml的时候就必须
把数字赋予该属性。因此,我们来修改一下它的代码,开发一下一个更好的ServiceMethodRequest吧。
本想继承
Sys.Net.ServiceMethodRequest并重载invoke函数可是令人惊讶的是,我们无法这样做,因为
Sys.Net.ServiceMethodRequest没有调用registerBaseMethod来注册invoke函数。虽然我们依旧可以写一
个this.invoke = function() { ... }也可以正确运行,但是这个不是OO的Good
Practise,因此我还是完整的写了一遍代码。
由于大部分代码和Sys.Net.ServiceMethodRequest相同,那么我就给出一小部分不同的代码吧。
 Jeffz.Net.ServiceMethodRequest部分代码 1 Type.registerNamespace('Jeffz.Net'); 2 3 Jeffz.Net.ServiceMethodRequest = function() { 4 Jeffz.Net.ServiceMethodRequest.initializeBase(this); 5 6 …… 7 8 var _onCompleteHander = null; 9 var _onErrorHander = null; 10 11 …… 12 13 this.get_parameters = function() { 14 if (_parameters == null) { 15 _parameters = Sys.Component.createCollection(this); 16 } 17 return _parameters; 18 } 19 20 …… 21 22 this.invoke = function(userContext) { 23 …… 24 25 var params = new Object(); 26 for (var i = 0; i < _parameters.length; i++) 27 { 28 params[_parameters[i].get_name()] = _parameters[i].get_value() 29 } 30 31 if (_onCompleteHander == null) 32 { 33 _onCompleteHander = Function.createDelegate(this, onMethodComplete); 34 } 35 36 if (_onErrorHander == null) 37 { 38 _onErrorHander = Function.createDelegate(this, onMethodError); 39 } 40 41 var serviceMethod = new Sys.Net.ServiceMethod(_url, _methodName, _appUrl); 42 _request = serviceMethod.invoke(params, _onCompleteHander, onMethodTimeout, 43 _onCompleteHander, onMethodAborted, this, 44 _timeoutInterval, _priority); 45 46 47 function onMethodComplete(result, response, target) { 48 …… 49 this.raisePropertyChanged("result");
|