深入Atlas系列:Web Sevices Access in Atlas(5) - 对于复杂数据类型的支持(中)

简介:
在前一片文章里,我们已经得到了从客户 端获得的字符串反序列化以后的Dictionary,我们知道它的形式和JSON非常的相像,只是存放了一些基本类型的数据还有IDictionary已 经IList对象。它们在被应用到真正用于调用Web Services方法之前,还需要将这个Dictionary里的数转化为强类型的参数对象。在Atlas中,是使用了 WebServiceMethodData.StrongTypeParameters方法来获得一个包含强类型参数对象的字典的。因此我们直接开始分析 这个方法吧。

  一个包含着JSON信息的Dictionary已经作为rawParams参数被传入该方法,代码如下:
ContractedBlock.gif  StrongTypeParameters方法

  我们可以看到,真正起作用的方法是ObjectConverter.ConvertToType静态方法,它通过传入的对象信息,目标类型来 获得最终我们需要的参数对象。那么this.Owner,这个WebServiceData对象的作用是什么呢?我们先来简单看一下 ObjectConverter.ConvertToType静态方法的代码:
ContractedBlock.gif  ConvertToType静态方法

  可以发现,WebServiceData对象被作为了IJavaScriptSerializationContext类型的对象来使用。 Microsoft.Web.Script.Serialization.IJavaScriptSerializationContext接口中最重要 的方法就是GetType(string),它通过给定一个字符串形式的类型名,来获得这个类型对象Type。很自然,这个类型首先需要被 IJavaScriptSerializationContext所识别。还记得在我的前一篇文章《 深入Atlas系列:Web Sevices Access in Atlas(4) - 对于复杂数据类型的支持(上) 》 中“问题4”的错误吗?它的产生原因就是因为那个WebServiceData对象无法识别字符串 “Jeffz.ComplexType.Employee”所表示的类型。既然无法识别,那么即使使用了“__serverType”来表示一个客户端对 象的服务器端类型,也是无法转化的。那么为什么会无法识别这个对象呢?我们继续追踪,分析ConvertObjectToType方法的实现,代码如下:
ContractedBlock.gif  ConvertObjectToType方法

  从这里开始,Atlas对于支持复杂类型的扩展性一点点地被揭开了。在进行转换时,首先会调用 JavaScriptConverter.ConverterExistsForType来检验是否有支持该Type的 JavaScriptConverter。这里的JavaScriptConverter是指一个继承了 Microsoft.Web.Script.Serialization.JavaScriptConverter抽象类的对象。在Atlas中可以可以 根据配置文件来加载需要的JavaScriptConverter。事实上,在默认的Web.config文件中,已经为DataSet、 DataTable和DataRow三个类写好了JavaScriptConverter。我们在Web.config类中可以发现这样的配置元素:
< microsoft .web >
    
< converters >
        
< add  type ="Microsoft.Web.Script.Serialization.Converters.DataSetConverter" />
        
< add  type ="Microsoft.Web.Script.Serialization.Converters.DataRowConverter" />
        
< add  type ="Microsoft.Web.Script.Serialization.Converters.DataTableConverter" />
    
</ converters >
    ……
</ microsoft.web >

  虽然没有在配置文件内将对象类型与JavaScriptConverter对应起来,但是在 Microsoft.Web.Script.Serialization.JavaScriptConverter抽象类中有个virtual属性 SupportedTypes,它返回了这个JavaScriptConverter能够支持的Type数组,默认为null。 JavaScriptConverter就是依靠这个把具体的JavaScriptConverter子类与某个特定的类型关联起来。

   在调用ObjectConverter.ConvertObjectToType方法时,首先会查找是不是已经有了目标type所对应的 JavaScriptConverter(第11行),如果有的话,则会通过JavaScriptObjectSerializer.Serialize 静态方法将需要转换的对象o序列化成字符串,然后再调用JavaScriptConverter.DeserializeString静态方法,将该字符 串反序列化成Web Services方法所需要的参数对象。值得注意的是,在这两个静态方法中,都会再次查看是否有对应的JavaScriptConverter与所操作的 对象相对应,如果有,则使用JavaScriptConverter进行操作,反之则使用Atlas内定的操作方法。

  可是,是不是有 朋友对于ObjectConverter.ConvertObjectToType方法中第13-16行的逻辑表示不解呢?为什么需要将传入的参数o通过 它对应的JavaScriptConverter重新序列化成字符串,再交给目标类型的JavaScriptConverter再反序列化一次呢?其实这 个思路似乎有点被JavaScriptObjectSerializer.Serialize静态方法的实现所误导了(确切的说是被在这里对于该静态方法 的使用所误导)。这个静态方法的确会检查是否有与o的类型对应的JavaScriptConverter存在,但是我们仔细思考一下,这个o的类型会是什 么呢?

  这里的参数o是刚从JSON字符串转变过来的表示参数的对象之一,它的作用是“表现参数”。它的类型其实只是Int32, Double,String,DateTime等基本数据类型或者IDictionary以及IList对象中的一种。我想,开发人员应该不太会为这写类 型写JavaScriptConverter吧?对于这两种对象的转换,Atlas已经支持地相当出色了。事实上,在Atlas中的确也没有为基础类型或 者IDictionary以及IList这写类型准备JavaScriptConverter。在 ObjectConverter.ConvertObjectToType方法中第14行,使用 JavaScriptObjectSerializer.Serialize静态方法序列化o对象,其实只是将这个对象再转变回字符串,转变为客户端通过 Request Body发送过来的原始字符串。

  这样的话,逻辑就顺利了。将一个类型与JavaScriptConverter所绑 定的目的,是为了能够自定义关于这个对象的序列化已经反序列化的操作。这两个操作应该是一体的。这个类需要负责将某个类型的对象输出成特别的JSON代 码,也需要输出一段Javascript来告知客户端该如何序列化一个客户端对象(通过提供对象一个serialize函数,这一点在上一篇文章中提到 过),在以后分析Atlas为某个Web Services生成客户端代理的原理时会详细说明这一点。可以想象,如果(仅仅是如果)在这之间,对象o找到了对应的 JavaScriptConverter,得到的序列化字符串并不是客户端的原始输入,那么为目标Type设计的JavaScriptConverter 在反序列化一个字符串时就会失败,或者导致不可预料的结果。

  因此,其实 JavaScriptConverter.DeserializeString方法接收到的字符串,应该(或者说在正常的情况下)就是客户端表示该对象的 原始输入字符串。这样,我们就能做到对一个类型自定义一组扩展。这个方法常常被用于序列化于反序列化一组相互引用的对象,在下一片文章中,我将以范例来具 体说明这一点。事实上,Atlas里已有的三个JavaScriptConverter,就是为了处理三个复杂类型DataSet,DataTable以 及DataRow的序列化与反序列化操作,它们就是存在着相互引用的对象,Atlas也通过了添加JavaScriptConverter来处理这个问 题。

  如果没有找到目标type所对应的JavaScriptConverter,那么 ObjectConverter.ConvertObjectToType方法就会使用Atlas的默认操作——或者说内部操作: ObjectConverter.ConvertObjectToTypeInternal方法来将o转换为目标type的对象,以便Web Services方法使用。接下来,我们来看一下它的实现:
ContractedBlock.gif  ConvertObjectToTypeInternal方法

  如果当前需要转换的对象为IDictionary类型或者IList类型,那么则调用相应的方法进行转换,这点稍后再作分析。如果这些o不是 这两种类型,则说明它们是些基本类型了,例如Int32,Double,DateTime,Boolean等。在这里我们看到Atlas允许我们进行另外 一种可以使用的扩展方式:使用自定义的System.ComponentModel.TypeConverter子类并使用 System.ComponentModel.TypeConverterAttribute为类型的转换进行标注。熟悉ASP.NET控件开发的朋友可 能会比较熟悉这个做法,为控件属性提供设计期支持时经常会使用这种做法。在Atlas中,如果我们的类型会从那些基本类型转换过来,那么我们就可以提供自 定义TypeConverter的方式进行扩展。在今后的文章中,我将提供示例供大家参考。

  在通过 TypeDescriptor.GetConverter方法获得目标类型的TypeConverter后,会查看该TypeConverter是否能够 从目前o对象的类型进行转换。如果可以,则返回转换后的结果。否则的话,则设法先将o对象转化为字符串,再使用TypeConverter的 ConvertFromInvariantString方法进行转换。请注意,这里使用的方法都是XXXInvariantString,也就是说,它们 是与系统的Culture相关的。举个例子,如果我们需要自定一个DateTime到一个特定类型的转换,传入TypeConverter中 ConvertFromInvariantString方法的字符串参数,它的值会根据系统当前Culture环境的不同而产生变化。

   事实上,对于目标类型本身就是参数类型(例如Int32)的情况,这段代码一般就会执行第36-39行的逻辑。例如对象o的类型是long,而目标类型 为int,则这段代码就会先使用Int64Converter将o转变为字符串,再通过Int32Converter将这个字符串转化为我们需要的参数类 型:Int32。

  另外,通过理解这个逻辑,我们也能够解释下面的行为的原因:

  如果参数为整型,那么Atlas会自动将客户端传入的字符串参数转化为整型以保证Web Services方法调用成功。例如,在我之前的文章《 深入Atlas系列:Web Sevices Access in Atlas - 示例一 - 特别的访问方式 》 的范例中,我将txtAge的text属性绑定到serviceMethod的参数age上。这就相当于将一个字符串的值赋于Web Services方法的整型参数,通过使用Fiddler查看HTTP传输的数据也能发现,在Request Body中传递的数据也类似与“{ age : "20", ... }”。显然,在服务器端得到的age参数输入,其类型是一个字符串。但是,就是因为之前描述的逻辑,在真正使用这个字符串的值去进行Web Services方法调用之前,会被Int32Converter转换成为整型。这样,方法调用就成功了。

  如果无法使用 TypeConverter将对象o转换为目标类型的话,就尝试着直接将o使用在方法调用上了。在上面的代码中可以发现,Atlas使用了目标type的 IsAssignableFrom方法来检测“直接赋值”的合法性。最后,如果发现对象o,不管使用什么方式都无法将其转换为目标type的话,则抛出了 InvalidOperationException,表示类型转换的失败。

  刚才详细讨论的逻辑是在最后对于一个“值”的转换。但是 一般来说,在Atlas转换数据类型时难免会遇到IDictionary和IList对象的嵌套。在这时候,就要使用代码所看到的 ConvertDictionaryToObject方法和ConvertListToObject方法了。首先我们来分析一下 ConvertListToObject方法代码:
ContractedBlock.gif  ConvertListToObject方法

  这个方法的作用是将IList对象list转换成type类型的对象。自然,它在实际使用中也是一个列表或者数组。在这个方法中,根据 Type格式的不同,转换后的数据类型可能是ArrayList,Array或者Generic类型List<T>。可以简单地认为,在 Atlas中所支持的“列表”类型或者“数组”类型就只有这些。因此,我们在Web Services方法中应该使用这些以及它们的兼容类型,例如IList<T>,ICollection<T>或者IList 等。对于无法转换的数据类型,只能不得已地抛出InvalidOperationException了。我们在使用Atlas访问Web Services方法时应该完全避免使用这样的数据类型。当然,我们能够添加自己的扩展,以支持我们需要的数据类型。在下一片文章中我将给出相应的范例。

   在上面的方法中,我们可以看到,它总是调用了AddItemToList方法来填充一个“列表”对象。其实我们也能够想象得到它的实现方式:它枚举了源 列表的每个元素,调用ConvertObjectToType方法将其转换成合适的对象,然后将其填充至目标列表中。对象的转换总是在大量的间接递归中实 现的。

  与上述的ConvertListToObject方法相对应是ConvertDictionaryToObject方法。相比于前者返回的是一个“列表”,而后者则返回的则好比是一个“元素”的对象。代码如下:
ContractedBlock.gif  ConvertDictionaryToObject方法

  在这里可以发现,如果客户端传来的对象指定了__serverType,那么将使用__serverType所表示的值从 IJavaScriptSerializationContext对象中获得真正使用的类。请注意,它和目标type所表示的类,完全可以不是同一个!产 生的对象是目标type的接口实现亦可,产生的对象是目标type的子类亦可,只要能够正确地调用Web Services方法的类型都可以。这就给了我们对于使用Atlas调用Web Services方法有了更高的灵活性,其能力甚至已经凌驾于Web Services本身!这就是Atlas的强大之处。以后将会出现与此相关的示例,敬请留意。:)

  等拿到了 真正 的 目标对象类型后,就使用Activator.CreateInstance方法进行对象构造。最后枚举Dictionary中的每一个key,并将以之对 应的value和对象传入AssignToPropertyOrField方法进行public属性或者成员变量的赋值。该方法首先再次使用 ConvertObjectToType方法递归地将value转换为合适的对象,然后尝试寻找与key对应的属性的set方法,如果没有的话,则再寻找 与key对应的Public Field,最后设值。

  就这样,一个对象就成功地被构造出来了。



  通过分析这篇文章的代码,我们至少可以发现一下几点:
  1. 在Atlas中,通过实现JavaScriptConverter的子类可以自定一个特定类型的序列化和反序列化能力。
  2. 在Atlas中,通过实现TypeConverter并使用TypeConverterAttribute标记可以为一个特定类型,自定义从基本类型(例如Int32,Double,DateTime,Boolean等)的反序列化的能力。
  3. Atlas访问Web Services方法对于范型类型(甚至对于普通数据类型)的支持,是不完整的,不过已经足够大多数应用的需求。而且对于不支持的数据类型,我们可以通过自定义的方式提供对它们的支持。
  4. Atlas访问Web Services方法有非常灵活的可控性,它的功能甚至超过普通的Web Services访问,能够做到普通Web Services无法做到的有用功能。
  在今后的文章中,我会一一提供对于这些功能的示例。



  到现在为止,我们对于服务器端反序列化客户端 数据的方式已经分析地差不多了,不过还缺少对于那个IJavaScriptSerializationContext,也就是那个 WebServiceData对象中GetType方法的分析与理解。那么这个对象这个方法到底是如何工作的呢?另外,得到了调用Web Services方法的结果以后,它又是怎么被序列化并输出是客户端的呢?



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

相关文章
|
2月前
|
JSON JavaScript Go
Go 语言学习指南:变量、循环、函数、数据类型、Web 框架等全面解析
掌握 Go 语言的常见概念,如变量、循环、条件语句、函数、数据类型等等。深入了解 Go 基础知识的好起点是查阅 Go 官方文档
725 2
|
2月前
|
JavaScript 前端开发 开发者
【Web 前端】TypeScript 的内置数据类型有哪些?
【5月更文挑战第1天】【Web 前端】TypeScript 的内置数据类型有哪些?
|
2月前
|
存储 前端开发 JavaScript
【Web 前端】JS数据类型有哪些?区别?
【4月更文挑战第22天】【Web 前端】JS数据类型有哪些?区别?
|
2月前
|
JavaScript 前端开发
【Web 前端】JS中检测数据类型的有哪些?
【4月更文挑战第22天】【Web 前端】JS中检测数据类型的有哪些?
|
8月前
|
应用服务中间件
idea 调试报错 Illegal access: this web application instance has been stopped already.
idea 调试报错 Illegal access: this web application instance has been stopped already.
101 0
|
2月前
|
应用服务中间件 数据安全/隐私保护
Tomcat【部署 02】Web端403 Access Denied You are not authorized to view this page解决方法(Tomcat 10.2.12 版本)
Tomcat【部署 02】Web端403 Access Denied You are not authorized to view this page解决方法(Tomcat 10.2.12 版本)
312 0
|
2月前
|
XML JSON 前端开发
ajax中get和post的区别,datatype返回的数据类型有哪些?web开发中数据提交的几种方式,有什么区别。百度使用哪种方式?
ajax中get和post的区别,datatype返回的数据类型有哪些?web开发中数据提交的几种方式,有什么区别。百度使用哪种方式?
32 0
|
JavaScript 前端开发
web前端-JavaScript标识符和数据类型/强制类型转换
web前端-JavaScript标识符和数据类型/强制类型转换
112 0
|
安全 Go C语言
Go Web编程实战(3)----数据类型(二)
Go Web编程实战(3)----数据类型(二)
101 0
Go Web编程实战(3)----数据类型(二)
|
存储 Java Go
Go Web编程实战(3)----数据类型(一)
Go Web编程实战(3)----数据类型(一)
112 0
Go Web编程实战(3)----数据类型(一)