深入Atlas系列:Web Sevices Access in Atlas(8) - RTM中可叹的Web Service Proxy

  在RTM Release之前,我已经差不多将Web Service Proxy的分析写完了,可惜一个“惊天地泣鬼神”的RTM一出,这片文章的诞生晚了20多天。

使用Web Service Proxy应该是使用ASP.NET AJAX访问Web Service最常用的方法了。服务器端会根据ScriptManager中添加的Service引用而对于Web Service类进行分析,并生成相应的客户端脚本。这样开发人员就能在客户端方便而且直观地访问Web Services方法了。这是ASP.NET中很重要的功能。

从官方文档上看来,CTP和RTM似乎在脚本使用这方面没有很大的改变,只要在服务器端将一些CustomAttribute改变一下就可以了。的确没错,在使用方式上只有这点细微改变,但是事实上,从生成脚本本身来说,CTP和RTM的做法大相径庭。它们的之间的区别主要有两点:
  1. 对于生成服务器端类型的脚本(例如您可以使用new Jeffz.WebServiceProxyDemo.Employee()来构造一个对象)来说,CTP中可以自定义,RTM则不可以。
  2. 对于生成WebService方法的代理来说,CTP生成的代码很少,RTM生成的代码很多,但是符合客户端“类”的标准。也就是说,RTM版本完全将一个服务器端的类“映射”到了客户端——当然只有需要生成代理的那些方法,也不包括其实现。另外,RTM中增加了Cache功能,这是通过返回status code为304实现的。
  我在标题中的“可叹”大都是指第1点。在RTM中,客户端Serialize方法取消了对于自定义序列化的支持,这样在服务器端也少了相应自定义服务器端类型脚本的功能。原本能够使用自定义的办法,“完美”地在服务器端和客户端处理复杂对象中的“循环引用”问题,现在当然已经行不通了。从这一点上来说,真不知道算是进步了还是退步了,虽然依旧能够使用JavaScriptConveter进行扩展,但是从“美学”上来说,现在的做法只能属于“workaround”的范畴。而且,对于服务器端代码的分析也没有很大的价值了,因此在这片文中我只是对于客户端的Proxy脚本进行一些分析和比较。

至于第2点,个人认为是配合了目前的Web Serivce访问方式而改变的。它遵守了以下的规范和特点:
  1. 使用ASP.NET AJAX中定义“类”规范将服务器端Web Service类映射到了客户端中。 
  2. 提供了客户端访问Web Service方法所需要的proxy。
  3. 支持defaultUserContext、defaultSucceededCallback和defaultFailedCallback。
  对了更清晰地理解CTP和RTM之间的区别,以及RTM中构造Proxy的形式,我们来分别看一下CTP和RTM中的proxy脚本(以下Proxy代码均经过整理)。

首先,我们在CTP和RTM中定义一个相同的复杂类型:Employee。代码如下:
namespace  Jeffz.WebServicesProxyDemo
{
    
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;
        }
    }
}

然后在CTP的页面中添加ScriptManager定义。如下:
< atlas:ScriptManager  runat ="server"  ID ="ScriptManager1" >
    
< Services >
        
< atlas:ServiceReference  InlineProxy ="true"  Path ="EmployeeService.asmx"   />
    
</ Services >
</ atlas:ScriptManager >

InlineProxy设为true,则会把这些Proxy脚本输出在页面中,而不是在页面中通过<script />添加一个脚本引用。而输出的脚本也只有寥寥数行,如下:
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';

可以看出,这里的一个关键,就是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);
    }
}

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 >

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 ;
}

然后就是使用prototype来定义类的方法了。可以看到它定义GetEmployee方法的脚本:
Jeffz.WebServicesProxyDemo.EmployeeService.prototype  =
{
    GetEmployee:
function (name,age,succeededCallback, failedCallback, userContext)
    {
        
// / <summary>Invoke the GetEmployee WebMethod</summary>
         // / <param name="name">WebMethod parameter: name(type: String)</param>
         // / <param name="age">WebMethod parameter: age(type: Int32)</param>
         // / <param name="succeededCallback" type="function" optional="true">Callback on successful completion of request</param>
         // / <param name="failedCallback" type="function" optional="true">Callback on failure of request</param>
         // / <param name="userContext" optional="true">User context data (any JavaScript type)</param>

        
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一些必须的方法的定义了,如下:
29175438_nApI.gif 29175438_4imf.gif proxy必须的方法
Jeffz.WebServicesProxyDemo.EmployeeService.prototype =
{
    ……

    _get_path: 
function()
    {
        
return Jeffz.WebServicesProxyDemo.EmployeeService.get_path();
    },

    set_timeout: 
function(value)
    {
        
/// <summary>Sets the timeout for this service.</summary>
        /// <param name="value" type="Number">The timeout in milliseconds.</param>
        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()
    {
        
/// <summary>Returns the timeout in milliseconds for this service.</summary>
        /// <returns type="Number">The timeout in milliseconds for the service.</returns>
        return this._timeout;
    },

    set_defaultUserContext: 
function(value)
    {
        
/// <summary>Sets the default userContext for this service.</summary>
        /// <param name="value">The default userContext for this service.</param>
        this._userContext = value;
    },

    get_defaultUserContext: 
function()
    {
        
/// <summary>Returns the default userContext for this service.</summary>
        /// <returns>Returns the default userContext for this service.</returns>
        return this._userContext;
    },

    set_defaultSucceededCallback: 
function(value)
    {
        
/// <summary>Sets the default succeededCallback for this service.</summary>
        /// <param name="value" type="Function">The default succeededCallback for this service.</param>
        var e = Function._validateParams(arguments, [{name: 'defaultSucceededCallback', type: Function}]);
        
if (e) throw e;
        
this._succeeded = value;
    },

    get_defaultSucceededCallback: 
function()
    {
        
/// <summary>Returns the default succeededCallback for this service.</summary>
        /// <returns type="Function">Returns the default succeededCallback for this service.</returns>
        return this._succeeded;
    },

    set_defaultFailedCallback: 
function(value)
    {
        
/// <summary>Sets the default FailedCallback for this service.</summary>
        /// <param name="value" type="Function">The default FailedCallback for this service.</param>
        var e = Function._validateParams(arguments, [{name: 'set_defaultFailedCallback', type: Function}]);
        
if (e) throw e;
        
this._failed = value;
    },

    get_defaultFailedCallback: 
function()
    {
        
/// <summary>Returns the default failedCallback for this service.</summary>
        /// <returns type="Function">Returns the default failedCallback for this service.</returns>
        return this._failed;
    }
}

然后建立一个Singleton对象,所有的工作将由该对象完成:
Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance  = new  Jeffz.WebServicesProxyDemo.EmployeeService();

接着定义Jeffz.WebServicesProxyDemo上的静态方法,并将实现委托给Singleton对象的相应方法。如下:
29175438_nApI.gif 29175438_4imf.gif static方法
Jeffz.WebServicesProxyDemo.EmployeeService.set_path = function(value)

    
/// <summary>Sets the service url.</summary>
    /// <param name="path" type="String">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()
{
    
/// <summary>Returns the service url.</summary>
    /// <returns type="String">The service url.</returns>
    return Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._path;
}

Jeffz.WebServicesProxyDemo.EmployeeService.set_timeout 
= function(value)

    
/// <summary>Sets the service timeout.</summary>
    /// <param name="value" type="Number">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()

    
/// <summary>Returns the service timeout.</summary>
    /// <returns type="Number">The service timeout.</returns>
    return Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._timeout;
}

Jeffz.WebServicesProxyDemo.EmployeeService.set_defaultUserContext 
= function(value)

    
/// <summary>Sets the service default userContext.</summary>
    /// <param name="value">The service default user context.
    Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._userContext = value;
}

Jeffz.WebServicesProxyDemo.EmployeeService.get_defaultUserContext 
= function()

    
/// <summary>Returns the service default user context.</summary>
    /// <returns>The service default user context.</returns>
    return Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._userContext; 
}

Jeffz.WebServicesProxyDemo.EmployeeService.set_defaultSucceededCallback 
= function(value)

    
/// <summary>Sets the service default succeeded callback.</summary>
    /// <param name="value" type="Function">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()

    
/// <summary>Returns the service default succeeded callback.</summary>
    /// <returns type="Function">The service default succeeded callback.</returns>
    return Jeffz.WebServicesProxyDemo.EmployeeService._staticInstance._succeeded;
}

Jeffz.WebServicesProxyDemo.EmployeeService.set_defaultFailedCallback 
= function(value)

    
/// <summary>Sets the service default failed callback function.</summary>
    /// <param name="value" type="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()

    
/// <summary>Returns the service default failed callback function.</summary>
    /// <returns type="Function">The service default failed callback function.</returns>
    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)
{
    
// / <summary>Invoke the GetEmployee WebMethod</summary>
     // / <param name="name">WebMethod parameter: name(type: String)</param>
     // / <param name="age">WebMethod parameter: age(type: Int32)</param>
     // / <param name="succeededCallback" type="function" optional="true">Callback on successful completion of request</param>
     // / <param name="failedCallback" type="function" optional="true">Callback on failure of request</param>
     // / <param name="userContext" optional="true">User context data (any JavaScript type)</param>
    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几乎一模一样,而且总共只有寥寥数行,在这里就不多加说明了。

转载于:https://my.oschina.net/abcijkxyz/blog/721829

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值