深入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  =   " [url]http://localhost:2481/Atlas-CTP/[/url] " ;
    
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一些必须的方法的定义了,如下:
 proxy必须的方法

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

  接着定义Jeffz.WebServicesProxyDemo上的静态方法,并将实现委托给Singleton对象的相应方法。如下:
 static方法

  最后,设置一下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几乎一模一样,而且总共只有寥寥数行,在这里就不多加说明了。



本文转自 jeffz 51CTO博客,原文链接:http://blog.51cto.com/jeffz/60669,如需转载请自行联系原作者

相关文章
|
3月前
|
安全 应用服务中间件 数据安全/隐私保护
Web安全-WebService远程部署
Web安全-WebService远程部署
33 4
|
4月前
【Azure 应用服务】Web App Service 中的 应用程序配置(Application Setting) 怎么获取key vault中的值
【Azure 应用服务】Web App Service 中的 应用程序配置(Application Setting) 怎么获取key vault中的值
|
1月前
【Azure App Service】PowerShell脚本批量添加IP地址到Web App允许访问IP列表中
Web App取消公网访问后,只允许特定IP能访问Web App。需要写一下段PowerShell脚本,批量添加IP到Web App的允许访问IP列表里!
|
4月前
|
关系型数据库 MySQL Linux
【Azure 应用服务】在创建Web App Service的时候,选Linux系统后无法使用Mysql in App
【Azure 应用服务】在创建Web App Service的时候,选Linux系统后无法使用Mysql in App
【Azure 应用服务】在创建Web App Service的时候,选Linux系统后无法使用Mysql in App
|
4月前
|
Shell PHP Windows
【Azure App Service】Web Job 报错 UNC paths are not supported. Defaulting to Windows directory.
【Azure App Service】Web Job 报错 UNC paths are not supported. Defaulting to Windows directory.
|
4月前
|
Linux 应用服务中间件 网络安全
【Azure 应用服务】查看App Service for Linux上部署PHP 7.4 和 8.0时,所使用的WEB服务器是什么?
【Azure 应用服务】查看App Service for Linux上部署PHP 7.4 和 8.0时,所使用的WEB服务器是什么?
|
4月前
【Azure 应用服务】通过 Web.config 开启 dotnet 应用的 stdoutLog 日志,查看App Service 产生500错误的原因
【Azure 应用服务】通过 Web.config 开启 dotnet 应用的 stdoutLog 日志,查看App Service 产生500错误的原因
|
4月前
|
Linux Python
【Azure 应用服务】Azure App Service For Linux 上实现 Python Flask Web Socket 项目 Http/Https
【Azure 应用服务】Azure App Service For Linux 上实现 Python Flask Web Socket 项目 Http/Https
|
4月前
|
存储 安全 网络安全
【Azure 环境】使用Azure中的App Service部署Web应用,以Windows为主机系统是否可以启动防病毒,防恶意软件服务呢(Microsoft Antimalware)?
【Azure 环境】使用Azure中的App Service部署Web应用,以Windows为主机系统是否可以启动防病毒,防恶意软件服务呢(Microsoft Antimalware)?
|
4月前
|
存储 Linux 网络安全
【Azure 应用服务】App Service For Linux 如何在 Web 应用实例上住抓取网络日志
【Azure 应用服务】App Service For Linux 如何在 Web 应用实例上住抓取网络日志
下一篇
DataWorks