Introduction
Json has become a widespread data format in web applications, mainly with Ajax enriched web sites. Also "normal" Windows applications can communicate with such web services and consume Json data. I’ll show here a way to accomplish that, and some caveats.
Background
A short time ago, I started to learn how to use ReSTful we services. Some good examples on writing such services are available from "REST in Windows Communication Foundation (WCF)"(^). But I also wanted to consume the service from a C# application. After a lot of trial and error, I decided to find out how to consume an existing web service first. As an example, I chose the Panoramio data API, described at http://www.panoramio.com/api/data/api.html (^).
Naïve Approach
Visual Studio allows us to add a reference to a web service. Hence I clicked "Add Service Reference", entered the address http://www.panoramio.com/map/get_panoramas.php, and clicked "Go". After a while, I received the message: "An error occurred (details) while attempting to find services at..." The details were: "… The remote server returned an unexpected response: (412) Precondition failed. …"
I checked with a web browser if I could get some meta-information from the server by adding a "?wsdl" to above address, as I would do with a "normal" web service. The response was: "Bad Request".
After a lot of research on Google, it turns out that the discovery using Web Services Description Language (^) is not common with ReSTful services. And alternatives are not yet available, only being discussed.
Step by Step
What to do now after that failure? Well, we can break the task down to the following steps:
- Set up the URL with all parameters
- Send a
GET WebRequest
- Receive the Json formatted data
- Deserialize the object(s)
- Do something useful with the received data
Most of these steps do not cause any problems, it should look something like:
string url = …;
WebRequest request = WebRequest.Create(url);
WebResponse ws = request.GetResponse();
JsonSerializer jsonSerializer = new JsonSerializer(typeof(PanoramioData));
PanoramioData photos = (PanoramioData)jsonSerializer.Deserialze(ws.GetResponseStream());
But where do we find that Serializer? And how to define the classes ("PanoramioData
") for those objects?
Panoramio shows an excerpt of a sample response, but no definition of the data – we have to guess their types from the values we see. If we want to use a service where even a sample response is not shown, we can still enter the URL in a web browser, and save the response to a text file.
I did not read through the definitions of the Json format (see http://www.json.org/ (^)), it looked so easy to understand, and quickly wrote two classes: "public class PanoramioData
" for the root element, and "public class Photo
" for the collection of photos.
Now we need a (de-)serializer. With XML, the "XmlSerializer
" can be found in the "System.Xml.Serialization
" namespace. But there is no "System.Json
" namespace… The serializer we need here is called "DataContractJsonSerializer
" and resides in the "System.Runtime.Serialization.Json
" namespace. Though the constructor looks still common (we pass the type of the object to be deserialized), there is no "Deserialize()
" method. Instead, we must call "ReadObject()
". The resulting object is cast to our required type, and we can use it.
I tried that and caught an exception… What had happened? The "upload_date
" property is not a "DateTime
" – it is a string
! According to "An Introduction to JavaScript Object Notation (JSON) in JavaScript and .NET" (^), there is no real standard for formatting a DateTime
with Json, and the way Panoramio chose is not understood by the DataContractJsonSerializer
.
After that little change, the application really works! If we needed that property in the appropriate type, we would have to add an extra function (e.g. public DateTime GetUploadDate()
) which parses the string
.
The main lines of code are now:
string url = …;
WebRequest request = WebRequest.Create(url);
WebResponse ws = request.GetResponse();
DataContractJsonSerializer jsonSerializer =
new DataContractJsonSerializer(typeof(PanoramioData));
PanoramioData photos = (PanoramioData)jsonSerializer.ReadObject(ws.GetResponseStream());
which could actually be put into a static
method of the PanoramioData
class (i.e. public static PanoramioData FromURL(string url)
).
Alternative Serializer: JavaScriptSerializer
In the System.Web.Script.Serialization
namespace, there is another Serializer: JavaScriptSerializer
. We must add a reference to System.Web.Extensions.dll before we can use it. Again, usage is different:
It is instantiated with a parameterless constructor. The Deserialize
method requires a string
, i.e. the web response must be read into a StreamReader
and from there into a string
.
This serializer recognizes the Date
format used by Panoramio. But it does not use a [DataMember(Name="OtherName")]
attribute.
Class Generation Tools
Is that manual generation of the classes really the only option we have? Is there no such thing like the xsd tool for XML data with Json?
I found two very nice tools for creating classes from Json serialized data:
JSON C# Class Generator
This tool generates C# classes from a Json formatted string
. A helper class for deserialization is also created. The deserialization process is different from the processes described above.
Source code and binary release of the tool are available at http://jsonclassgenerator.codeplex.com/ (^).
json2csharp
This is a beautiful web site: http://json2csharp.com/ (^). Enter your Json string
– or even only the URL from where you received the Json data, and the tool generates the classes which are then used as described above.
Class Generator for VB.Net
I did not find such a tool on the web. For the sample project, I manually translated the C# code to VB.
Exception Handling
With ReSTful Web services, an exception should result in an http status code in the range of 400-499. The message body should then contain some more information on the exception.
With the Panoramio data API used in this example, the status code of 400 corresponds to this idea, but the message body is a full (html) web page instead of a Json formatted response. The citiesJson
request of geonames (^) still returns status code 200 (=OK) in case of errors, but a Json formatted error message.
request.GetResponse()
throws a WebException
for a status code of 400. This means that we have to catch the WebException
, then we can access its WebResponse
property, and get further information from it.
When the serializer (no matter if JavaScriptSerializer
or DataContractJsonSerializer
) is used for deserializing the object returned from the geonames API and there was an exception, it still returns a "valid" root object, only all its members being null
. Hence we have to check for that case, then try to deserialize the message to an exception status message.
Example Project
The example project contains the classes for the Panoramio data API and the geonames API, and a simple interface for setting the parameters. These parameters are not checked for usefulness and format, they are directly put into the URL – it is up to you to enter useful data.
On a second form, the results are shown: the photos are placed into PictureBoxes
(upon a double click the picture is opened in the default web browser), below the picture is a LinkLabel
to the author's page. Moving the mouse over a picture shows the photo title.
With the buttons "Place" and "Toponym", you can search for the nearest place or toponym nearby, if you have a valid user name for the geonames API.
The code shows the use of both serializers and their respective error handling.
Conclusion
Though consuming a ReSTful Json web service involves many more manual steps than "common" aspx web services, it is actually not really complicated (only terribly cumbersome) when you follow the steps above and use the tools.
The lack of a discovery method – and tools using it automatically – makes ReSTful services a bad choice for consumption. The methods for exception handling make it even worse.
And now try to find out how to do more complicated things like updating data on the server – the most important point in a real world application, but not available on publicly accessible web servers…
How nice would be a good toolset and some more standardization!