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

  在上一篇文章中,我们提到了 Atlas在将一个Dictionary转换为一个对象时,会调用对应的IJavaScriptSerializationContext对象的 GetType(string)方法,以获得“真正”的目标对象类型。在Atlas对于Web Services方法的Request所引起的转换过程中,这个IJavaScriptSerializationContext对象是一个 WebServiceData类的实例,它的GetType方法会在对应的那个Web Services类中寻找相关的数据类型。那么Atlas是如何寻找这些类型的呢?什么样的类型会被查找到呢?

  通过查看代码,我们可 以发现,WebServiceData.GetType(string)方法其实是返回它的一个叫做_clientTypesDictionary的实例 变量中保存的Type。WebServiceData._clientTypesDictionary在第一被使用之前会被初始化。我们在这里略过了各个 方法之间的调用,直接查看真正初始化该变量的方法:WebServiceData.ProcessClientTypes方法。代码如下:
ProcessClientTypes()方法分析
 1 private void ProcessClientTypes()
 2 {
 3     // 初始化用于保存类名与类型对象映射的字典
 4     this._clientTypesDictionary = new Dictionary<string, Type>();
 5     // 初始化用于保存枚举类型的字典,只作为标记使用
 6     this._enumTypesDictionary = new Dictionary<Type, object>();
 7     // 初始化用于标记以处理过的类型的哈希表
 8     this._processedTypes = new Hashtable();
 9 
10     // 枚举Web Services类中的每一个方法
11     foreach (WebServiceMethodData data in this.MethodDatas)
12     {
13         // 枚举当前Web Service方法中的每一个参数
14         using (IEnumerator<WebServiceParameterData> enumerator = data.ParameterDatas.GetEnumerator())
15         {
16             while (enumerator.MoveNext())
17             {
18                 Type type = enumerator.Current.ParameterInfo.ParameterType;
19                 // 调用ProcessClientType加载该参数类型
20                 this.ProcessClientType(type, false);
21             }
22         }
23 
24         // 调用ProcessXmlIncludeAttributes方法加载
25         // 附加在Web Services方法上的类型。
26         this.ProcessXmlIncludeAttributes(data.MethodInfo);
27     }
28 
29     // 枚举Web Services类中的每一个方法
30     foreach (WebServiceMethodData data3 in this.MethodDatas)
31     {
32         // 对于不以Xml形式返回的Web Services方法
33         if (!data3.UseXmlResponse)
34         {
35             // 调用ProcessClientType加载返回对象类型,
36             // 注意第二个参数true表示只处理枚举类型。
37             this.ProcessClientType(data3.MethodInfo.ReturnType, true);
38         }
39     }
40 
41     // 释放该对象,这个只是上述初始化操作的辅助变量。
42     this._processedTypes = null;
43 }

  代码很容易理解,通过枚举每一个方法的每一个参数加以处理,最后还要处理返回值类型。在这里有一个相当重要的方法,就是通过ProcessClientType(Type, bool)加载可识别的参数类型,我们来看一下它的代码:
ProcessClientType(Type, bool)方法分析
 1 private void ProcessClientType(Type t, bool enumOnly)
 2 {
 3     // 只有是没有处理过的类型,才会进入if体内进行处理
 4     if (!this._processedTypes.Contains(t))
 5     {
 6         // 表示该类型已经被处理过了
 7         this._processedTypes[t] = null;
 8         // 如果该类型是枚举类型
 9         if (t.IsEnum)
10         {
11             // 那么把它加到枚举类型里
12             this._enumTypesDictionary[t] = null;
13         }
14         // 否则只有当该类型不是基本类型(如int)、Object、
15         // String,DataTime中的任何一个,才加以处理。
16         else if (((!t.IsPrimitive && (t != typeof(object))) && (t != typeof(string))) && (t != typeof(DateTime)))
17         {
18             // 如果不光处理枚举类型,并且是客户端可以实例话的
19             // 类型(非抽象,非接口,非数组,没有JavaScriptConverter与
20             // 之对应,并且存在无参数的实例构造函数),才处理这样
21             // 的类型,因为只有这样的类型在反序列化途中被构造出来。
22             if (!enumOnly && ObjectConverter.IsClientInstantiatableType(t))
23             {
24                 // 做一个类型名与类型对象的映射,下面
25                 // 使用的静态方法其实质是返回t.FullName。
26                 this._clientTypesDictionary[WebServiceData.GetTypeStringRepresentation(t)] = t;
27             }
28             
29             // 如果t是数组的话
30             if (t.IsArray)
31             {
32                 // 那么递归调用ProcessClientType方法处理它的元素类型
33                 this.ProcessClientType(t.GetElementType(), false);
34             }
35             else
36             {
37                 // 否则就处理寻找带有set方法的公有实例属性,
38                 // 注意这里不会寻找成员变量。
39                 PropertyInfo[] infoArray = t.GetProperties(BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance);
40                 // 枚举每一个属性
41                 foreach (PropertyInfo info in infoArray)
42                 {
43                     // 递归调用ProcessClientType方法处理该属性的类型
44                     this.ProcessClientType(info.PropertyType, false);
45                 }
46             }
47         }
48     }
49 }

  标记完可以处理的类型后,还会递归的将该类型相关的所有类型做处理。例如数组的元素和属性的类型。值得注意的是,在递归处理该类型相关的类型时,只会处理具有set方法的属性,而不会处理公有实例成员变量的类型,这也就解释了在之前的文章《深入Atlas系列:Web Sevices Access in Atlas(4) - 对于复杂数据类型的支持(上)》 中问题4所遇到的类型无法找到的错误。奇怪的是,在反序列化的对象之后恢复对象信息时也为实例的成员变量设值,因此个人认为,这是Atlas的一个“失 误”。因此对于作为实例成员变量的类型,除非它也直接作为Web Services方法参数出现过,否则就无法被找到了。但是个人建议,在为Atlas创建Web Services方法的参数类型时,尽量完全使用属性,而不是成员变量。

  不过这样的话,仅仅只有Web Services方法参数,以及相关的类型才能在反序列化的时候被使用到,这样似乎也够用了。不过Atlas的能力还远不止这些。Atlas能够将任意的 类型附加在Web Services类中,使该类型能够在反序列化过程中被找到。这就是ProcessXmlIncludeAttributes方法的功能,可以看到,它在 ProcessClientType()方法中被调用了。我们来看一下它的代码:
ProcessXmlIncludeAttributes方法分析
 1 private void ProcessXmlIncludeAttributes(MemberInfo info)
 2 {
 3     // 获得该方法上的所有XmlIncludeAttribute标记
 4     XmlIncludeAttribute[] attArray = (XmlIncludeAttribute[]) info.GetCustomAttributes(typeof(XmlIncludeAttribute), true);
 5     // 枚举每一个标记
 6     foreach (XmlIncludeAttribute att in attArray)
 7     {
 8         // 处理标记里保留的类型
 9         this.ProcessClientType(att.Type, false);
10     }
11 }

  代码非常简单,就不多加解释了。可以看出,原本用来辅助XmlSerialiaer的XmlIncludeAttribute被用在这里了! 这的确是一个好消息,于是我们就能通过为一个方法标记XmlIncludeAttribute来为一个Web Services添加额外支持的数据类型,例如:
[XmlInclude( typeof (AdditionalType))]

  有了这个功能,就为Atlas访问Web Services方法的功能增色了不少。在以后将会有相关示例来演示XmlIncludeAttribute的有用之处。



   到上面为止,从客户端序列化一个对象,到服务器端将Web Services方法的参数生成的整个过程已经讨论完了,也就是说Atlas访问Web Services方法的大部分的实现方式已经分析结束了。下面的做法自然是使用反射机制调用Web Services方法,然后再将所得的结果对象进行JSON序列化输出(Xml序列化输出非常常见,因此在这里不作讨论)。将结果序列化的功能是调用 Microsoft.Web.Script.Serialization.JavaScriptObjectSerializer的静态方法 Serialize(object,IJavaScriptSerializationContext)工作的,不过真正进行序列化工作的是 Microsoft.Web.Script.Serialization.JavaScriptObjectSerializer类的实例方法 SerializeValue。很自然,在构造JavaScriptObjectSerializer的时候传入的 IJavaScriptSerializationContext对象为当前的WebServiceData。我们来看一下SerializeValue 方法的实现:
SerializeValue方法分析
 1 private void SerializeValue(object o)
 2 {
 3     // 如果o不为null,并且提供了JavaScriptConverter该类型对应
 4     if ((o != null&& JavaScriptConverter.ConverterExistsForType(o.GetType()))
 5     {
 6         // 则调用该类型相应的JavaScriptConverter进行序列化
 7         this._sb.Append(JavaScriptConverter.SerializeObject(o));
 8     }
 9     else
10     {
11         // 否则调用Atlas内部(相对于自定义的“外部”)
12         // 方式序列化对象
13         this.SerializeValueInternal(o);
14     }
15 }

  如果我们在Web.config里配置了与该类型对应的JavaScriptConverter(详细信息请见之前的文章《深入Atlas系列:Web Sevices Access in Atlas(5) - 对于复杂数据类型的支持(中)》), 那么就会调用相应JavaScriptConverter对象的SerializeObject方法进行对象的序列化操作,这就是之前谈到的自定义对象序 列化和反序列化功能的体现。在之后的文章里我将提供相应的示例来具体解释如何通过继承JavaScriptConverter来对Atlas进行扩展,在 某些时候这样的扩展还是相当有必要的,特别是在使用复杂类型的时候。

  在这里,我们只关注Atlas提供的内部序列化对象的方法。因此来看一下SerializeValueInternal方法的实现。代码如下:
SerializeValueInternal方法
  1 private void SerializeValueInternal(object o)
  2 {
  3     // 如果o是null,或者是DBNull
  4     if ((o == null|| DBNull.Value.Equals(o))
  5     {
  6         // 则输出"null"
  7         this._sb.Append("null");
  8     }
  9     else
 10     {
 11         // 尝试转换成String
 12         string text = o as string;
 13         if (text != null)
 14         {
 15             // 如果对象o是String,则会调用SerialieString序列化之,
 16             // 方法里会做必要的字符替换,以保证正确性。
 17             this.SerializeString(text);
 18         }
 19         else if (o is char)
 20         {
 21             this.SerializeString(o.ToString());
 22         }
 23         else if (o is bool)
 24         {
 25             this.SerializeBoolean((bool) o);
 26         }
 27         // 对于剩余的基本类型,或者decimal
 28         else if (o.GetType().IsPrimitive || (o is decimal))
 29         {
 30             // 设法通过IConverible转换
 31             IConvertible convertible = o as IConvertible;
 32             if (convertible1 != null)
 33             {
 34                 this._sb.Append(convertible.ToString(CultureInfo.InvariantCulture));
 35             }
 36             else
 37             {
 38                 // 否则直接输出ToString。
 39                 this._sb.Append(o.ToString());
 40             }
 41         }
 42         else if (o is DateTime) // 对于DateTime类型
 43         {
 44             // 使用DateTimeJavaScriptObjectSerializer
 45             // 反序列化之,最后的形式为new Date(...)
 46             this._sb.Append(DateTimeJavaScriptObjectSerializer.Serialize(o));
 47         }
 48         else
 49         {
 50             Type type = o.GetType();
 51             // 如果对象o是枚举类型
 52             if (type.IsEnum)
 53             {
 54                 // 则返回它的数值
 55                 this._sb.Append((int) o);
 56             }
 57             else
 58             {
 59                 try
 60                 {
 61                     // 侦测循环引用的辅助哈希表
 62                     if (this._objectsInUse == null)
 63                     {
 64                         this._objectsInUse = new Hashtable();
 65                     }
 66                     // 如果有发现有已经序列化过的对象,则说明出现了循环引用。
 67                     else if (this._objectsInUse.ContainsKey(o))
 68                     {
 69                         // 释放哈希表
 70                         this._objectsInUse = null;
 71                         // 并抛出异常
 72                         throw new InvalidOperationException(
 73                             string.Format(
 74                                 CultureInfo.CurrentCulture,
 75                                 AtlasWeb.CircularReference,
 76                                 new object[] { o.GetType().FullName }
 77                             )
 78                         );
 79                     }
 80 
 81                     // 如果对象o还没有被序列化过,则作上标记
 82                     this._objectsInUse.Add(o, null);
 83 
 84                     // 如果对象o是IDictionary对象
 85                     IDictionary dictionary = o as IDictionary;
 86                     if (dictionary != null)
 87                     {
 88                         // 则调用SerializeDictionary方法反序列化之。
 89                         this.SerializeDictionary(dictionary);
 90                     }
 91                     else // 否则
 92                     {
 93                         // 如果是IEnumerable对象
 94                         IEnumerable enumerable = o as IEnumerable;
 95                         if (enumerable != null)
 96                         {
 97                             // 则调用SerializeEnumerable方法反序列化之
 98                             this.SerializeEnumerable(enumerable);
 99                         }
100                         else
101                         {
102                             // 否则,就使用SerializeCustomObject反序列化之
103                             this.SerializeCustomObject(o);
104                         }
105                     }
106                 }
107                 finally
108                 {
109                     if (this._objectsInUse != null)
110                     {
111                         this._objectsInUse.Remove(o);
112                     }
113                 }
114             }
115         }
116     }
117 }

  企图对于如此清晰的代码作任何过多的解释似乎都是愚蠢的行为。需要注意的地方似乎只有在序列化中会对循环的引用进行侦测,如果发现已经被序列 化过的代码又需要序列化了则会抛出InvalidOperationException。不过对于复杂对象,这个似乎是无法避免的,在之后的文章里我将提 供示例来说明如何使用继承JavaScriptConveter来提供对于复杂类型的自定义序列化以及反序列化的方式,正如Atlas中为 DataSet,DataTable和DataRow定义这种扩展一样。

  在上面这个方法中,SerializeDictionary和SerializeEnumerable的实现都非常简单,只是将对象序列化成JSON形式的“{}”或“{}”而已。我们在这里只关注SerializeCustomObject方法的实现,代码如下:
SerializeCustomObject方法分析
 1 private void SerializeCustomObject(object o)
 2 {
 3     // 一个对象在JSON里使用字典的形式表示,
 4     // 因此需要添加左大括号"{"
 5     this._sb.Append('{');
 6     bool isFirstEle = true;
 7     // 如果提供了IJavaScriptSerializationContext
 8     if (this._context != null)
 9     {
10         // 则从上下文中获得表示Type的String,
11         // 这个值必须能够从同一个上下文找回相同的Type对象,
12         // 在WebServiceData中的实现就是返回type.FullName
13         string typeStr = this._context.GetTypeString(o.GetType());
14         // 如果上下文中有这个对象
15         if (typeStr != null)
16         {
17             // 则通过__serverType输出类型
18             this.SerializeString("__serverType");
19             this._sb.Append(':');
20             this.SerializeValue(typeStr);
21             isFirstEle = false;
22         }
23     }
24 
25     // 获得对象所有的共有成员变量
26     FieldInfo[] fieldInfoArray = o.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
27     // 枚举每一个FieldInfo
28     foreach (FieldInfo fieldInfo in fieldInfoArray)
29     {
30         // 如果没有使用XmlIgnoreAttribute进行标记
31         if (!fieldInfo.IsDefined(typeof(XmlIgnoreAttribute), true))
32         {
33             // 如果不是第一个元素,则输出逗号。
34             if (!isFirstEle)
35             {
36                 this._sb.Append(',');
37                 isFirstEle = false;
38             }
39             // 递归调用SerializeValue方法序列化对象
40             this.SerializeString(fieldInfo.Name);
41             this._sb.Append(':');
42             this.SerializeValue(fieldInfo.GetValue(o));
43         }
44     }
45 
46     // 同上,只是枚举所有的带Get方法的属性
47     PropertyInfo[] propInfoArray = o.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance);
48     foreach (PropertyInfo propInfo in propInfoArray)
49     {
50         if (!propInfo.IsDefined(typeof(XmlIgnoreAttribute), true))
51         {
52             // 获得该属性的get方法
53             MethodInfo methodInfo = propInfo.GetGetMethod();
54             if (methodInfo.GetParameters().Length <= 0)
55             {
56                 if (!isFirstEle)
57                 {
58                     this._sb.Append(',');
59                 }
60                 this.SerializeString(propInfo.Name);
61                 this._sb.Append(':');
62                 // 输出该属性的Get方法所得的返回值
63                 this.SerializeValue(methodInfo.Invoke(o, null));
64                 isFirstEle = false;
65             }
66         }
67     }
68 
69     this._sb.Append('}');
70 }

  通过代码可以看到,在序列化一个对象时对略过使用了XmlIgnoreAttribute标记的成员变量或属性,在开发时可以利用这一点。其余的似乎都没有什么可说的了。



   就这样,Atlas调用Web Service方法对于复杂类型支持的代码就全部分析完了。代码可能虽然有点多,但是其逻辑还是相当清晰的,也提供了非常强大的自定义方式以供扩展。在这 篇文章之后,我将会提供几个示例来说明“深入Atlas系列:Web Sevices Access in Atlas(6) - 对于复杂数据类型的支持”三篇文章里所提到的功能,希望对大家有用。另外,如果有朋友看了这些文章有任何感想和使用技巧的话,欢迎和我进行讨论,本人不胜 感激。:)

本文出自 “赵��” 博客,转载请与作者联系!

你可能感兴趣的:(Web,in,数据类型,Access,Atlas,Sevices)