一个包含着JSON信息的Dictionary已经作为rawParams参数被传入该方法,代码如下:
StrongTypeParameters方法
1 private IDictionary<string, object> StrongTypeParameters(IDictionary<string, object> rawParams)
2 {
3 // 从Web Service方法的参数获得存放参数名和WebServiceParameterData的字典
4 // WebServiceParameterData对象存放了参数的ParameterInfo和参数的编号,从0开始。
5 IDictionary<string, WebServiceParameterData> dictParamData = this.ParameterDataDictionary;
6
7 // 建立一个存放参数名和调用方法所用的参数的字典,
8 // 这就是最终使用的参数字典。
9 IDictionary<string, object> dictTypedParam = new Dictionary<string, object>(rawParams.Count);
10
11 // 枚举rawParams里的每个Pair,每一个Pair的Value将被
12 // 被转变为调用Web Service方法所使用的参数。
13 foreach (KeyValuePair<string, object> pair in rawParams)
14 {
15 // 获得参数名
16 string paramName = pair.Key;
17 if (dictParamData.ContainsKey(paramName))
18 {
19 // 获得参数类型
20 Type type = dictParamData[paramName].ParameterInfo.ParameterType;
21 // 通过调用ObjectConvertier.ConvertToType方法,
22 // 将pair.Value转化为类型为type的对象。
23 // this.Owner是表示当前Web Service类的WebServiceData对象。
24 dictTypedParam[paramName] = ObjectConverter.ConvertToType(pair.Value, type, this.Owner);
25 }
26 }
27 return dictTypedParam;
28 }
1 private IDictionary<string, object> StrongTypeParameters(IDictionary<string, object> rawParams)
2 {
3 // 从Web Service方法的参数获得存放参数名和WebServiceParameterData的字典
4 // WebServiceParameterData对象存放了参数的ParameterInfo和参数的编号,从0开始。
5 IDictionary<string, WebServiceParameterData> dictParamData = this.ParameterDataDictionary;
6
7 // 建立一个存放参数名和调用方法所用的参数的字典,
8 // 这就是最终使用的参数字典。
9 IDictionary<string, object> dictTypedParam = new Dictionary<string, object>(rawParams.Count);
10
11 // 枚举rawParams里的每个Pair,每一个Pair的Value将被
12 // 被转变为调用Web Service方法所使用的参数。
13 foreach (KeyValuePair<string, object> pair in rawParams)
14 {
15 // 获得参数名
16 string paramName = pair.Key;
17 if (dictParamData.ContainsKey(paramName))
18 {
19 // 获得参数类型
20 Type type = dictParamData[paramName].ParameterInfo.ParameterType;
21 // 通过调用ObjectConvertier.ConvertToType方法,
22 // 将pair.Value转化为类型为type的对象。
23 // this.Owner是表示当前Web Service类的WebServiceData对象。
24 dictTypedParam[paramName] = ObjectConverter.ConvertToType(pair.Value, type, this.Owner);
25 }
26 }
27 return dictTypedParam;
28 }
我们可以看到,真正起作用的方法是ObjectConverter.ConvertToType静态方法,它通过传入的对象信息,目标类型来获得最终我们需要的参数对象。那么this.Owner,这个WebServiceData对象的作用是什么呢?我们先来简单看一下ObjectConverter.ConvertToType静态方法的代码:
ConvertToType静态方法
1 internal static object ConvertToType(object o, Type type, IJavaScriptSerializationContext context)
2 {
3 // 构造一个ObjectConvertier,它需要得到一个类型为
4 // IJavaScriptSerializationContext的对象作为工作的上下文
5 ObjectConverter converter = new ObjectConverter(context);
6
7 // 调用ConvertObjectToType方法来获得将o转变为
8 // type类型的对象,以用作Web Services方法的参数
9 return converter.ConvertObjectToType(o, type);
10 }
1 internal static object ConvertToType(object o, Type type, IJavaScriptSerializationContext context)
2 {
3 // 构造一个ObjectConvertier,它需要得到一个类型为
4 // IJavaScriptSerializationContext的对象作为工作的上下文
5 ObjectConverter converter = new ObjectConverter(context);
6
7 // 调用ConvertObjectToType方法来获得将o转变为
8 // type类型的对象,以用作Web Services方法的参数
9 return converter.ConvertObjectToType(o, type);
10 }
可以发现,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方法的实现,代码如下:
ConvertObjectToType方法
1 private object ConvertObjectToType(object o, Type type)
2 {
3 // 如果传入的o为null的话,
4 // 那么不管type是什么都要返回null了
5 if (o == null)
6 {
7 return null;
8 }
9
10 // 查看有没有在配置中加载Converter
11 if (JavaScriptConverter.ConverterExistsForType(type))
12 {
13 // 如果存在的话则现将o进行序列化得到字符串
14 string serializedObj = JavaScriptObjectSerializer.Serialize(o);
15 // 然后再通过type对应的Converter来反序列化对象
16 return JavaScriptConverter.DeserializeString(serializedObj, type);
17 }
18
19 // 否则则使用内部的转换方式(相对于在配置文件
20 // 中加载的“外部”转换)转换对象。
21 return this.ConvertObjectToTypeInternal(o, type);
22 }
1 private object ConvertObjectToType(object o, Type type)
2 {
3 // 如果传入的o为null的话,
4 // 那么不管type是什么都要返回null了
5 if (o == null)
6 {
7 return null;
8 }
9
10 // 查看有没有在配置中加载Converter
11 if (JavaScriptConverter.ConverterExistsForType(type))
12 {
13 // 如果存在的话则现将o进行序列化得到字符串
14 string serializedObj = JavaScriptObjectSerializer.Serialize(o);
15 // 然后再通过type对应的Converter来反序列化对象
16 return JavaScriptConverter.DeserializeString(serializedObj, type);
17 }
18
19 // 否则则使用内部的转换方式(相对于在配置文件
20 // 中加载的“外部”转换)转换对象。
21 return this.ConvertObjectToTypeInternal(o, type);
22 }
从这里开始,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 >
< 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方法使用。接下来,我们来看一下它的实现:
ConvertObjectToTypeInternal方法
1 private object ConvertObjectToTypeInternal(object o, Type type)
2 {
3 // 查看对象o是否为IDictionary。
4 IDictionary<string, object> dictObj = o as IDictionary<string, object>;
5 if (dictObj != null)
6 {
7 // 如果是,则调用ConvertDictionaryToObject方法转换对象o
8 return this.ConvertDictionaryToObject(dictObj, type);
9 }
10
11 // 同样的,查看对象o是否为IList对象
12 IList listObj = o as IList;
13 if (listObj != null)
14 {
15 // 如果是,则调用ConvertListToObject方法转换对象o
16 return this.ConvertListToObject(listObj, type);
17 }
18
19 // 如果type为null或者o已经是type类型了
20 if ((type == null) || (o.GetType() == type))
21 {
22 // 那么直接返回对象即可
23 return o;
24 }
25
26 // 又是可扩展的一点!
27 // 获得使用TypeConverterAttribute标注的TypeConverter
28 TypeConverter converter = TypeDescriptor.GetConverter(type);
29
30 // 如果该TypeConverter不能够从o这个Type转化
31 if (!converter.CanConvertFrom(o.GetType()))
32 {
33 // 那么如果该TypeConverter能够从string进行转换
34 if (converter.CanConvertFrom(typeof(string)))
35 {
36 // 获得o的Converter,将其转换成string。
37 string text = TypeDescriptor.GetConverter(o).ConvertToInvariantString(o);
38 // 再使用目标type的Converter从text转换
39 return converter.ConvertFromInvariantString(text);
40 }
41
42 // 如果目标对象无法从o进行直接赋值
43 // 可以简单理解为o不是type类型、type的子类类型
44 // (包括接口实现)等,也无法隐式转换为type类型。
45 if (!type.IsAssignableFrom(o.GetType()))
46 {
47 // 无法转换了,抛出异常
48 throw new InvalidOperationException(
49 string.Format(
50 CultureInfo.CurrentCulture,
51 AtlasWeb.CannotConvertObjectToType,
52 new object[] { o.GetType(), type }
53 )
54 );
55 }
56
57 // 由于能购把o直接转换成目标类型,那么直接返回
58 return o;
59 }
60
61 // 到了这一步,说明该TypeConverter能够
62 // 从o的类型这里进行转换,于是转换。
63 return converter.ConvertFrom(o);
64 }
1 private object ConvertObjectToTypeInternal(object o, Type type)
2 {
3 // 查看对象o是否为IDictionary。
4 IDictionary<string, object> dictObj = o as IDictionary<string, object>;
5 if (dictObj != null)
6 {
7 // 如果是,则调用ConvertDictionaryToObject方法转换对象o
8 return this.ConvertDictionaryToObject(dictObj, type);
9 }
10
11 // 同样的,查看对象o是否为IList对象
12 IList listObj = o as IList;
13 if (listObj != null)
14 {
15 // 如果是,则调用ConvertListToObject方法转换对象o
16 return this.ConvertListToObject(listObj, type);
17 }
18
19 // 如果type为null或者o已经是type类型了
20 if ((type == null) || (o.GetType() == type))
21 {
22 // 那么直接返回对象即可
23 return o;
24 }
25
26 // 又是可扩展的一点!
27 // 获得使用TypeConverterAttribute标注的TypeConverter
28 TypeConverter converter = TypeDescriptor.GetConverter(type);
29
30 // 如果该TypeConverter不能够从o这个Type转化
31 if (!converter.CanConvertFrom(o.GetType()))
32 {
33 // 那么如果该TypeConverter能够从string进行转换
34 if (converter.CanConvertFrom(typeof(string)))
35 {
36 // 获得o的Converter,将其转换成string。
37 string text = TypeDescriptor.GetConverter(o).ConvertToInvariantString(o);
38 // 再使用目标type的Converter从text转换
39 return converter.ConvertFromInvariantString(text);
40 }
41
42 // 如果目标对象无法从o进行直接赋值
43 // 可以简单理解为o不是type类型、type的子类类型
44 // (包括接口实现)等,也无法隐式转换为type类型。
45 if (!type.IsAssignableFrom(o.GetType()))
46 {
47 // 无法转换了,抛出异常
48 throw new InvalidOperationException(
49 string.Format(
50 CultureInfo.CurrentCulture,
51 AtlasWeb.CannotConvertObjectToType,
52 new object[] { o.GetType(), type }
53 )
54 );
55 }
56
57 // 由于能购把o直接转换成目标类型,那么直接返回
58 return o;
59 }
60
61 // 到了这一步,说明该TypeConverter能够
62 // 从o的类型这里进行转换,于是转换。
63 return converter.ConvertFrom(o);
64 }
如果当前需要转换的对象为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方法代码:
ConvertListToObject方法
1 private IList ConvertListToObject(IList list, Type type)
2 {
3 // 如果type是null,或者是object,
4 // 或者是Array,或者是ArrayList类型,
5 // 那么就使用ArrayList作为容器返回。
6 if (((type == null) || (type == typeof(object))) || (type.IsArray || (type == typeof(ArrayList))))
7 {
8 // 首先将元素对象假设为object
9 Type eleType = typeof(object);
10 // 如果type不是null,并且type不是object的类型。
11 if ((type != null) && (type != typeof(object)))
12 {
13 // 那么获得IList的元素类型
14 eleType = type.GetElementType();
15 }
16
17 // 创建目标容器:一个ArrayList
18 ArrayList destList1 = new ArrayList();
19 // 调用AddItemToList方法将list内的元素,
20 // 转换为eleType表示的类型,放入目标容器中。
21 this.AddItemToList(list, destList1, eleType);
22
23 // 如果目标类型是ArrayList
24 if (type == typeof(ArrayList))
25 {
26 // 那么直接返回容器作为结果
27 return destList1;
28 }
29
30 // 否则,则返回eleType表示类型的数组
31 return destList1.ToArray(eleType);
32 }
33
34 // 如果这是一个“能够在客户端被实例化”的对象,即:
35 // 非抽象,非接口,非数组,没有JavaScriptConverter与之对应,
36 // 并且存在无参数的实例构造函数。
37 // 要进入该if体,目标type可以直接赋值给IList类型
38 if (ObjectConverter.IsClientInstantiatableType(type) && typeof(IList).IsAssignableFrom(type))
39 {
40 // 那么就使用Activator.CreateInstance方法构造目标type的对象,
41 // 作为容器,并将其转换为IList对象以便操作。
42 IList destList2 = (IList) Activator.CreateInstance(type);
43 // 调用AddItemToList方法将list内元素放入容器,
44 // 第三个参数为null表示不做类型转换。
45 this.AddItemToList(list, destList2, null);
46 return destList2;
47 }
48
49 // 如果目标对象为范型类型,并且只有一个范型类型,例如List<T>
50 if (type.IsGenericType && (type.GetGenericArguments().Length == 1))
51 {
52 // 获得那个范型类型
53 Type genericType = type.GetGenericArguments()[0];
54 // 调用typeof(IEnumerable<>).MakeGenericType方法构造一个范型对象的Type
55 Type genericEnumType = ObjectConverter._enumerableGenericType.MakeGenericType(new Type[] { genericType });
56
57 // 如果能够将这个对象直接赋值给type类型变量
58 if (genericEnumType.IsAssignableFrom(type))
59 {
60 // 调用typeof(List<>).MakeGenericType方法获得一个List<genericType>的Type
61 Type genericListType = ObjectConverter._listGenericType.MakeGenericType(new Type[] { genericType });
62 // 构造一个范型List
63 IList genericList = (IList) Activator.CreateInstance(genericListType);
64 // 调用AddItemToList方法将list内的元素,
65 // 转换为eleType表示的类型,放入目标容器中。
66 this.AddItemToList(list, genericList, genericType);
67 return genericList;
68 }
69 }
70
71 // 无法转换,抛出异常。
72 throw new InvalidOperationException(
73 string.Format(
74 CultureInfo.CurrentCulture,
75 AtlasWeb.ArrayTypeNotSupported,
76 new object[] { type.FullName }
77 )
78 );
79 }
1 private IList ConvertListToObject(IList list, Type type)
2 {
3 // 如果type是null,或者是object,
4 // 或者是Array,或者是ArrayList类型,
5 // 那么就使用ArrayList作为容器返回。
6 if (((type == null) || (type == typeof(object))) || (type.IsArray || (type == typeof(ArrayList))))
7 {
8 // 首先将元素对象假设为object
9 Type eleType = typeof(object);
10 // 如果type不是null,并且type不是object的类型。
11 if ((type != null) && (type != typeof(object)))
12 {
13 // 那么获得IList的元素类型
14 eleType = type.GetElementType();
15 }
16
17 // 创建目标容器:一个ArrayList
18 ArrayList destList1 = new ArrayList();
19 // 调用AddItemToList方法将list内的元素,
20 // 转换为eleType表示的类型,放入目标容器中。
21 this.AddItemToList(list, destList1, eleType);
22
23 // 如果目标类型是ArrayList
24 if (type == typeof(ArrayList))
25 {
26 // 那么直接返回容器作为结果
27 return destList1;
28 }
29
30 // 否则,则返回eleType表示类型的数组
31 return destList1.ToArray(eleType);
32 }
33
34 // 如果这是一个“能够在客户端被实例化”的对象,即:
35 // 非抽象,非接口,非数组,没有JavaScriptConverter与之对应,
36 // 并且存在无参数的实例构造函数。
37 // 要进入该if体,目标type可以直接赋值给IList类型
38 if (ObjectConverter.IsClientInstantiatableType(type) && typeof(IList).IsAssignableFrom(type))
39 {
40 // 那么就使用Activator.CreateInstance方法构造目标type的对象,
41 // 作为容器,并将其转换为IList对象以便操作。
42 IList destList2 = (IList) Activator.CreateInstance(type);
43 // 调用AddItemToList方法将list内元素放入容器,
44 // 第三个参数为null表示不做类型转换。
45 this.AddItemToList(list, destList2, null);
46 return destList2;
47 }
48
49 // 如果目标对象为范型类型,并且只有一个范型类型,例如List<T>
50 if (type.IsGenericType && (type.GetGenericArguments().Length == 1))
51 {
52 // 获得那个范型类型
53 Type genericType = type.GetGenericArguments()[0];
54 // 调用typeof(IEnumerable<>).MakeGenericType方法构造一个范型对象的Type
55 Type genericEnumType = ObjectConverter._enumerableGenericType.MakeGenericType(new Type[] { genericType });
56
57 // 如果能够将这个对象直接赋值给type类型变量
58 if (genericEnumType.IsAssignableFrom(type))
59 {
60 // 调用typeof(List<>).MakeGenericType方法获得一个List<genericType>的Type
61 Type genericListType = ObjectConverter._listGenericType.MakeGenericType(new Type[] { genericType });
62 // 构造一个范型List
63 IList genericList = (IList) Activator.CreateInstance(genericListType);
64 // 调用AddItemToList方法将list内的元素,
65 // 转换为eleType表示的类型,放入目标容器中。
66 this.AddItemToList(list, genericList, genericType);
67 return genericList;
68 }
69 }
70
71 // 无法转换,抛出异常。
72 throw new InvalidOperationException(
73 string.Format(
74 CultureInfo.CurrentCulture,
75 AtlasWeb.ArrayTypeNotSupported,
76 new object[] { type.FullName }
77 )
78 );
79 }
这个方法的作用是将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方法。相比于前者返回的是一个“列表”,而后者则返回的则好比是一个“元素”的对象。代码如下:
ConvertDictionaryToObject方法
1 private object ConvertDictionaryToObject(IDictionary<string, object> dictionary, Type type)
2 {
3 object typeObj;
4 Type destType = type;
5 string typeName = null;
6 object resultObj = dictionary;
7
8 // 查看Dictionary中有没有__serverType对应的值,
9 // 如果有__serverType,则忽视type,而使用__serverType的标记。
10 if (dictionary.TryGetValue("__serverType", out typeObj))
11 {
12 // 如果有的话,则通过ConvertObjectToType方法将其转化为string,
13 // 一般来说,typeObj已经是string类型,因此这个方法会很快返回值。
14 typeName = this.ConvertObjectToType(typeObj, typeof(string)) as string;
15
16 // 如果指定了typeName
17 if (typeName != null)
18 {
19 // 如果指定了JavaScript序列化操作的上下文
20 if (this._context != null)
21 {
22 // 则必须在上下文中寻找这个类型
23 destType = this._context.GetType(typeName);
24 if (destType == null)
25 {
26 throw new InvalidOperationException();
27 }
28 }
29 // 将__serverType移除,因为它不会被作为
30 // “成员变量”或“属性”赋值
31 dictionary.Remove("__serverType");
32 }
33 }
34
35 // 如果是“客户端可实例化”的对象,即:
36 // 非抽象,非接口,非数组,没有JavaScriptConverter与之对应,
37 // 并且存在无参数的实例构造函数。
38 if ((typeName != null) || ((destType != null) && ObjectConverter.IsClientInstantiatableType(destType)))
39 {
40 // 则通过目标类型构造对象
41 resultObj = Activator.CreateInstance(destType);
42 }
43
44 // 如果构造出来的对象无法被赋值到type
45 if ((type != null) && !type.IsAssignableFrom(resultObj.GetType()))
46 {
47 // 则抛出异常
48 throw new InvalidOperationException(
49 string.Format(
50 CultureInfo.InvariantCulture,
51 AtlasWeb.DeserializerTypeMismatch,
52 new object[] { type.FullName }
53 )
54 );
55 }
56
57 // 枚举输入Dictionary中的每个key。
58 List<string> keyList = new List<string>(dictionary.Keys);
59 foreach (string key in keyList)
60 {
61 // 为Property或者Field赋值
62 object value = dictionary[key];
63 this.AssignToPropertyOrField(value, resultObj, key);
64 }
65
66 return resultObj;
67 }
1 private object ConvertDictionaryToObject(IDictionary<string, object> dictionary, Type type)
2 {
3 object typeObj;
4 Type destType = type;
5 string typeName = null;
6 object resultObj = dictionary;
7
8 // 查看Dictionary中有没有__serverType对应的值,
9 // 如果有__serverType,则忽视type,而使用__serverType的标记。
10 if (dictionary.TryGetValue("__serverType", out typeObj))
11 {
12 // 如果有的话,则通过ConvertObjectToType方法将其转化为string,
13 // 一般来说,typeObj已经是string类型,因此这个方法会很快返回值。
14 typeName = this.ConvertObjectToType(typeObj, typeof(string)) as string;
15
16 // 如果指定了typeName
17 if (typeName != null)
18 {
19 // 如果指定了JavaScript序列化操作的上下文
20 if (this._context != null)
21 {
22 // 则必须在上下文中寻找这个类型
23 destType = this._context.GetType(typeName);
24 if (destType == null)
25 {
26 throw new InvalidOperationException();
27 }
28 }
29 // 将__serverType移除,因为它不会被作为
30 // “成员变量”或“属性”赋值
31 dictionary.Remove("__serverType");
32 }
33 }
34
35 // 如果是“客户端可实例化”的对象,即:
36 // 非抽象,非接口,非数组,没有JavaScriptConverter与之对应,
37 // 并且存在无参数的实例构造函数。
38 if ((typeName != null) || ((destType != null) && ObjectConverter.IsClientInstantiatableType(destType)))
39 {
40 // 则通过目标类型构造对象
41 resultObj = Activator.CreateInstance(destType);
42 }
43
44 // 如果构造出来的对象无法被赋值到type
45 if ((type != null) && !type.IsAssignableFrom(resultObj.GetType()))
46 {
47 // 则抛出异常
48 throw new InvalidOperationException(
49 string.Format(
50 CultureInfo.InvariantCulture,
51 AtlasWeb.DeserializerTypeMismatch,
52 new object[] { type.FullName }
53 )
54 );
55 }
56
57 // 枚举输入Dictionary中的每个key。
58 List<string> keyList = new List<string>(dictionary.Keys);
59 foreach (string key in keyList)
60 {
61 // 为Property或者Field赋值
62 object value = dictionary[key];
63 this.AssignToPropertyOrField(value, resultObj, key);
64 }
65
66 return resultObj;
67 }
在这里可以发现,如果客户端传来的对象指定了__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,最后设值。
就这样,一个对象就成功地被构造出来了。
通过分析这篇文章的代码,我们至少可以发现一下几点:
- 在Atlas中,通过实现JavaScriptConverter的子类可以自定一个特定类型的序列化和反序列化能力。
- 在Atlas中,通过实现TypeConverter并使用TypeConverterAttribute标记可以为一个特定类型,自定义从基本类型(例如Int32,Double,DateTime,Boolean等)的反序列化的能力。
- Atlas访问Web Services方法对于范型类型(甚至对于普通数据类型)的支持,是不完整的,不过已经足够大多数应用的需求。而且对于不支持的数据类型,我们可以通过自定义的方式提供对它们的支持。
- Atlas访问Web Services方法有非常灵活的可控性,它的功能甚至超过普通的Web Services访问,能够做到普通Web Services无法做到的有用功能。
到现在为止,我们对于服务器端反序列化客户端数据的方式已经分析地差不多了,不过还缺少对于那个IJavaScriptSerializationContext,也就是那个WebServiceData对象中GetType方法的分析与理解。那么这个对象这个方法到底是如何工作的呢?另外,得到了调用Web Services方法的结果以后,它又是怎么被序列化并输出是客户端的呢?
我们将在下一篇文章中讨论这些问题。