ASP.NET AJAX(最初代码名为“ATLAS”)框架,作为重点支持ASP.NET开发平台的开源Ajax框架在它一出世时就受到广大.NET开发人员的青睐。在本文中,我们将专注于分析ASP.NET AJAX编程中服务器与客户端通信过程中的数据存储形式的问题。具体地说,我们将探讨一个实现序列化与反序列化的服务器端对象—JavaScriptSerializer。
一、JSON简介
在正式讨论JSON格式之前,首先让我们简要回忆一下XML。XML是“可扩展的标记语言”的简称,它提供了定义Web中一系列数据传输协议的方式,是文本型的,被人们誉为“完全开发Internet和Web潜力的理想方式”。
那么,为什么ASP.NET AJAX中还要引入JSON呢?还是让我们先来观察一下例子吧。比如当前Web页面将从后台载入一些通讯录的信息,这些信息如果写成XML,可能是如下形式:
<contact>
<friend>
<name>Michael</name>
<email>[email protected]</email>
<homepage>http://www.jialing.net</homepage>
</friend>
<friend>
<name>John</name>
<email>[email protected]</email>
<homepage>http://www.john.com</homepage>
</friend>
<friend>
<name>Peggy</name>
<email>[email protected]</email>
<homepage>http://www.peggy.com</homepage>
</friend>
</contact>
而写成JSON形式,则会是:
[
friend: {
name:"Michael",
email:"[email protected]",
homepage:"http://www.jialing.net"
},
friend: {
name:"John",
email:"[email protected]",
homepage:"http://www.jobn.com"
},
friend: {
name:"Peggy",
email:"[email protected]",
homepage:"http://www.peggy.com"
}
]
相比之下,后面JSON表达方式明显更为简洁。其实,我们最关心的并不只是表达上的简单性,最重要的是如何简化DOM的解析。因为不同的浏览器中XML/SOAP解释器的实现是有所不同的,所以,解释相同的XML和SOAP格式的数据未必会一定得到一致的结果。为此,ASP.NET AJAX中特地引入了更为轻量级的JSON格式,并创建了一致的JSON解析器,再加上JavaScript本身就支持以JSON方式创建对象,所以,这一切都显然十分流畅自然和水到渠成。
JSON,即JavaScript对象标志,如今已经成为JavaScript内置支持的数据格式,它实现了比以前的XML和SOAP更为有效的网络数据传送方式。简言之,JSON是一种基本的把对象序列化成轻量级字符串的方式,而字符串数据可以轻松地通过网络进行数据传输而且毫不失真。我们不妨再来考虑下面简单的C#类:
public class User
{
string FirstName;
string LastName;
}
现在,假设定义了上面对象的如下实例:
User usr = new User();
usr.FirstName = "John"
usr.LastName = "Smith";
当把上面数据序列化成JSON格式的文本时,最终会得到如下所示的形式:
{ FirstName : "John"
LastName : "Smith" }
借助于JavaScript内置支持的方法eval()或者ASP.NET AJAX提供的Sys.Serialization.JavaScriptSerializer类,我们可以在JavaScript代码中实现JSON文本串的反序列化(即再还原成最初的对象形式)。
在本文中,我们将专注于讨论使用ASP.NET AJAX提供的特有类来实现把一个JavaScript对象序列化为文本串,然后发送到服务器端,以及如何使用服务器端类JavaScriptSerializer把JSON文本串反序列化成原始的对象数据。
二、实现序列/反序列化的关键—JavaScriptSerializer
实现序列化及反序列化的对象是JavaScriptSerializer。下面,我们来具体地看一下这个对象的成员定义情况:
public class JavaScriptSerializer
{
//字段
internal const int DefaultMaxJsonLength = 0x200000;
internal const int DefaultRecursionLimit = 100;
internal const string ServerTypeFieldName = "__type";
//方法
static JavaScriptSerializer();
public JavaScriptSerializer();
public JavaScriptSerializer(JavaScriptTypeResolver resolver);
public T ConvertToType(object obj);
public T Deserialize(string input);
public object DeserializeObject(string input);
public void RegisterConverters(IEnumerable converters);
public string Serialize(object obj);
public void Serialize(object obj, StringBuilder output);
//属性
public int MaxJsonLength { get; set; }
public int RecursionLimit { get; set; }
internal JavaScriptTypeResolver TypeResolver { get; }
}
首先需要注意的是,如果嵌套的对象数目大于RecursionLimit属性中所定义的值100的话,序列化过程将会失败。显然,如果序列化后的字符串的长度超出MaxJsonLength属性所定义的值0x200000(即十进制的2,097,152)的话,序列化过程也会失败。
此外,通过上面代码也可以看出,对象将被序列化为一个StringBuilder对象,最后返回相应的字符串数据。其实,主要的工作是在私有方法SerializeValue()中完成的。在我们讨论这个方法之前,首先注意到,JavaScriptSerializer对象使用了JavaScriptTypeResolver对象。这个JavaScript类型解析器负责实现在字符串类型与其他类型之间的相互转换;当序列化定制对象时这一功能是非常重要的。还应注意的是,__type属性将被包含于JSON序列化文本中,以便标识对象的类型。之后,客户端会把JSON文本反序列化为原始的对象形式。
JavaScriptTypeResolver对象包括两个public类型的方法,一个负责把原始类型解析为字符串类型,另一个则负责把字符串解析为原始类型。这个类的原型定义如下所示:
public abstract class JavaScriptTypeResolver
{
//方法
protected JavaScriptTypeResolver();
public abstract Type ResolveType(string id);
public abstract string ResolveTypeId(Type type);
}
显然,上面这个JavaScriptTypeResolver类是一个抽象基类;因此,必须通过其他对象进一步派生使用,从而最终实现在原始类型与字符串之间的相应解析。现在,我们可以使用的对象是SimpleTypeResolver,而且它也正好实现了上面所要求实现的方法—使用System.Type对象实现字符串描述形式与原始Type对象之间的相互解析。请看下面的例子:
public override Type ResolveType(string id)
{
return Type.GetType(id);
}
public override string ResolveTypeId(Type type)
{
if (type == null)
{
throw new ArgumentNullException("type");
}
return type.AssemblyQualifiedName;
}
最后,JavaScriptSerializer对象还有可能使用JavaScriptConverter对象,因为内置的序列化过程无法实现对于所有可用数据类型的序列化。在这种情况下,可以从抽象基类JavaScriptConverter对象派生一个子类,由它来实现特定类型数据的序列/反序列化。可以使用JavaScriptSerializer对象的RegisterConverters()方法注册一个转换器对象。此方法把所有的转换器对象存储在一个Dictionary对象中;针对多种不同的数据类型的转换器经注册后都会存储在此字典对象中。概括来看,这个Dictionary对象被定义如表格1所示的一些数据类型。
原始数据类型
|
被序列化后的形式
|
null或DBNull
|
"null"
|
string
|
带引号的字符串
|
char
|
如果是‘/0’则转换为"null";否则,序列化为带引号的字符串
|
bool
|
"true"或"false"
|
DateTime
|
"//Date(ticks since 12:00AM 1970/01/01 UTC)//"
|
Guid
|
“string representation”: sb.Append(""").Append(guid.ToString()).Append(""");
|
Uri
|
sb.Append(""").Append(uri.GetComponents(UriComponents.SerializationInfoString,UriFormat.UriEscaped)).Append(""");
|
double
|
sb.Append(((double) o).ToString("r", CultureInfo.InvariantCulture));
|
float
|
sb.Append(((float) o).ToString("r", CultureInfo.InvariantCulture));
|
primitive或decimal
|
IConvertible convertible = o as IConvertible;
sb.Append(convertible.ToString(CultureInfo.InvariantCulture)); |
Enum
|
sb.Append((int) o);
|
IDictionary
|
转换为JSON文本串,例如:
{"Key1":Value1,"Key2":Value2 ... } |
IEnumerable
|
转换为JSON文本串,例如:
{"Key1":Value1,"Key2":Value2 ... } |
对于定制对象来说,它们可以按照类似于IDictionary的方式加以序列化,但仍存在一些不同之处。如果事先定义了一个JavaScriptTypeResolver对象,那么对象的类型将被转换成一个字符串,于是对象定义中会包括一个字符串常量__type,它的后面跟着的是描述对象数据类型的字符串。所有定义为public类型而且不包含元数据ScriptIgnoreAttribute属性的字段和属性都会包含在此对象的JSON对象描述之中。
三、序列/反序列化举例
现在,让我们通过一个例子来具体分析一下序列化过程。首先,请考虑下面定义的Customer对象:
public class Customer
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}
private string _email;
public string EmailAddress
{
get { return _email; }
set { _email = value; }
}
private Phone _phoneNumber;
public Phone PhoneNumbers
{
get { return _phoneNumber; }
set { _phoneNumber = value; }
}
}
public class Phone
{
private string _homePhone;
public string HomePhone
{
get { return _homePhone; }
set { _homePhone = value; }
}
private string _workPhone;
public string WorkPhone
{
get { return _workPhone; }
set { _workPhone = value; }
}
}
如果这个对象通过一个web服务方法返回,那么此对象将在内部处理器RestHandler类的InvokeMethod()方法中被自动序列化。然而在上面的例子中,我们却在我们的Web页面的典型的Page.Page_Load()方法内部使用这些对象。因此,我们应该创建这些对象并且自动地使用前面已经讨论的JavaScriptSerializer对象对它们进行序列化。例如,我们来考虑使用如下代码来序列化一个对象的情形:
JavaScriptSerializer jsSerializer = new
JavaScriptSerializer(new SimpleTypeResolver());
Customer cust = new Customer();
cust.FirstName = "Joe";
cust.EmailAddress = "[email protected]";
cust.PhoneNumbers = new Phone();
cust.PhoneNumbers.HomePhone = "888-888-8888";
string serializedText = jsSerializer.Serialize(cust);
根据前面的分析,JavaScriptSerializer对象已经使用SimpleTypeResolver初始化完毕(其中,SimpleTypeResolver负责把要序列化的对象转换成一个字符串形式)。下面,我们来看一下序列化之后的JSON文本:
{"__type":"Customer, App_Web_plrzlwbj,
Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null","FirstName":"Joe","LastName":null,
"EmailAddress":[email protected],
"PhoneNumbers":{"__type":"Phone, App_Web_plrzlwbj, Version=0.0.0.0,
Culture=neutral, PublicKeyToken=null",
"HomePhone":"888-888-8888","WorkPhone":null}}
注意,其中的PhoneNumbers属性属于定制类型—Phone。因此,当被序列化时,PhoneNumbers属性的值本身就是一个JSON对象,它正是Phone对象的序列化之后的形式。再次强调的是,这里添加了类型标记,以备反序列化时使用,从而确保创建相应类型的原始对象。
接下来,再让我们来看一下反序列化的过程。
反序列化过程是借助于JavaScriptObjectDeserializer对象实现的。当创建一个对象的实例时,相应的JSON文本串将会以参数方式传递到这个类的构造器中。当对象实例创建结束,我们就可以调用DeserializeInternal()方法。这个方法具体负责解析JSON序列化字符串并创建相应的原始类型的对象。
上面分析了反序列化的内部实现。但是,作为使用JavaScriptSerializer类的程序员,要实现一个JSON字符串的反序列化,我们只需简单地调用JavaScriptSerializer对象的Deserialize()方法。这个方法的返回值正相应于原始对象的一个实例,而且相应的属性也被进行了正确的赋值。我们不妨来考虑下面的代码:
Customer cust = jsSerializer.Deserialize(serializedText);
在此,不再给出变量cust的内容展现,有兴趣的读者可以自动跟踪分析。
四、关于客户端JavaScriptSerializer对象
ASP.NET AJAX客户端框架中的Microsoft Ajax Library也提供了一个Sys.Serialization.JavaScriptSerializer对象,从而方便在基于非ASP.NET平台上的ASP.NET AJAX编程中的JSON数据的编码与解码。这个JavaScriptSerializer对象暴露了两个方法:serialize和deserialize。
其中,serialize方法接受一个JavaScript对象形式的参数,此函数返回一个描述JSON数据的字符串,例如:
var man = {firstName: 'John', lastName: 'Mike'};
var s1 = Sys.Serialization.JavaScriptSerializer;
var json = s1.serialize(man);
在此,变量json中存储了一个字符串,这个串中相应于变量man中存储的对象的JSON描述形式。
而deserialize方法执行与serialize方法相反的任务。它接受一个JSON字符串形式的参数,返回的是相应的JavaScript对象,例如:
var man = serializer.deserialize(json);
最后需要注意的一点是,当你使用JSON分析器时,你需要小心日期数据的处理方式,这是因为JavaScript并不直接支持日期格式的数据。更多的有关日期格式的数据的JSON操作请参考官方网站提供的示例,在此不再赘述。
五、小结
现在,你应该对ASP.NET AJAX框架中的JSON序列化及反序列化过程以及如何使用JSON序列化技术有了一个基本的认识。总体来看,无论是JSON序列化还是反序列化过程还是比较简单的,特别是在客户端分析JSON文本串要较之于XML数据的序列化分析要简单。
ASP.NET AJAX编程中服务器与客户端通信过程中的数据存储形式的问题是一个基本的问题。在基于ASP.NET AJAX服务器端框架编程中,你可以基于ASP.NET AJAX核心程序集System.Web.Extensions中命名空间System.Web.Script.Serialization中的JavaScriptSerializer类提供的方法来JSON数据的序列化与反序列化。在基于ASP.NET AJAX客户端框架编程中,你可以基于Microsoft Ajax Library中提供的Sys.Serialization.JavaScriptSerializer对象提供的方法来实现JSON数据的序列化与反序列化。