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






4.81/5 (25投票s)
从 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 服务器上可用…
如果有一个好的工具集和更多的标准化,那该多好啊!