深入Atlas系列:Web Sevices Access in Atlas(6) - 对于复杂数据类型的支持(下)
2006-10-17 00:56 by 老赵, 2597 visits
在上一篇文章中,我们提到了Atlas在将一个Dictionary转换为一个对象时,会调用对应的IJavaScriptSerializationContext对象的GetType(string)方法,以获得“真正”的目标对象类型。在Atlas对于Web Services方法的Request所引起的转换过程中,这个IJavaScriptSerializationContext对象是一个WebServiceData类的实例,它的GetType方法会在对应的那个Web Services类中寻找相关的数据类型。那么Atlas是如何寻找这些类型的呢?什么样的类型会被查找到呢?
通过查看代码,我们可以发现,WebServiceData.GetType(string)方法其实是返回它的一个叫做_clientTypesDictionary的实例变量中保存的Type。WebServiceData._clientTypesDictionary在第一被使用之前会被初始化。我们在这里略过了各个方法之间的调用,直接查看真正初始化该变量的方法:WebServiceData.ProcessClientTypes方法。代码如下:
代码很容易理解,通过枚举每一个方法的每一个参数加以处理,最后还要处理返回值类型。在这里有一个相当重要的方法,就是通过ProcessClientType(Type, bool)加载可识别的参数类型,我们来看一下它的代码:
标记完可以处理的类型后,还会递归的将该类型相关的所有类型做处理。例如数组的元素和属性的类型。值得注意的是,在递归处理该类型相关的类型时,只会处理具有set方法的属性,而不会处理公有实例成员变量的类型,这也就解释了在之前的文章《深入Atlas系列:Web Sevices Access in Atlas(4) - 对于复杂数据类型的支持(上)》中问题4所遇到的类型无法找到的错误。奇怪的是,在反序列化的对象之后恢复对象信息时也为实例的成员变量设值,因此个人认为,这是Atlas的一个“失误”。因此对于作为实例成员变量的类型,除非它也直接作为Web Services方法参数出现过,否则就无法被找到了。但是个人建议,在为Atlas创建Web Services方法的参数类型时,尽量完全使用属性,而不是成员变量。
不过这样的话,仅仅只有Web Services方法参数,以及相关的类型才能在反序列化的时候被使用到,这样似乎也够用了。不过Atlas的能力还远不止这些。Atlas能够将任意的类型附加在Web Services类中,使该类型能够在反序列化过程中被找到。这就是ProcessXmlIncludeAttributes方法的功能,可以看到,它在ProcessClientType()方法中被调用了。我们来看一下它的代码:
代码非常简单,就不多加解释了。可以看出,原本用来辅助XmlSerialiaer的XmlIncludeAttribute被用在这里了!这的确是一个好消息,于是我们就能通过为一个方法标记XmlIncludeAttribute来为一个Web Services添加额外支持的数据类型,例如:
有了这个功能,就为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方法的实现:
如果我们在Web.config里配置了与该类型对应的JavaScriptConverter(详细信息请见之前的文章《深入Atlas系列:Web Sevices Access in Atlas(5) - 对于复杂数据类型的支持(中)》),那么就会调用相应JavaScriptConverter对象的SerializeObject方法进行对象的序列化操作,这就是之前谈到的自定义对象序列化和反序列化功能的体现。在之后的文章里我将提供相应的示例来具体解释如何通过继承JavaScriptConverter来对Atlas进行扩展,在某些时候这样的扩展还是相当有必要的,特别是在使用复杂类型的时候。
在这里,我们只关注Atlas提供的内部序列化对象的方法。因此来看一下SerializeValueInternal方法的实现。代码如下:
企图对于如此清晰的代码作任何过多的解释似乎都是愚蠢的行为。需要注意的地方似乎只有在序列化中会对循环的引用进行侦测,如果发现已经被序列化过的代码又需要序列化了则会抛出InvalidOperationException。不过对于复杂对象,这个似乎是无法避免的,在之后的文章里我将提供示例来说明如何使用继承JavaScriptConveter来提供对于复杂类型的自定义序列化以及反序列化的方式,正如Atlas中为DataSet,DataTable和DataRow定义这种扩展一样。
在上面这个方法中,SerializeDictionary和SerializeEnumerable的实现都非常简单,只是将对象序列化成JSON形式的“{}”或“{}”而已。我们在这里只关注SerializeCustomObject方法的实现,代码如下:
通过代码可以看到,在序列化一个对象时对略过使用了XmlIgnoreAttribute标记的成员变量或属性,在开发时可以利用这一点。其余的似乎都没有什么可说的了。
就这样,Atlas调用Web Service方法对于复杂类型支持的代码就全部分析完了。代码可能虽然有点多,但是其逻辑还是相当清晰的,也提供了非常强大的自定义方式以供扩展。在这篇文章之后,我将会提供几个示例来说明“深入Atlas系列:Web Sevices Access in Atlas(6) - 对于复杂数据类型的支持”三篇文章里所提到的功能,希望对大家有用。另外,如果有朋友看了这些文章有任何感想和使用技巧的话,欢迎和我进行讨论,本人不胜感激。:)
通过查看代码,我们可以发现,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 }
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 }
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 }
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 }
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 }
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 }
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) - 对于复杂数据类型的支持”三篇文章里所提到的功能,希望对大家有用。另外,如果有朋友看了这些文章有任何感想和使用技巧的话,欢迎和我进行讨论,本人不胜感激。:)
总算将Atlas访问Web Services方法中对于复杂类型的支持讲完了,接下来就该写一些示例了。:)