深入Atlas系列:Web Sevices Access in Atlas示例(6) - 在客户端隐藏服务器端类型信息

  如果要在客户端指定服务器端Web Service方法所接收的参数类型,就必须在客户端通过“__type”来指定,但是这就暴露了服务器端的具体类型了,这可不太好。现在我们就来看一下应该如何解决这个问题。在这里,我会使用以前的文章《深入Atlas系列:Web Sevices Access in Atlas示例(3) - 在Web Services方法中使用多态》里用过的例子,不过它的内容是使用CTP版本的Atlas,已经过期,因此还是需要一些改变。这个示例会分成好几步进行,我们一点点地来看它的实现:


1、定义需要的类型

首先,我们定义一下所需的类型。我们的目标是计算某种类型员工的工资,于是,我们先定义一个员工的抽象类:
namespace  Jeffz.HiddenTypes
{
    
public   abstract   class  Employee
    {
        
private   int  _Years;

        
public   int  Years
        {
            
get
            {
                
return   this ._Years;
            }
            
set
            {
                
this ._Years  =  value;
            }
        }

        
public   string  RealStatus
        {
            
get
            {
                
return   this .GetType().Name;
            }
        }

        
public   abstract   int  CalculateSalary();
    }
}

然后定义一下可怜的实习生,不管干多少年,永远只有2000元工资:
namespace  Jeffz.HiddenTypes
{
    
public   class  Intern : Employee
    {
        
public   override   int  CalculateSalary()
        {
            
return   2000 ;
        }
    }
}

然后是签第三方公司的合同工,底薪5000,每年增加1000:
namespace  Jeffz.HiddenTypes
{
    
public   class  Vendor : Employee
    {
        
public   override   int  CalculateSalary()
        {
            
return   5000   +   1000   *  (Years  -   1 );
        }
    }
}

最后是正式员工(全职工),底薪12000,每年增加2000:
namespace  Jeffz.HiddenTypes
{
    
public   class  FulltimeEmployee : Employee
    {
        
public   override   int  CalculateSalary()
        {
            
return   12000   +   2000   *  (Years  -   1 );
        }
    }
}


2、制作一个不隐藏服务器端类型的应用

首先,自然是定义一个Web Service,我们将其命名为ExposedRealTypesService.asmx
[WebService(Namespace  =   " http://tempuri.org/ " )]
[WebServiceBinding(ConformsTo 
=  WsiProfiles.BasicProfile1_1)]
[ScriptService]
public   class  ExposedRealTypesService : System.Web.Services.WebService
{
    [GenerateScriptType(
typeof (Intern))]
    [GenerateScriptType(
typeof (Vendor))]
    [GenerateScriptType(
typeof (FulltimeEmployee))]
    [WebMethod]
    
public   string  CalculateSalary(Employee employee)
    {
        
return   " I'm  "   +  employee.RealStatus  +   " , my salary is  "   +  employee.CalculateSalary()  +   " . " ;
    }
    
}

在这里我们使用了GenerateScriptTypeAttribute来告诉这个Web Service:“我们可能会使用这些类来作为参数传递给你,请注意JSON字符串里的__type标志”,于是我们就能使用了。我们来看一下我们需要的HTML:
< asp:ScriptManager  ID ="ScriptManager"  runat ="server" >
    
< Services >
        
< asp:ServiceReference  Path ="ExposedRealTypesService.asmx"  InlineScript ="false"   />
    
Services >
asp:ScriptManager >
    
< div > Years: < input  type ="text"  id ="txtYears"   /> div >
< div >
    Status:
    
< select  id ="comboStatus"  style ="width:150px;" >
        
< option  value ="Jeffz.HiddenTypes.Intern" > Intern option >
        
< option  value ="Jeffz.HiddenTypes.Vendor" > Vendor option >
        
< option  value ="Jeffz.HiddenTypes.FulltimeEmployee" > FTE option >
    
select >
div >
< input  type ="button"  onclick ="calculateSalary()"  value ="Calculate!"   />
< h1 > Result: h1 >
< div  id ="result" > div >

所需的JavaScript代码如下:
function  calculateSalary()
{
    
var  emp  =  eval( " new  "   +  $get( " comboStatus " ).value  +   " () " );
    emp.Years 
=  parseInt($get( " txtYears " ).value,  10 );
    
    ExposedRealTypesService.CalculateSalary(emp, onComplete);
}
        
function  onComplete(result)
{
    $get(
" result " ).innerHTML  =  result;
}

由于comboStatue的value就是客户端的类名,因此我使用了拼接字符串并且eval的方法生成客户端的类,并作为参数传递过去。其余的代码应该非常简单,我们就来看一下使用效果吧:

打开页面,先选择Intern,输入工龄为2,点击“Calculate!”按钮:
深入Atlas系列:Web Sevices Access in Atlas示例(6) - 在客户端隐藏服务器端类型信息_第1张图片

再选择FTE,输入工龄为5,点击“Calculate!”按钮:
深入Atlas系列:Web Sevices Access in Atlas示例(6) - 在客户端隐藏服务器端类型信息_第2张图片

嘿,这说明我们客户端传了不同的服务器对象给Web Service方法了。



3、隐藏客户端的__type信息

我们打开Fiddler看看请求的内容吧:
深入Atlas系列:Web Sevices Access in Atlas示例(6) - 在客户端隐藏服务器端类型信息_第3张图片

嗯?看到了JSON字符串里的信息了不?明明白白地写着“Jeffz.HiddenTypes.Intern”!哎,怎么能把我们服务器端的类型信息给暴露出去呢?这样是不是太危险了一点?不过没有关系,我们可以这么做。首先,修改一下Web Service方法,我们这里就另存为HiddenRealTypesService.asmx吧:
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class HiddenRealTypesService : System.Web.Services.WebService
{
    [GenerateScriptType(typeof(Intern),  ScriptTypeId="Intern")]
    [GenerateScriptType(typeof(Vendor),  ScriptTypeId = "Vendor")]
    [GenerateScriptType(typeof(FulltimeEmployee),  ScriptTypeId = "FTE")]
    [WebMethod]
    public string CalculateSalary(Employee employee)
    {
        return "I'm " + employee.RealStatus + ", my salary is " + employee.CalculateSalary() + ".";
    }    
}

注意到了在使用GenerateScriptTypeAttribute时我们改变了什么吗?对了,就是我们设置了“ScriptTypeId”的值。这是什么?我们来看一下GenerateScriptTypeAttribute的使用方式吧。如下:
internal   class  WebServiceData : JavaScriptTypeResolver
{
    ……

    
private   void  ProcessIncludeAttributes(GenerateScriptTypeAttribute[] attributes)
    {
          
foreach  (GenerateScriptTypeAttribute attribute1  in  attributes)
          {
                
if  ( ! string .IsNullOrEmpty(attribute1.ScriptTypeId))
                {
                      
this ._typeResolverSpecials[attribute1.Type.FullName]  =  attribute1.ScriptTypeId;
                }

                ……

               
this .ProcessClientType(type1);
          }
    }

    ……
}

这段代码就是ASP.NET AJAX用于处理Web Service的WebServiceData类,当然它也提供了许多功能。在ProcessIncludeAttributes方法中会处理所有的GenrateScriptTypeAttribute,可以看到在这里为每个Attribute的ScriptTypeId与Type的FullName进行了映射。这种映射是不是让你想到了JavaScriptTypeResovler?没错,WebServiceData类就是继承了JavaScriptTypeResolver,它辅助了ASP.NET AJAX客户端访问Web Service方法时的序列化与反序列化工作。关于这一点,在我之前的文章《深入Atlas系列:探究序列化与反序列化能力(上) - 客户端支持,JavaScriptTypeResolver与JavaScriptConverter》里有比较详细的描述。

我们来看一下效果,当然在这之前还需要修改一下页面中ScriptManager的Service引用,如下:
< asp:ScriptManager  ID ="ScriptManager"  runat ="server" >
    
< Services >
        
< asp:ServiceReference  Path ="HiddenRealTypesService.asmx"   />
    
Services >
asp:ScriptManager >

再用Fiddler看一下传输内容吧,如下:
深入Atlas系列:Web Sevices Access in Atlas示例(6) - 在客户端隐藏服务器端类型信息_第4张图片

嘿,这样就不会把服务器端的具体类型暴露给别人了,不是吗?嗯,我们再多想想……:)



4、隐藏客户端代码调用时使用的具体信息

我们还有相当的路要走。有没有发现,我们在调用中带有了非常“明显”的类型信息。我们的方式其实和下面的差不多:
var  emp  =   new  Jeffz.HiddenTypes.Intern();
……
HiddenRealTypesService.CalculateSalary(emp, onComplete);

“Jeffz.HiddenTypes.Intern”?这不还是服务器端的具体类型吗?那么该怎么办呢?其实“new Jeffz.HiddenTypes.Intern()”操作也只是返回了一个普通的Object对象,不过它带有“__type”这个信息,其值为服务器端具体类型的ID。因此我们其实只要像下面这么做,也能得到同样的效果了。

首先,将