65.9K
CodeProject 正在变化。 阅读更多。
Home

从 C# 或 VB 应用程序使用 Json WebService

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (25投票s)

2011年7月30日

CPOL

7分钟阅读

viewsIcon

338608

downloadIcon

23785

从 C# 应用程序使用 Json 网站服务的一些步骤

引言

JSON 已成为 Web 应用程序中广泛使用的数据格式,主要用于富含 Ajax 的网站。普通的 Windows 应用程序也可以与此类 Web 服务进行通信并使用 JSON 数据。我将在此介绍一种实现方法以及一些注意事项。

背景 

不久前,我开始学习如何使用 RESTful Web 服务。关于编写此类服务的优秀示例,可以参考“Windows Communication Foundation (WCF) 中的 REST”(^)。但我还想从 C# 应用程序中调用该服务。经过大量的试错,我决定先找出如何调用现有的 Web 服务。作为示例,我选择了 Panoramio 数据 API,其描述在 http://www.panoramio.com/api/data/api.html^)。

朴素的方法 

Visual Studio 允许我们添加 Web 服务引用。因此,我点击“添加服务引用”,输入地址 http://www.panoramio.com/map/get_panoramas.php,然后点击“转到”。过了一会儿,我收到了消息:“尝试查找服务时出错(详细信息)...” 详细信息是:“…远程服务器返回了意外的响应:(412) 前提条件失败。…”

我用浏览器检查是否可以通过在上述地址后面添加“?wsdl”来获取服务器的一些元信息,就像我会对“普通”Web 服务那样做。响应是:“错误请求”。

在 Google 上进行了大量研究后,发现使用 Web 服务描述语言^)进行发现,对于 RESTful 服务来说并不常见。而且替代方案尚未可用,仅在讨论中。

分步指南

失败后现在该怎么办?嗯,我们可以将任务分解为以下几个步骤:

  • 设置带有所有参数的 URL
  • 发送 `GET WebRequest`
  • 接收 JSON 格式的数据
  • 反序列化对象
  • 对接收到的数据做一些有用的事情

这些步骤中的大多数都不会引起任何问题,应该看起来是这样的

string url = …;
WebRequest request = WebRequest.Create(url);
WebResponse ws = request.GetResponse();
JsonSerializer jsonSerializer = new JsonSerializer(typeof(PanoramioData));
PanoramioData photos = (PanoramioData)jsonSerializer.Deserialze(ws.GetResponseStream());

但是反序列化器在哪里?如何定义这些对象的类(“PanoramioData”)?

Panoramio 展示了一个示例响应的摘录,但没有提供数据定义——我们必须从看到的值中猜测它们的类型。如果我们想使用甚至没有显示示例响应的服务,我们仍然可以在浏览器中输入 URL,并将响应保存到文本文件中。

我没有通读 JSON 格式的定义(参见 http://www.json.org/^)),它看起来很容易理解,并很快编写了两个类:“public class PanoramioData”用于根元素,以及“public class Photo”用于照片集合。

现在我们需要一个(反)序列化器。对于 XML,可以在“System.Xml.Serialization”命名空间中找到“XmlSerializer”。但没有“System.Json”命名空间… 我们需要的序列化器称为“DataContractJsonSerializer”,它位于“System.Runtime.Serialization.Json”命名空间中。尽管构造函数看起来仍然很常见(我们传入要反序列化的对象的类型),但没有“Deserialize()”方法。相反,我们必须调用“ReadObject()”。返回的对象被强制转换为我们需要的类型,然后我们就可以使用它了。

我尝试了,并捕获了一个异常… 发生了什么?“upload_date”属性不是“DateTime”——它是一个 `string`!根据“JavaScript 对象表示法 (JSON) 在 JavaScript 和 .NET 中的简介”(^),JSON 没有对 `DateTime` 格式化的真正标准,而 Panoramio 选择的方式 `DataContractJsonSerializer` 无法理解。

进行这个小改动后,应用程序真的可以工作了!如果我们还需要该属性为相应类型,我们就必须添加一个额外的函数(例如 `public DateTime GetUploadDate()`),该函数解析 `string`。

现在主要的几行代码是

string url = …;
WebRequest request = WebRequest.Create(url);
WebResponse ws = request.GetResponse();
DataContractJsonSerializer jsonSerializer = 
		new DataContractJsonSerializer(typeof(PanoramioData));
PanoramioData photos = (PanoramioData)jsonSerializer.ReadObject(ws.GetResponseStream());

这些实际上可以放在 `PanoramioData` 类的 `static` 方法中(即 `public static PanoramioData FromURL(string url)`)。

替代序列化器:JavaScriptSerializer

在 `System.Web.Script.Serialization` 命名空间中,还有一个序列化器:`JavaScriptSerializer`。我们必须先添加对 _System.Web.Extensions.dll_ 的引用才能使用它。同样,用法也不同:

它通过无参构造函数进行实例化。`Deserialize` 方法需要一个 `string`,也就是说,必须将 Web 响应读取到 `StreamReader` 中,然后从那里读取到 `string`。

此序列化器能识别 Panoramio 使用的 `Date` 格式。但它不使用 `[DataMember(Name="OtherName")]` 属性。

类生成工具

难道生成类如此手动真的是我们唯一的选择吗?对于 XML 数据,有 xsd 工具,难道 JSON 就没有这样的东西吗?

我找到了两个非常好的工具,用于从 JSON 序列化数据创建类:

JSON C# 类生成器

此工具可从 JSON 格式的 `string` 生成 C# 类。还会创建一个反序列化的辅助类。反序列化过程与上述过程不同。

该工具的源代码和二进制发行版可在 http://jsonclassgenerator.codeplex.com/^)上获得。

json2csharp

这是一个很棒的网站:http://json2csharp.com/^)。输入您的 JSON `string`——甚至只是接收 JSON 数据的 URL,该工具就会生成类,然后按照上述方式使用。

VB.Net 的类生成器

我没有在网上找到这样的工具。对于示例项目,我手动将 C# 代码翻译成了 VB。

异常处理

对于 RESTful Web 服务,异常应导致 HTTP 状态码在 400-499 范围内。消息正文应包含有关异常的更多信息。

在本文示例中使用的 Panoramio 数据 API 中,状态码 400 符合此想法,但消息正文是一个完整的(HTML)网页,而不是 JSON 格式的响应。来自 geonames^)的 `citiesJson` 请求在发生错误时仍然返回状态码 200 (=OK),但返回 JSON 格式的错误消息。

`request.GetResponse()` 对于状态码 400 会抛出 `WebException`。这意味着我们必须捕获 `WebException`,然后可以访问其 `WebResponse` 属性,并从中获取更多信息。

当使用序列化器(无论是 `JavaScriptSerializer` 还是 `DataContractJsonSerializer`)反序列化从 geonames API 返回的对象,并且发生异常时,它仍然返回一个“有效”的根对象,只有其所有成员都为 `null`。因此,我们必须检查这种情况,然后尝试将消息反序列化为异常状态消息。

示例项目

示例项目包含 Panoramio 数据 API 和 geonames API 的类,以及一个用于设置参数的简单界面。这些参数不检查其有用性和格式,直接放入 URL——这取决于您输入有用的数据。

在第二个窗体上,显示了结果:照片被放置在 `PictureBoxes` 中(双击图片会在默认浏览器中打开),图片下方是一个指向作者页面的 `LinkLabel`。将鼠标移到图片上会显示照片标题。

使用“Place”(地点)和“Toponym”(地名)按钮,如果您拥有 geonames API 的有效用户名,可以搜索附近最近的地点或地名。

代码展示了两种序列化器及其各自错误处理的使用。

结论

尽管使用 RESTful JSON Web 服务比“普通”aspx Web 服务需要更多手动步骤,但如果您遵循上述步骤并使用这些工具,它实际上并不算非常复杂(只是极其繁琐)。

缺乏发现方法——以及自动使用它的工具——使得 RESTful 服务成为一个糟糕的消费选择。异常处理的方法使其变得更糟。

现在试着找出如何做更复杂的事情,比如更新服务器上的数据——这是实际应用程序中最重要的一点,但并非在公开可访问的 Web 服务器上可用…

如果有一个好的工具集和更多的标准化,那该多好啊!

© . All rights reserved.