深入Atlas系列:Web Sevices Access in Atlas示例(1) - 特别的访问方式
2006-10-12 14:32 by 老赵, 3339 visits
注意:部分内容已经过期,请结合《深入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方法。由于Sys.Net.ServiceMethod的invoke方法提供了“神奇”的“函数重载”(详细信息请见《深入Atlas系列:Web Sevices Access in Atlas(1) - 客户端支持》的分析),因此Sys.Net.ServiceMethod.invoke静态方法也能通过两种方式调用,如下:
第一种是:
第二种是:
于是,以后就能使用这种比较简洁的方式访问Web Services方法了。
二、使用Declarative Syntax访问Web Services方法
深入代码可以发现各种有效的使用方式,尤其在现在这样文档极其匮乏的时期。事实上,对于使用Declarative Syntax访问Web Services的描述才是这篇文章的重点。仔细Atlas代码之后,可以发现在这样一个类:
在我之前的文章《使用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中的实现:
以上就是Sys.Net.ServiceMethodRequest类的成员描述,应该说还是相当直观的。
接着开始分析Sys.Net.ServiceMethodRequest类的主要成员,一些属性的get/set方法就忽略了。其实整个类唯一作用较大的方法就是this.invoke。代码如下:
整个Sys.Net.ServiceMethodRequest类的代码就分析完了!看得出来它只是对Sys.Net.ServiceMethod类进行了一个封装,但是就是这种简单的封装就能提供Declarative Syntax支持!原因就在于Atlas的代码已经对于解析Xml Scripts和识别一个类做了非常多的工作,我们当然就省事了。
不过在这个类中,有一个非常有意思的成员,那就是parameters属性。它是个只读属性,返回一个对象作为参数字典,我们能够对其以key - value的方式进行设置。事实上我们不是第一次遇到这个东西了。在最常用的Action之一:Sys.InvokeMethodAction就有该参数,该参数允许这样使用:
很自然,我们的parameters属性也能如此使用。Atlas在解析Xml时对于这样的情况,会将Xml属性名和值以key - value的形式存放在该parameters对象中。
但是,这远远不够!我们为什么要在getDescripter方法中给出该属性的定义?目的不是为了给我们的代码调用(只要存在我们代码就能访问,根本无须通过这个方法获得类成员),而是为了让Declarative Syntax识别!有了类成员的描述,我们就能使用各种Action,还有Atlas的特色之一:Binding。
对了,大家应该也已经想到了,我们可以将parameters属性和“别的什么”绑定起来啊。但是先别高兴太早,因为parameters是只读属性,按照普通的方法无法将一个值赋于该属性。但是Atlas想到了这一点,它的Binding能够处理这种情况。在后面的例子中可以看到,我们可以如此使用Binding。
这里使用到了目前文档中不曾记载的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方法和类:
每次调用AddEmployee方法会在List中增加一个Employee对象,并输出整个Employee对象。
接下来是HTML:
这个范例的目的是在两个文本框(txtName和txtAge)里填写一个Employee的姓名和年龄,然后点击按钮btnInvoke之后会调用Web Services方法增加一个Employee并得到一个Employee列表,然后显示在页面上。在这里,我会使用Atlas中的Sys.UI.Data.ListView控件来显示Employee列表。关于该控件的使用方式,可以参考Dflying兄的文章《使用ASP.NET Atlas ListView控件显示列表数据》。
然后就是最重要的Atlas Xml Scripts了:
我们关注一下最重要的<serviceMethod />使用吧,正如之前所提到的,我将txtName的值与name参数绑定起来,并且将txtAge的值与age参数绑定起来,就是这么简单。
嗯,先不急着运行,是不是看出什么问题来了?对,我们为什么没有将employeeService的result属性和listView的data属性绑定起来的呢?否则我们如何获得数据呢?其实我也想,这可以说是Sys.Net.ServiceMethodRequest的一个Bug:它在result更新是不会调用this.raisePropertyChanged方法!这样Binding怎么可能收到result更新的信息呢?对于这点我也相当无语。没有办法我们只能响应completed事件,让它调用onComplete这个javascript方法了。onComplete方法代码如下:
代码非常简单,就这样起效果了。我们来看一下使用吧:
首先打开页面,会看到显示为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相同,那么我就给出一小部分不同的代码吧。
可以看到,我在onMethodComplete和onMethodError方法里都加了this.raisePropertyChanged("result")的调用。于是Binding就能收到result改变的消息,重新去获得它的值了。为了让“this”引用在上面两个回调函数内被正确的指向,因此使用了Function.createDelegate方法。在我之前的文章《使用Atlas创建自己的Client Control》也提到过这一点。另外,我还将priority属性改成了Sys.Net.WebRequestPriority枚举类型,这样我们就能使用"High"和"Normal"等字样了。
等等,你还发现了什么?我将parameters属性的类型改成了Array,它的get方法也变了。这是我的一个尝试,将parameters属性变成了一个Jeffz.Net.Parameter的Collection。属性类型方面的细节涉及到对于Atlas Xml Scripts的解析,因此现在不做讨论。Jeffz.Net.Parameter很简单,代码如下:
于是,相关的Declarative Syntax也会有所改变。但是请注意,这只是我的一个尝试,这并不是一个好的做法,没有任何实用价值。
我们来看一下它的使用,基本上代码没有变,只是改变了部分Atlas Xml Scripts:
可能最值得注意的就是在<listView />内使用了Binding,现在一行Javascript代码都不用写就能运行了,这就是我们所需要的!对于现在的parameters的做法,再强调一下,对于目前的问题,它没有任何实用价值。
由于使用效果和上面一例完全相同,因此就不做演示了。:)
点击这里下载两个范例源文件。
点击这里查看“使用Declarative Syntax访问Web Services方法”范例效果。
点击这里查看“开发自己的Componet,完全使用Declarative Syntax访问Web Services方法”范例效果。
在《深入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 }
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 });
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);
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);
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');
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 }
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 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 }
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>
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>
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>
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");
50 ……
51 }
52
53 function onMethodError(result, response, target) {
54 ……
55 this.raisePropertyChanged("result");
56 ……
57 }
58
59 ……
60 }
61 Jeffz.Net.ServiceMethodRequest.registerBaseMethod(this, 'invoke');
62
63 ……
64
65 this.getDescriptor = function() {
66 var td = Jeffz.Net.ServiceMethodRequest.callBaseMethod(this, 'getDescriptor');
67
68 ……
69
70 td.addProperty('parameters', Array, true);
71
72 ……
73
74 td.addProperty('priority', Sys.Net.WebRequestPriority);
75
76 ……
77 }
78 Jeffz.Net.ServiceMethodRequest.registerBaseMethod(this, 'getDescriptor');
79
80
81 this.dispose = function() {
82 _parameters.dispose();
83 _parameters = null;
84
85 _onCompleteHander = null;
86 _onErrorHander = null;
87
88 Jeffz.Net.ServiceMethodRequest.callBaseMethod(this, 'dispose');
89 }
90 Jeffz.Net.ServiceMethodRequest.registerBaseMethod(this, 'dispose');
91 }
92 Jeffz.Net.ServiceMethodRequest.registerClass('Jeffz.Net.ServiceMethodRequest', Sys.Component);
93 Sys.TypeDescriptor.addType('jeffz', 'serviceMethod', 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");
50 ……
51 }
52
53 function onMethodError(result, response, target) {
54 ……
55 this.raisePropertyChanged("result");
56 ……
57 }
58
59 ……
60 }
61 Jeffz.Net.ServiceMethodRequest.registerBaseMethod(this, 'invoke');
62
63 ……
64
65 this.getDescriptor = function() {
66 var td = Jeffz.Net.ServiceMethodRequest.callBaseMethod(this, 'getDescriptor');
67
68 ……
69
70 td.addProperty('parameters', Array, true);
71
72 ……
73
74 td.addProperty('priority', Sys.Net.WebRequestPriority);
75
76 ……
77 }
78 Jeffz.Net.ServiceMethodRequest.registerBaseMethod(this, 'getDescriptor');
79
80
81 this.dispose = function() {
82 _parameters.dispose();
83 _parameters = null;
84
85 _onCompleteHander = null;
86 _onErrorHander = null;
87
88 Jeffz.Net.ServiceMethodRequest.callBaseMethod(this, 'dispose');
89 }
90 Jeffz.Net.ServiceMethodRequest.registerBaseMethod(this, 'dispose');
91 }
92 Jeffz.Net.ServiceMethodRequest.registerClass('Jeffz.Net.ServiceMethodRequest', Sys.Component);
93 Sys.TypeDescriptor.addType('jeffz', 'serviceMethod', Jeffz.Net.ServiceMethodRequest);
可以看到,我在onMethodComplete和onMethodError方法里都加了this.raisePropertyChanged("result")的调用。于是Binding就能收到result改变的消息,重新去获得它的值了。为了让“this”引用在上面两个回调函数内被正确的指向,因此使用了Function.createDelegate方法。在我之前的文章《使用Atlas创建自己的Client Control》也提到过这一点。另外,我还将priority属性改成了Sys.Net.WebRequestPriority枚举类型,这样我们就能使用"High"和"Normal"等字样了。
等等,你还发现了什么?我将parameters属性的类型改成了Array,它的get方法也变了。这是我的一个尝试,将parameters属性变成了一个Jeffz.Net.Parameter的Collection。属性类型方面的细节涉及到对于Atlas Xml Scripts的解析,因此现在不做讨论。Jeffz.Net.Parameter很简单,代码如下:
Jeffz.Net.Parameter代码
1 Jeffz.Net.Parameter = function()
2 {
3 Jeffz.Net.Parameter.initializeBase(this);
4
5 var _name = null;
6 var _value = null;
7
8 this.get_name = function()
9 {
10 return _name;
11 }
12
13 this.set_name = function(value)
14 {
15 _name = value;
16 }
17
18 this.get_value = function()
19 {
20 return _value;
21 }
22
23 this.set_value = function(value)
24 {
25 _value = value;
26 }
27
28 this.setOwner = function(owner) {
29 this._owner = owner;
30 }
31
32 this.getDescriptor = function() {
33 var td = Jeffz.Net.Parameter.callBaseMethod(this, 'getDescriptor');
34
35 td.addProperty('name', String);
36 td.addProperty('value', String);
37
38 return td;
39 }
40 Jeffz.Net.ServiceMethodRequest.registerBaseMethod(this, 'getDescriptor');
41 }
42 Jeffz.Net.Parameter.registerSealedClass('Jeffz.Net.Parameter', Sys.Component);
43 Sys.TypeDescriptor.addType('jeffz', 'param', Jeffz.Net.Parameter);
1 Jeffz.Net.Parameter = function()
2 {
3 Jeffz.Net.Parameter.initializeBase(this);
4
5 var _name = null;
6 var _value = null;
7
8 this.get_name = function()
9 {
10 return _name;
11 }
12
13 this.set_name = function(value)
14 {
15 _name = value;
16 }
17
18 this.get_value = function()
19 {
20 return _value;
21 }
22
23 this.set_value = function(value)
24 {
25 _value = value;
26 }
27
28 this.setOwner = function(owner) {
29 this._owner = owner;
30 }
31
32 this.getDescriptor = function() {
33 var td = Jeffz.Net.Parameter.callBaseMethod(this, 'getDescriptor');
34
35 td.addProperty('name', String);
36 td.addProperty('value', String);
37
38 return td;
39 }
40 Jeffz.Net.ServiceMethodRequest.registerBaseMethod(this, 'getDescriptor');
41 }
42 Jeffz.Net.Parameter.registerSealedClass('Jeffz.Net.Parameter', Sys.Component);
43 Sys.TypeDescriptor.addType('jeffz', 'param', Jeffz.Net.Parameter);
于是,相关的Declarative Syntax也会有所改变。但是请注意,这只是我的一个尝试,这并不是一个好的做法,没有任何实用价值。
我们来看一下它的使用,基本上代码没有变,只是改变了部分Atlas Xml Scripts:
Atlas Xml Scripts部分代码
1 <script type="text/xml-script">
2 <page xmlns:jeffz="http://www.jeffzlive.net">
3 <components>
4
5 ……
6
7 <jeffz:serviceMethod id="employeeService" url="EmployeeService.asmx" methodName="AddEmployee" priority="High">
8 <parameters>
9 <jeffz:param name="name">
10 <bindings>
11 <binding dataContext="txtName" dataPath="text" property="value" />
12 </bindings>
13 </jeffz:param>
14 <jeffz:param name="age">
15 <bindings>
16 <binding dataContext="txtAge" dataPath="text" property="value" />
17 </bindings>
18 </jeffz:param>
19 </parameters>
20 </jeffz:serviceMethod>
21
22 <listView id="listView" itemTemplateParentElementId="itemTemplateParent">
23 <bindings>
24 <binding dataContext="employeeService" dataPath="result" property="data" />
25 </bindings>
26 ……
27 </listView>
28 </components>
29 </page>
30 </script>
1 <script type="text/xml-script">
2 <page xmlns:jeffz="http://www.jeffzlive.net">
3 <components>
4
5 ……
6
7 <jeffz:serviceMethod id="employeeService" url="EmployeeService.asmx" methodName="AddEmployee" priority="High">
8 <parameters>
9 <jeffz:param name="name">
10 <bindings>
11 <binding dataContext="txtName" dataPath="text" property="value" />
12 </bindings>
13 </jeffz:param>
14 <jeffz:param name="age">
15 <bindings>
16 <binding dataContext="txtAge" dataPath="text" property="value" />
17 </bindings>
18 </jeffz:param>
19 </parameters>
20 </jeffz:serviceMethod>
21
22 <listView id="listView" itemTemplateParentElementId="itemTemplateParent">
23 <bindings>
24 <binding dataContext="employeeService" dataPath="result" property="data" />
25 </bindings>
26 ……
27 </listView>
28 </components>
29 </page>
30 </script>
可能最值得注意的就是在<listView />内使用了Binding,现在一行Javascript代码都不用写就能运行了,这就是我们所需要的!对于现在的parameters的做法,再强调一下,对于目前的问题,它没有任何实用价值。
由于使用效果和上面一例完全相同,因此就不做演示了。:)
点击这里下载两个范例源文件。
点击这里查看“使用Declarative Syntax访问Web Services方法”范例效果。
点击这里查看“开发自己的Componet,完全使用Declarative Syntax访问Web Services方法”范例效果。
这篇文章提到的问题比较简单,不过为了保持完整性也加到“深入”Atlas系列了……