1. Problem
Your Silverlight application needs to exchange JavaScript Object Notation (JSON) messages with an HTTP endpoint.
2. Solution
Use the HttpWebRequest/HttpWebResponse pair of types to exchange JSON messages with the HTTP endpoint. Use DataContractJsonSerializer to serialize/deserialize JSON data.
3. How It Works
The techniques used in this recipe are largely .
3.1. JSON
JSON is a very
lightweight format that can be applied to data exchanged on the wire
between computers. JSON is textual, like XML, and is based on a subset
of the JavaScript programming language, borrowing those portions of the
JavaScript syntax that are needed to represent data structures and
collections. JSON has gained a lot of popularity of late as a
serialization format of choice, especially for Ajax web applications,
where objects and collections need to be serialized to and from
JavaScript code. For more on the format specification, and a more
detailed introduction, visit www.json.org.
Listing 1 shows the JSON serialized representation of an instance of the ProductDetail class (which we use in the past recipes and continue to use here), for ProductId of value 680.
Listing 1. JSON representation of a ProductDetail instance
{"Class":"H", "Color":"Black", "DaysToManufacture":1, "DiscontinuedDate":"", "FinishedGoodsFlag":"True", "MakeFlag":"True", "ProductId":680, "ProductLine":"R ", "ProductNumber":"FR-R92B-58", "ReorderPoint":375, "SafetyStockLevel":500, "Size":null, "StandardCost":1059.31, "Style":"U ", "Weight":null}
|
It is easy to note that the
serialized format does not contain any details about the actual CLR type
or even the data types of the properties being serialized—it is a
collection of named properties and their values. It is the job of an
appropriate JSON serializer on both ends of the wire to take this
textual format and convert it into an instance of a class.
Part of JSON's popularity is
based on the fact that it is much more compact than XML in most
cases—although both are textual, JSON is less verbose. However, JSON has
some disadvantages as well. It was designed to be a serialization
format and therefore is not meant to be used in a stand-alone way. In
other words, the serialized textual format shown earlier is not much use
until you turn it back into an object. XML, on the other hand, enjoys
facilities that can be used to operate on the XML itself, such as XPath,
XQuery, XSL transformations, and LINQ to XML, whereby the serialized
XML can be useful to you without having to be deserialized into an
object structure.
If you must choose formats,
and you have control on both ends of the wire, JSON is preferable if you
never intend to operate directly on the serialized form; XML is
preferable otherwise. If you do not have control on both ends, the
choice may already be made for you.
3.2. Using the DataContractJsonSerializer Type
Silverlight provides the DataContractJsonSerializer type in System.Runtime.Serialization.Json. It lets you serialize or deserialize JSON data to and from CLR types decorated with the DataContract attribute.
To use DataContractSerializer, create a new instance of it, and initialize it with the type of the CLR object you want to serialize:
DataContractJsonSerializer jsonSer = new
DataContractJsonSerializer(typeof(List<ProductHeader>));
To deserialize some JSON data, pass in a reference to the stream containing the JSON data to the ReadObject() method, and cast the returned object to the desired type:
List<ProductHeader> productList =
jsonSer.ReadObject(jsonStream ) as List<ProductHeader>;
DataContractJsonSerializer supports object trees with nested objects, and ReadObject() returns to you the object at the root of the tree.
To serialize objects to JSON, use the WriteObject() method, passing in a destination stream, and the root object in the object tree that you want serialized:
jsonSer.WriteObject(jsonStream,rootObject);
3.3. Configuring WCF to Use JSON
We continue to use the WCF
service from the previous recipes, but let's configure it this time to
use JSON formatting on the messages exchanged.
WebGetAttribute and WebInvokeAttribute expose two properties that let you control this formatting: RequestFormat and ResponseFormat. Both properties are of type WebMessageFormat, which is an enum. You need to set RequestFormat to WebMessageFormat.Json to enable the service to accept JSON-formatted requests; set ResponseFormat identically to send JSON-formatted responses from the service.
You must also configure
your WCF service endpoint to specify the use of a JSON serializer. To do
this, you apply a custom behavior to the endpoint. Define a behavior
named ScriptBehavior; the webHttp element in it enforces the use of JSON:
<endpointBehaviors>
<behavior name="ScriptBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
You can apply the behavior to an endpoint as shown here:
<endpoint address="" behaviorConfiguration="ScriptBehavior" binding="webHttpBinding"
contract="IProductManager" />
4. The Code
Listing 2 shows the service contract modified to use JSON.
Listing 2. Service contract modified for JSON in ServiceContract.cs
using System.Collections.Generic; using System.ServiceModel; using System.ServiceModel.Web;
namespace Recipe7_3.ProductsDataJSONService { [ServiceContract] public interface IProductManager { [OperationContract] [WebGet(ResponseFormat = WebMessageFormat.Json)] List<ProductHeader> GetProductHeaders();
[OperationContract] [WebInvoke(RequestFormat = WebMessageFormat.Json)] void UpdateProductHeaders(List<ProductHeader> Updates);
[OperationContract] [WebGet(ResponseFormat = WebMessageFormat.Json)] ProductDetail GetProductDetail(ushort ProductId);
[OperationContract] [WebInvoke(RequestFormat = WebMessageFormat.Json)] void UpdateProductDetail(ProductDetail Update);
} }
|
You specify the RequestFormat and the ResponseFormat properties of the WebGet and WebInvoke attributes to use JSON. In this case, in the methods GetProductHeaders() and GetProductDetail(), you only need to specify ResponseFormat,
because the query is performed using a GET. In case of the update
methods, you do not expect a response back from the POST, so only the RequestFormat
is set to use JSON; thus the data sent to the service is formatted
appropriately. However, when using POST, you may encounter scenarios
where you are both sending and receiving data, in which case you need to
specify both properties in WebInvokeAttribute.
Listing 3. Codebehind for JSON serialization and deserialization in MainPage.xaml.cs
private List<ProductHeader> DeserializeProductHeaders(Stream HeaderJson) { //create and initialize a new DataContractJsonSerializer DataContractJsonSerializer jsonSer = new DataContractJsonSerializer(typeof(List<ProductHeader>)); //Deserialize - root object returned and cast List<ProductHeader> ProductList = jsonSer.ReadObject(HeaderJson) as List<ProductHeader>; return ProductList; } private void OnProdHdrUpdReqStreamAcquired(IAsyncResult target) { HttpWebRequest webReq = target.AsyncState as HttpWebRequest; Stream stmUpdates = webReq.EndGetRequestStream(target); Dispatcher.BeginInvoke(new Action(delegate { List<ProductHeader> AllItems = ProductHeaderDataGrid.ItemsSource as List<ProductHeader>;
List<ProductHeader> UpdateList = new List<ProductHeader> ( from Prod in AllItems where Prod.Dirty == true select Prod ); //create and initialize a DataContractJsonSerializer DataContractJsonSerializer jsonSer = new DataContractJsonSerializer(typeof(List<ProductHeader>)); //write object tree out to the stream jsonSer.WriteObject(stmUpdates, UpdateList);
stmUpdates.Close();
webReq.BeginGetResponse( new AsyncCallback(OnProductHeadersUpdateCompleted), webReq); })); } private ProductDetail DeserializeProductDetails(Stream DetailJson) {
DataContractJsonSerializer jsonSer = new DataContractJsonSerializer(typeof(ProductDetail)); ProductDetail Detail = jsonSer.ReadObject(DetailJson) as ProductDetail; return Detail; } private void OnProductDetailUpdateRequestStreamAcquired(IAsyncResult target) { HttpWebRequest webReq = (target.AsyncState as object[])[0] as HttpWebRequest; Stream stmUpdates = webReq.EndGetRequestStream(target);
ProductDetail Detail = (target.AsyncState as object[])[1] as ProductDetail;
DataContractJsonSerializer jsonSer = new DataContractJsonSerializer(typeof(ProductDetail)); jsonSer.WriteObject(stmUpdates, Detail); stmUpdates.Close(); webReq.BeginGetResponse( new AsyncCallback(OnProductDetailsUpdateCompleted), webReq); }
|
The DeserializeProductHeaders() method uses a DataContractJsonSerializer to deserialize JSON data from a stream to a List<ProductHeader>. You create a new instance of DataContractJsonSerializer, passing in the targeted CLR type. You then call the ReadObject() method, passing in the stream containing the serialized object tree. This deserializes the object and returns it to you as an Object,
which you must cast appropriately. Note that if an object tree is
serialized into the stream, on deserialization the entire tree is
reconstructed and the root object of the tree is returned to you.
In OnProdHdrUpdReqStreamAcquired(), you switch to the main thread using Dispatcher.Invoke(). Prior to sending an update to a ProductHeader, you serialize a List<ProductHeader> containing the updates to a stream as JSON. After you filter out the collection of ProductHeaders containing the updates using LINQ, you again use a newly constructed DataContractJsonSerializer instance, this time initializing it with the type of List<ProductHeader>. You then call the WriteObject()List<ProductHeader> instance containing the updates that you want to serialize. method on it, passing in the target stream and the
DeserializeProductDetails() and OnProductDetailUpdateRequestStreamAcquired() are implemented following the same pattern and should be self-explanatory.
Also note that while
sending POST requests that contain JSON-formatted data, you must set the
MIME type appropriately by setting the ContentType property on the request to the string "application/json" like so:
WebRequest webReq = HttpWebRequest.Create
(new Uri(string.Format("{0}/UpdateProductHeaders",ServiceUri)));
webReq.Method = "POST";
webReq.ContentType = "application/json";