在 .NET Framework 中使用 JSON
JSON 文本可依据 JavaScript 代码轻松地进行创建和分析,这是它的一个吸引人之处。但是,当 JSON 用于 ASP.NET web 应用程序时,只有浏览器受 JavaScript 支持,因为服务器端代码很可能是用 Visual Basic 或 C# 编写的。
大多数为 ASP.NET 设计的 Ajax 库都支持以编程方式创建和分析 JSON 文本。因此,要在 .NET 应用程序中使用 JSON,请考虑使用其中一个库。有众多的开源和第三方方案可供选择,而 Microsoft 也有自己的 Ajax 库,名为 ASP.NET AJAX。
在本文中,我们会介绍使用 Jayrock 的示例,它是一种用于 Microsoft .NET Framework 的 JSON 开源实现,由合著者 Atif Aziz 创建。我们选择使用 Jayrock 而非 ASP.NET AJAX 的原因有三个:
- Jayrock 是开源的,可以按需要扩展或自定义。
- Jayrock 可用于 ASP.NET 1.x,、2.0 和 Mono 应用程序,而 ASP.NET AJAX 仅限于 ASP.NET 2.0 版。
- Jayrock 的作用域仅限于 JSON 和 JSON-RPC,前者是本文的重点内容。ASP.NET AJAX 包括对创建和分析 JSON 文本的一些支持,但它的主要用途是提供一个丰富的平台,在 ASP.NET 中构建端到端 Ajax 风格的 web 应用程序。您的重点内容为 JSON 时,额外的点缀性功能反而会让人分神。
在 .NET 中通过 Jayrock 使用 JSON,与在 .NET Framework 中通过 XmlWriter、XmlReader 和 XmlSerializer 类使用 XML 相似。位于 Jayrock 中的类 JsonWriter、JsonReader、JsonTextWriter 和 JsonTextReader 模拟 .NET Framework 类 XmlWriter、XmlReader、XmlTextWriter 和 XmlTextReader 的语义。这些类在低级别和面向流的级别与 JSON 交互时非常有用。使用这些类,可通过一系列的方法调用逐个创建或分析 JSON 文本。例如,使用 JsonWriter 类方法 WriteNumber(number) 可根据 JSON 标准写出适当的“数字”形式的字符串表示形式。JsonConvert 类提供了用于在 .NET 类型和 JSON 之间进行转换的 Export 和 Import 方法。这些方法提供了分别与 XmlSerializer 类方法 Serialize 和 Deserialize 相似的功能。
创建 JSON 文本
以下代码说明了如何使用 JsonTextWriter 类来创建洲字符串数组的 JSON 文本。此 JSON 文本会发送到传入构造函数的 TextWriter 实例,在此示例中正好是来自控制台的输出流(在 ASP.NET 中您可以使用 Response.Output):
using (JsonTextWriter writer = JsonTextWriter(Console.Out))
{
writer.WriteStartArray();
writer.WriteString("Europe");
writer.WriteString("Asia");
writer.WriteString("Australia");
writer.WriteString("Antarctica");
writer.WriteString("North America");
writer.WriteString("South America");
writer.WriteString("Africa");
writer.WriteEndArray();
}
除了 WriteStartArray、WriteString 和 WriteEndArray 方法之外,JsonWriter 类还提供了用于编写其他 JSON 值类型的方法,如 WriteNumber、WriteBoolean、WriteNull 等。WriteStartObject、WriteEndObject 和 WriteMember 方法为对象创建 JSON 文本。以下示例说明了如何为“了解 JavaScript 中的文字表示法”部分探讨的联系对象创建 JSON 文本:
private static void WriteContact()
{
using (JsonWriter writer = new JsonTextWriter(Console.Out))
{
writer.WriteStartObject(); // {
writer.WriteMember("Name"); // "Name" :
writer.WriteString("John Doe"); // "John Doe",
writer.WriteMember("PermissionToCall"); // "PermissionToCall"
:
writer.WriteBoolean(true); // true,
writer.WriteMember("PhoneNumbers"); // "PhoneNumbers" :
writer.WriteStartArray(); // [
WritePhoneNumber(writer, // {
"Location": "Home",
"Home", "555-555-1234"); // "Number":
"555-555-1234" },
WritePhoneNumber(writer, // {
"Location": "Work",
"Work", "555-555-9999 Ext. 123"); // "Number":
"555-555-9999 Ext. 123" }
writer.WriteEndArray(); // ]
writer.WriteEndObject(); // }
}
}
private static void WritePhoneNumber(JsonWriter writer, string location,
string number)
{
writer.WriteStartObject(); // {
writer.WriteMember("Location"); // "Location" :
writer.WriteString(location); // "...",
writer.WriteMember("Number"); // "Number" :
writer.WriteString(number); // "..."
writer.WriteEndObject(); // }
}
JsonConvert 类中的 Export 和 ExportToString 方法可用于将指定的 .NET 类型序列化为 JSON 文本。例如,不需要使用 JsonTextWriter 类为七大洲数组手动构建 JSON 文本,以下对 JsonConvert.ExportToString 的调用即可产生相同的结果:
string[] continents = {
"Europe", "Asia", "Australia", "Antarctica", "North America", "South
America", "Africa"
};
string jsonText = JsonConvert.ExportToString(continents);
分析 JSON 文本
JsonTextReader 类提供了各种分析 JSON 文本令牌的方法,其中核心的一种是 Read。每次调用 Read 方法时,分析器会使用下一个令牌,可能是字符串值、数字值、对象成员名称、数组开头等。可能的话,可以通过 Text 属性访问当前令牌的已分析文本。例如,如果该读取器位于 Boolean 数据中,则 Text 属性会根据实际分析值返回“true”或“false”。
以下示例代码使用 JsonTextReader 类,对包含七大洲名称的字符串数组的 JSON 文本表示形式进行分析。每个以字母“A”开头的洲会发送到控制台:
string jsonText = @"["Europe", "Asia", "Australia", "Antarctica",
"North America", "South America", "Africa"]";
using (JsonTextReader reader = new JsonTextReader(new
StringReader(jsonText)))
{
while (reader.Read())
{
if (reader.TokenClass == JsonTokenClass.String &
reader.Text.StartsWith("A"))
{
Console.WriteLine(reader.Text);
}
}
}
注意Jayrock 中的 JsonTextReader 类是一个非常自由的 JSON 文本分析器。它实际上允许的语法要比 RFC 4627 中列出的规则所规定的有效 JSON 文本多得多。例如,就象在 JavaScript 中一样,JsonTextReader 类允许单行和多行注释出现在 JSON 文本内。单行注释以双斜杠 (//) 开头,多行注释以斜杠星号 (/*) 开头,并以星号斜杠 (*/) 结尾。单行注释甚至能以井字号 (#) 开头,这在 Unix 样式的配置文件中十分常见。在所有实例中,分析器会完全跳过注释,不会通过 API 公开。和在 JavaScript 中一样,JsonTextReader 允许以撇号 (') 分隔 JSON 字符串。该分析器甚至可以容忍最后一个对象成员或者数组元素后面多余的逗号。
即使具备所有这些附加内容,JsonTextReader 仍是符合标准的分析器!而 JsonTextWriter 则只能产生严格符合标准的 JSON 文本。这遵循了通常所说的可靠性原则,即“严以律己,宽以待人”。
要将 JSON 文本直接转换为 .NET 对象,请使用 JsonConvert 类导入方法,指定输出类型和 JSON 文本。以下示例显示了从 JSON 字符串数组到 .NET 字符串数组的转换:
string jsonText = @"["Europe", "Asia", "Australia", "Antarctica",
"North America", "South America", "Africa"]";
string[] continents = (string[]) JsonConvert.Import(typeof(string[]),
jsonText);
以下是一个更有意思的转换示例:取得 RSS XML 源,使用 XmlSerializer 将其反序列化为 .NET 类型,然后使用 JsonConvert 将该对象转换为 JSON 文本(将 XML 格式的 RSS 有效转换为 JSON 文本):
XmlSerializer serializer = new XmlSerializer(typeof(RichSiteSummary));
RichSiteSummary news;
// Get the MSDN RSS feed and deserialize it...
using (XmlReader reader = XmlReader.Create("http://msdn.microsoft.com/rss.xml"))
{
news = (RichSiteSummary) serializer.Deserialize(reader);
}
// Export the RichSiteSummary object as JSON text, emitting the output to
Console.Out
using (JsonTextWriter writer = new JsonTextWriter(Console.Out))
{
JsonConvert.Export(news, writer);
}
注意 可在本文附带的示例中找到 RichSiteSummary 定义及其相关类型。
在 ASP.NET 中使用 JSON
我们已经介绍了如何在 JavaScript 中、以及通过 Jayrock 在 .NET Framework 中使用 JSON,接下来我们来看一个关于在何处以及如何应用这些知识的实际示例。考虑 ASP.NET 2.0 的客户端脚本回调功能,它可简化 web 浏览器向 ASP.NET 页面(或向页面中的特定控件)发出带外调用的过程。在典型的回调情形中,浏览器中的客户端脚本将数据打包并回送到 web 服务器,由服务器端方法进行某些处理。从服务器收到响应数据后,客户端会用它来更新浏览器显示。
注意 可在《MSDN 杂志》的文章“ASP.NET 2.0 中的脚本回调”中找到更多信息。
客户端回调情形中的难题在于,客户端和服务器只能来回运送一个字符串。因此,待交换的信息必须在发送前从本机内存中的表示形式转换为字符串,然后在收到后从字符串分析回本机内存中的表示形式。ASP.NET 2.0 中的客户端脚本回调功能不要求进行交换的数据使用特定字符串格式,也不提供在本机内存中和字符串表示之间进行转换的任何内置功能;开发人员可以依据所选择的数据交换格式来实现转换逻辑。
以下示例说明了如何在客户端脚本回调情形中将 JSON 用作数据交换格式。特别是,该示例由 ASP.NET 页面组成,此页面使用 Northwind 数据库中的数据,以下拉列表形式提供类别列表;选定类别中的产品则显示在项目符号列表中(请参见图 3)。每当客户端更改下拉列表时,将发生回调并传入唯一元素为选定 CategoryID 的数组。
注意 我们传入的是包含选定 CategoryID 作为其唯一元素的数组(而不仅仅是 CategoryID),因为 JSON 标准要求任何 JSON 文本都必须有对象或数组作为其根。当然,客户端不需要向服务器传递 JSON 文本,在此示例中本来可以只将选定的 CategoryID 作为字符串进行传递。但是,我们想要演示在回调的请求和响应消息中发送 JSON 文本。
Page_Load 事件处理程序的以下代码配置了 Categories DropDownList Web 控件,以便在它发生更改时调用 GetProductsForCategory 函数,并传递选定的下拉列表值。如果传入的下拉列表值大于零,此函数会初始化客户端脚本回调:
// Add client-side onchange event to drop-down list
Categories.Attributes["onchange"] = "Categories_onchange(this);";
// Generate the callback script
string callbackScript = ClientScript.GetCallbackEventReference(
/* control */ this,
/* argument */ "'[' + categoryID + ']'",
/* clientCallback */ "showProducts",
/* context */ "null");
// Add the Categories_onchange function
ClientScript.RegisterClientScriptBlock(GetType(),
"Categories_onchange", @"
function Categories_onchange(sender)
{
clearResults();
var categoryID = sender.value;
if (categoryID > 0)
{
" + callbackScript + @"
}
}", true);
ClientScriptManager 类中的 GetCallBackEventReference 方法用于生成可调用回调的 JavaScript 代码,具有以下签名:
public string GetCallbackEventReference (
Control control,
string argument,
string clientCallback,
string context,
)
argument 参数指定回调期间哪些数据会从客户端发送到 web 服务器,clientCallback 参数则指定在回调完成时调用的客户端函数的名称 (showProducts)。GetCallBackEventReference 方法调用会生成以下 JavaScript 代码,并将它添加到呈现的标记中:
WebForm_DoCallback('__Page','[' + categoryID +
']',showProducts,null,null,false)
'[' + categoryID + ']' 是回调期间传递到服务器的值(具有单个元素 categoryID 的数组),showProducts 则是回调返回时执行的 JavaScript 函数。
在服务器端,响应回调而执行的方法使用 Jayrock 的 JsonConvert 类来分析传入的 JSON 文本,并设置传出 JSON 文本的格式。特别值得注意的是,与选定类别相关的产品的名称都作为一个字符串数组进行检索并返回。
// Deserialize the JSON text into an array of integers int[] args = (int[]) JsonConvert.Import(typeof(int[]), eventArgument); // Read the selected CategoryID from the array int categoryID = args[0]; // Get products based on categoryID
NorthwindDataSet.ProductsRow[] rows = Northwind.Categories.FindByCategoryID(categoryID).GetProductsRows();// Load the names into a string array
string[] productNames = new string[rows.Length]; for (int i = 0; i < rows.Length; i++) { productNames[i] = rows[i].ProductName;}
// Serialize the string array as JSON text and return it to the client
return JsonConvert.ExportToString(productNames);
注意JsonConvert 类使用了两次——第一次是将 eventArgument 中的 JSON 文本转换为整数数组,第二次是将该字符串数组 productNames 转换为 JSON 文本,以返回到客户端。或者,我们也可以使用此处的 JsonReader 和 JsonWriter 类,但在涉及数据相对较小并易于映射到现有类型的情况下,JsonConvert 可以将同样的工作做得相当好。
数据从服务器端返回时,会调用依据 GetCallBackEventReference 方法指定的 JavaScript 函数,并传递返回值。此 JavaScript 方法 showProducts 以引用 <div> 元素 ProductOutput 开始。然后它会分析 JSON 响应,并动态添加无序列表,每个数组元素一个列表项。如果没有返回选定类别的产品,则会显示相应的消息。
function showProducts(arg, context)
{
// Dump the JSON text response from the server.
document.forms[0].JSONResponse.value = arg;
// Parse JSON text returned from callback.
var categoryProducts = eval("(" + arg + ")");
// Get a reference to the <div> ProductOutput.
var output = document.getElementById("ProductOutput");
// If no products for category, show message.
if (categoryProducts.length == 0)
{
output.appendChild(document.createTextNode("There are no products
for this category..."));
}
else
{
// There are products, display them in an unordered list.
var ul = document.createElement("ul");
for (var i = 0; i < categoryProducts.length; i++)
{
var product = categoryProducts[i];
var li = document.createElement("li");
li.appendChild(document.createTextNode(product));
ul.appendChild(li);
}
output.appendChild(ul);
}
}
结束语
JSON 是一种基于文本的轻型数据交换格式,以 JavaScript 编程语言中文字表示法的子集为基础。它提供了用于应用程序数据结构的简洁编码,通常用于一种或两种应用程序交换数据都可以使用 JavaScript 实现的情况,如 Ajax 风格的 web 应用程序。JSON 的吸引人之处在于简单易懂、便于采用和实现。JSON 对于已经熟悉 JavaScript 或同样支持丰富文字表示法的其他编程语言(如 Python 和 Ruby)的开发人员来说,实际上没有学习曲线。只需调用 eval 函数,即可完成对 JavaScript 代码中 JSON 文本的分析;使用 http://www.json.org/json.js 上提供的 json.js 脚本,即可轻松创建 JSON 文本。
有许许多多的库可帮助在所有主要的平台和框架中使用 JSON。本文中我们介绍了 Jayrock,它是用于在 .NET 应用程序中创建和分析 JSON 文本的开源库。Jayrock 可用于 ASP.NET 1.x、2.0 和 Mono 应用程序。ASP.NET AJAX 提供相似的 JSON 功能,但仅限于 ASP.NET 2.0 应用程序。
快乐编程!
Ajax 还是 AJAX?
Ajax 这个术语最初由 Jesse James Garrett 设想出来,用于描述 web 应用程序风格和创建高度交互的 web 应用程序所需的一组技术。从前,术语 Ajax 作为 Asynchronous JavaScript And XML(异步 JavaScript 和 XML)的缩写 AJAX 在 web 上传播。但随着时间的变化,人们意识到 AJAX 中的“X”无法形象地代表与后台 web 服务器进行通信时使用的基础数据格式,因为大多数实现都转向 JSON 作为更简单而有效的替代方案。因此,没有人提出如 AJAJ(有些拗口)之类的替代缩写,该缩写基本上已引退,而倾向于采用“Ajax 术语”而非“AJAX 缩写”。
在撰写本文时,希望看到混合及广泛使用的“AJAX”和“Ajax”具有同一含义。在本文中,我们坚持“Ajax 术语”。但是,如果商业产品要提供启用 Ajax 样式应用程序的框架,则需要使用缩写形式来区分与其名称类似的洗涤剂产品,避免可能的商标或法律争端。
参考资料
- ASP.NET AJAX
- 使用 AJAX 扩展进行客户端 Web 服务调用
- 核心 JavaScript 1.5 指南
- eval(expr) 函数
- Jayrock
- JSON.org
- ASP.NET 中的脚本回调
- RFC 4627
特别感谢
在本文提交给 MSDN 之前,有许多人自愿帮助我们校对文章,并对内容、语法和方向等方面提出了宝贵意见。审校过程的主要参与者有 Douglas Crockford、Eric Schönholzer 和 Milan Negovan。
作者简介
Atif Aziz 是 Skybow AG 的首席顾问,他的主要职责是帮助客户了解和构建 .NET 开发平台上的解决方案。Atif 通过会议演讲以及为技术出版物撰写文章,经常为微软开发人员社区出力。他是一位 INETA 发言人,并且是瑞士最大的 .NET 用户组的总裁。您可以通过他的网站 http://www.raboof.com/ 与他联系。
Scott Mitchell 是六部 ASP/ASP.NET 书籍的作者及 4GuysFromRolla.com 网站的创立者。自 1998 年以来,他一直从事 Microsoft web 技术方面的工作。Scott 是一位独立技术顾问、培训专家兼作家。可通过他的博客与其联系:http://ScottOnWriting.net。
文章来源:http://msdn2.microsoft.com/zh-cn/library/bb299886.aspx