使用Web Service Proxy应该是使用ASP.NET AJAX访问Web Service最常用的方法了。服务器端会根据ScriptManager中添加的Service引用而对于Web Service类进行分析,并生成相应的客户端脚本。这样开发人员就能在客户端方便而且直观地访问Web Services方法了。这是ASP.NET中很重要的功能。
从官方文档上看来,CTP和RTM似乎在脚本使用这方面没有很大的改变,只要在服务器端将一些CustomAttribute改变一下就可以了。的确没错,在使用方式上只有这点细微改变,但是事实上,从生成脚本本身来说,CTP和RTM的做法大相径庭。它们的之间的区别主要有两点:
- 对于生成服务器端类型的脚本(例如您可以使用new Jeffz.WebServiceProxyDemo.Employee()来构造一个对象)来说,CTP中可以自定义,RTM则不可以。
- 对于生成WebService方法的代理来说,CTP生成的代码很少,RTM生成的代码很多,但是符合客户端“类”的标准。也就是说,RTM版本完全将一个服务器端的类“映射”到了客户端——当然只有需要生成代理的那些方法,也不包括其实现。另外,RTM中增加了Cache功能,这是通过返回status code为304实现的。
至于第2点,个人认为是配合了目前的Web Serivce访问方式而改变的。它遵守了以下的规范和特点:
- 使用ASP.NET AJAX中定义“类”规范将服务器端Web Service类映射到了客户端中。
- 提供了客户端访问Web Service方法所需要的proxy。
- 支持defaultUserContext、defaultSucceededCallback和defaultFailedCallback。
首先,我们在CTP和RTM中定义一个相同的复杂类型:Employee。代码如下:
namespace
Jeffz.WebServicesProxyDemo
{
public class Employee
{
public string Name;
public int Age;
}
}
{
public class Employee
{
public string Name;
public int Age;
}
}
再为CTP定义一个Web Service,其中有一个GetEmployee方法。代码如下:
namespace Jeffz.WebServicesProxyDemo
{
public class EmployeeService : WebService
{
[WebMethod]
public Employee GetEmployee( string name, int age)
{
Employee emp = new Employee();
emp.Name = name;
emp.Age = age;
return emp;
}
}
}
{
public class EmployeeService : WebService
{
[WebMethod]
public Employee GetEmployee( string name, int age)
{
Employee emp = new Employee();
emp.Name = name;
emp.Age = age;
return emp;
}
}
}
然后在CTP的页面中添加ScriptManager定义。如下:
<
atlas:ScriptManager
runat
="server"
ID
="ScriptManager1"
>
< Services >
< atlas:ServiceReference InlineProxy ="true" Path ="EmployeeService.asmx" />
Services >
atlas:ScriptManager >
< Services >
< atlas:ServiceReference InlineProxy ="true" Path ="EmployeeService.asmx" />
Services >
atlas:ScriptManager >
InlineProxy设为true,则会把这些Proxy脚本输出在页面中,而不是在页面中通过添加一个脚本引用。而输出的脚本也只有寥寥数行,如下:
Type.registerNamespace('Jeffz.WebServicesProxyDemo');
Jeffz.WebServicesProxyDemo.EmployeeService = new function ()
{
this .appPath = " http://localhost:2481/Atlas-CTP/ " ;
var cm = Sys.Net.ServiceMethod.createProxyMethod;
cm( this , " GetEmployee " , " name " , " age " );
}
Jeffz.WebServicesProxyDemo.EmployeeService.path = '/Atlas-CTP/WebServicesProxyDemo/EmployeeService.asmx';
Jeffz.WebServicesProxyDemo.EmployeeService = new function ()
{
this .appPath = " http://localhost:2481/Atlas-CTP/ " ;
var cm = Sys.Net.ServiceMethod.createProxyMethod;
cm( this , " GetEmployee " , " name " , " age " );
}
Jeffz.WebServicesProxyDemo.EmployeeService.path = '/Atlas-CTP/WebServicesProxyDemo/EmployeeService.asmx';
可以看出,这里的一个关键,就是Sys.Net.ServiceMethod.createProxyMethod,它会为Jeffz.WebServiceProxyDemo.EmployeeService对象添加一个GetEmployee方法。代码如下:
//
访问该方法时:
// Sys.Net.ServiceMethod.createProxyMethod(
// proxy, methodName, argName1, argName2, ...)
Sys.Net.ServiceMethod.createProxyMethod = function (proxy, methodName)
{
var numOfParams = arguments.length - 2 ;
var createWebMethodArguments = arguments;
// 下面就是方法(例如“GetEmployee”)的真正定义
proxy[methodName] = function ()
{
// 就是大名鼎鼎的params对象,传递给服务器端方法的参数
var args = {};
for ( var i = 0 ; i < numOfParams; i ++ )
{
// 参数不能是function
if ( typeof (arguments[i]) == ' function ')
{
throw Error.createError(
String.format(
" Parameter #{0} passed to method '{1}' should not be a function " ,
i + 1 , methodName));
}
// 一一指定参数
args[createWebMethodArguments[i + 2 ]] = arguments[i];
}
// 传递给Sys.Net.ServiceMethod.invoke方法的参数
var callMethodArgs = [ proxy.path, methodName, proxy.appPath, args ];
// 准备callMethodArgs内的各个callback,priority,userContext等。
for ( var i = 0 ; i + numOfParams < arguments.length; i ++ )
{
callMethodArgs[i + 4 ] = arguments[numOfParams + i];
}
// 调用Sys.Net.ServiceMethod.invoke方法
return Sys.Net.ServiceMethod.invoke.apply( null , callMethodArgs);
}
}
// Sys.Net.ServiceMethod.createProxyMethod(
// proxy, methodName, argName1, argName2, ...)
Sys.Net.ServiceMethod.createProxyMethod = function (proxy, methodName)
{
var numOfParams = arguments.length - 2 ;
var createWebMethodArguments = arguments;
// 下面就是方法(例如“GetEmployee”)的真正定义
proxy[methodName] = function ()
{
// 就是大名鼎鼎的params对象,传递给服务器端方法的参数
var args = {};
for ( var i = 0 ; i < numOfParams; i ++ )
{
// 参数不能是function
if ( typeof (arguments[i]) == ' function ')
{
throw Error.createError(
String.format(
" Parameter #{0} passed to method '{1}' should not be a function " ,
i + 1 , methodName));
}
// 一一指定参数
args[createWebMethodArguments[i + 2 ]] = arguments[i];
}
// 传递给Sys.Net.ServiceMethod.invoke方法的参数
var callMethodArgs = [ proxy.path, methodName, proxy.appPath, args ];
// 准备callMethodArgs内的各个callback,priority,userContext等。
for ( var i = 0 ; i + numOfParams < arguments.length; i ++ )
{
callMethodArgs[i + 4 ] = arguments[numOfParams + i];
}
// 调用Sys.Net.ServiceMethod.invoke方法
return Sys.Net.ServiceMethod.invoke.apply( null , callMethodArgs);
}
}
Proxy就是这样生成的。可以看出,在这里Jeffz.WebServicesProxyDemo.EmployeeService像是服务器端对应的Web Service类的一个客户端实例,而且还是个Singleton。这个实例中存在着一个个方法,使用这些方法能够方便地调用服务器端的Web Service方法。
与CTP不同的是,RTM中由于每个Web Service类的功能增多(例如增加了defaultSuccessCallback属性等),因此在客户端输出了一个完整的Web Service类的定义,然后在初始化一个Singleton实例。再将我们需要使用的方法定义成Static方法,在Static方法中会将功能委托给Singleton实例的相应方法。具体如下:
依旧使用上面的Employee类与GetEmployee方法(在GetEmployee方法总必须加上ScriptServiceAttribute标记)。ScriptManager的使用稍微有些变化。如下:
<
asp:ScriptManager
runat
="server"
ID
="ScriptManager1"
ScriptMode
="Debug"
>
< Services >
< asp:ServiceReference InlineScript ="true" Path ="EmployeeService.asmx" />
Services >
asp:ScriptManager >
< Services >
< asp:ServiceReference InlineScript ="true" Path ="EmployeeService.asmx" />
Services >
asp:ScriptManager >
InlineScript的作用和之前的InlineProxy相同。请注意这里我将ScriptManager的ScriptMode设为了Debug,这样页面上生成的代码会相对容易阅读,并且增加了必要的注释和参数验证代码。
首先是注册了命名空间和构造函数:
Type.registerNamespace('Jeffz.WebServicesProxyDemo');
Jeffz.WebServicesProxyDemo.EmployeeService = function ()
{
this ._timeout = 0 ;
this ._userContext = null ;
this ._succeeded = null ;
this ._failed = null ;
}
Jeffz.WebServicesProxyDemo.EmployeeService = function ()
{
this ._timeout = 0 ;
this ._userContext = null ;
this ._succeeded = null ;
this ._failed = null ;
}
然后就是使用prototype来定义类的方法了。可以看到它定义GetEmployee方法的脚本:
Jeffz.WebServicesProxyDemo.EmployeeService.prototype
=
{
GetEmployee: function (name,age,succeededCallback, failedCallback, userContext)
{
// /Invoke the GetEmployee WebMethod
// / WebMethod parameter: name(type: String)
// / WebMethod parameter: age(type: Int32)
// / Callback on successful completion of request
// / Callback on failure of request
// / User context data (any JavaScript type)
return Sys.Net._WebMethod._invoke.apply (
null ,
[
this ,
'GetEmployee',
'Jeffz.WebServicesProxyDemo.EmployeeService.GetEmployee',
false ,
{name : name, age : age},
succeededCallback,
failedCallback,
userContext
]);
},
……
}
{
GetEmployee: function (name,age,succeededCallback, failedCallback, userContext)
{
// /
// / WebMethod parameter: name(type: String)
// / WebMethod parameter: age(type: Int32)
// / Callback on successful completion of request
// / Callback on failure of request
// / User context data (any JavaScript type)
return Sys.Net._WebMethod._invoke.apply (
null ,
[
this ,
'GetEmployee',
'Jeffz.WebServicesProxyDemo.EmployeeService.GetEmployee',
false ,
{name : name, age : age},
succeededCallback,
failedCallback,
userContext
]);
},
……
}
可以看出,Proxy使用了Sys.Net._WebMethod._invoke方法访问服务器端,并将this传递作为proxy变量传递给该方法。将自身作为proxy传递给Sys.Net._WebMethod是ASP.NET AJAX客户端脚本中大量使用的方法,这个方法有个好处就是能够集中地管理proxy的功能,因为这些功能往往只和某个Web Service方法的访问有关。但是如果需要共享proxy的话,那么只有另外定义一个proxy对象了。
接下来就是对于proxy一些必须的方法的定义了,如下:
proxy必须的方法
Jeffz.WebServicesProxyDemo.EmployeeService.prototype =
{
……
_get_path: function()
{
return Jeffz.WebServicesProxyDemo.EmployeeService.get_path();
},
set_timeout: function(value)
{
///Sets the timeout for this service.
/// The timeout in milliseconds.
var e = Function._validateParams(arguments, [{name: 'timeout', type: Number}]);
if (e) throw e;
if (value < 0) {
throw Error.argumentOutOfRange('value', value, Sys.Res.invalidTimeout);
}
this._timeout = value;
},
get_timeout: function()
{
///Returns the timeout in milliseconds for this service.
///The timeout in milliseconds for the service.
return this._timeout;
},
set_defaultUserContext: function(value)
{
///Sets the default userContext for this service.
/// The default userContext for this service.
this._userContext = value;
},
get_defaultUserContext: function()
{
///Returns the default userContext for this service.
///Returns the default userContext for this service.
return this._userContext;
},
set_defaultSucceededCallback: function(value)
{
///Sets the default succeededCallback for this service.
/// The default succeededCallback for this service.
var e = Function._validateParams(arguments, [{name: 'defaultSucceededCallback', type: Function}]);
if (e) throw e;
this._succeeded = value;
},
get_defaultSucceededCallback: function()
{
///Returns the default succeededCallback for this service.
///Returns the default succeededCallback for this service.
return this._succeeded;
},
set_defaultFailedCallback: function(value)
{
///Sets the default FailedCallback for this service.
/// The default FailedCallback for this service.
var e = Function._validateParams(arguments, [{name: 'set_defaultFailedCallback', type: Function}]);
if (e) throw e;
this._failed = value;
},
get_defaultFailedCallback: function()
{
///Returns the default failedCallback for this service.
///Returns the default failedCallback for this service.
return this._failed;
}
}
Jeffz.WebServicesProxyDemo.EmployeeService.prototype =
{
……
_get_path: function()
{
return Jeffz.WebServicesProxyDemo.EmployeeService.get_path();
},
set_timeout: function(value)
{
///
/// The timeout in milliseconds.
var e = Function._validateParams(arguments, [{name: 'timeout', type: Number}]);
if (e) throw e;
if (value < 0) {
throw Error.argumentOutOfRange('value', value, Sys.Res.invalidTimeout);
}
this._timeout = value;
},
get_timeout: function()
{
///
///
return this._timeout;
},
set_defaultUserContext: function(value)
{
///
/// The default userContext for this service.
this._userContext = value;
},
get_defaultUserContext: function()
{
///
///
return this._userContext;
},
set_defaultSucceededCallback: function(value)
{
///
/// The default succeededCallback for this service.
var e = Function._validateParams(arguments, [{name: 'defaultSucceededCallback', type: Function}]);
if (e) throw e;
this._succeeded = value;
},
get_defaultSucceededCallback: function()
{
///
///
return this._succeeded;
},
set_defaultFailedCallback: function(value)
{
///
/// The default FailedCallback for this service.
var e = Function._validateParams(arguments, [{name: 'set_defaultFailedCallback', type: Function}]);
if (e) throw e;
this._failed = value;
},
get_defaultFailedCallback: function()
{
///
///
return this._failed;
}
}
然后建立一个Singleton对象,所有的工作将由该对象完成:
Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance
=
new
Jeffz.WebServicesProxyDemo.EmployeeService();
接着定义Jeffz.WebServicesProxyDemo上的静态方法,并将实现委托给Singleton对象的相应方法。如下:
static方法
Jeffz.WebServicesProxyDemo.EmployeeService.set_path = function(value)
{
///Sets the service url.
/// The service url.
var e = Function._validateParams(arguments, [{name: 'path', type: String}]);
if (e) throw e; Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._path = value;
}
Jeffz.WebServicesProxyDemo.EmployeeService.get_path = function()
{
///Returns the service url.
///The service url.
return Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._path;
}
Jeffz.WebServicesProxyDemo.EmployeeService.set_timeout = function(value)
{
///Sets the service timeout.
/// The service timeout.
var e = Function._validateParams(arguments, [{name: 'timeout', type: Number}]);
if (e) throw e;
if (value < 0)
{
throw Error.argumentOutOfRange('value', value, Sys.Res.invalidTimeout);
}
Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._timeout = value;
}
Jeffz.WebServicesProxyDemo.EmployeeService.get_timeout = function()
{
///Returns the service timeout.
///The service timeout.
return Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._timeout;
}
Jeffz.WebServicesProxyDemo.EmployeeService.set_defaultUserContext = function(value)
{
///Sets the service default userContext.
/// The service default user context.
Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._userContext = value;
}
Jeffz.WebServicesProxyDemo.EmployeeService.get_defaultUserContext = function()
{
///Returns the service default user context.
///The service default user context.
return Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._userContext;
}
Jeffz.WebServicesProxyDemo.EmployeeService.set_defaultSucceededCallback = function(value)
{
///Sets the service default succeeded callback.
/// The service default succeed callback function.
var e = Function._validateParams(arguments, [{name: 'defaultSucceededCallback', type: Function}]);
if (e) throw e;
Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._succeeded = value;
}
Jeffz.WebServicesProxyDemo.EmployeeService.get_defaultSucceededCallback = function()
{
///Returns the service default succeeded callback.
///The service default succeeded callback.
return Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._succeeded;
}
Jeffz.WebServicesProxyDemo.EmployeeService.set_defaultFailedCallback = function(value)
{
///Sets the service default failed callback function.
/// The service default failed callback function.
var e = Function._validateParams(arguments, [{name: 'defaultFailedCallback', type: Function}]);
if (e) throw e;
Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._failed = value;
}
Jeffz.WebServicesProxyDemo.EmployeeService.get_defaultFailedCallback = function()
{
///Returns the service default failed callback function.
///The service default failed callback function.
return Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._failed;
}
Jeffz.WebServicesProxyDemo.EmployeeService.set_path = function(value)
{
///
/// The service url.
var e = Function._validateParams(arguments, [{name: 'path', type: String}]);
if (e) throw e; Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._path = value;
}
Jeffz.WebServicesProxyDemo.EmployeeService.get_path = function()
{
///
///
return Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._path;
}
Jeffz.WebServicesProxyDemo.EmployeeService.set_timeout = function(value)
{
///
/// The service timeout.
var e = Function._validateParams(arguments, [{name: 'timeout', type: Number}]);
if (e) throw e;
if (value < 0)
{
throw Error.argumentOutOfRange('value', value, Sys.Res.invalidTimeout);
}
Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._timeout = value;
}
Jeffz.WebServicesProxyDemo.EmployeeService.get_timeout = function()
{
///
///
return Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._timeout;
}
Jeffz.WebServicesProxyDemo.EmployeeService.set_defaultUserContext = function(value)
{
///
/// The service default user context.
Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._userContext = value;
}
Jeffz.WebServicesProxyDemo.EmployeeService.get_defaultUserContext = function()
{
///
///
return Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._userContext;
}
Jeffz.WebServicesProxyDemo.EmployeeService.set_defaultSucceededCallback = function(value)
{
///
/// The service default succeed callback function.
var e = Function._validateParams(arguments, [{name: 'defaultSucceededCallback', type: Function}]);
if (e) throw e;
Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._succeeded = value;
}
Jeffz.WebServicesProxyDemo.EmployeeService.get_defaultSucceededCallback = function()
{
///
///
return Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._succeeded;
}
Jeffz.WebServicesProxyDemo.EmployeeService.set_defaultFailedCallback = function(value)
{
///
/// The service default failed callback function.
var e = Function._validateParams(arguments, [{name: 'defaultFailedCallback', type: Function}]);
if (e) throw e;
Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._failed = value;
}
Jeffz.WebServicesProxyDemo.EmployeeService.get_defaultFailedCallback = function()
{
///
///
return Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._failed;
}
最后,设置一下Web Service方法需要的路径:
Jeffz.WebServicesProxyDemo.EmployeeService.set_path(
"
/Value-add-WebSite/WebServicesDemo/EmployeeService.asmx
"
);
当然,也不要忘了最重要的事情:将静态GetEmployee方法的功能委托给Singleton对象的GetEmployee方法完成。如下:
Jeffz.WebServicesProxyDemo.EmployeeService.GetEmployee
=
function
(name, age, onSuccess, onFailed, userContext)
{
// /Invoke the GetEmployee WebMethod
// / WebMethod parameter: name(type: String)
// / WebMethod parameter: age(type: Int32)
// / Callback on successful completion of request
// / Callback on failure of request
// / User context data (any JavaScript type)
Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance.GetEmployee(name, age, onSuccess, onFailed, userContext);
}
{
// /
// / WebMethod parameter: name(type: String)
// / WebMethod parameter: age(type: Int32)
// / Callback on successful completion of request
// / Callback on failure of request
// / User context data (any JavaScript type)
Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance.GetEmployee(name, age, onSuccess, onFailed, userContext);
}
可以看到,虽然同样是使用Jeffz.WebServicesProxyDemo.EmployeeService.GetEmployee访问Web Service方法,但是CTP和RTM版本中的逻辑可以说相差了很多。虽然RTM版本生成的脚本多出许多,还好有了Cache机制,性能上不会有什么影响,而且RTM的功能也增强了。除去“自定义能力”的丢失之外,RTM的Web Service访问的确有了进步。
至于生成Employee类的客户端脚本,CTP和RTM几乎一模一样,而且总共只有寥寥数行,在这里就不多加说明了。