July 2012

Volume 27 Number 07

Data Points - Create and Consume JSON-Formatted OData

By Julie Lerman | July 2012

Julie LermanIn my June column, “Data Bind OData in Web Apps with Knockout.js” (msdn.microsoft.com/magazine/jj133816), I had some fun with Knockout.js and OData. As I learned how to data bind Knockout to the results of the OData service, I also found out more about OData and JSON than I had previously known. In this month’s column, I’ll shift my focus to consuming JSON from OData and creating JSON-friendly WCF Data Services.

I’ll show you how to consume JSON directly from JavaScript, how to take advantage of the OData JavaScript SDK (called datajs) and how to ensure your feed is flexible enough to support any style of client-side coding that will need JSON.

OData is a specification for ensuring that data service consumers can rely on a consistent experience from the services they consume. One of the rules of the spec is that OData results are output by default in ATOM format (which is a specific way of formatting XML), and that it can also output results in JSON format.

As the OData specification evolves, it introduces new features that you may want users of your service to leverage. After I discuss the various ways to consume and create JSON in OData feeds, I’ll make sure you understand how upcoming changes to the OData spec will affect OData and what you can do today to be prepared. I’ll begin by exposing a Customer with the following schema in my service:

public class Customer
{
  public int Id { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string AccountNumber { get; set; }
}

By default, a Customer result from my service would be ATOM data and look (as formatted by a browser) as shown in Figure 1.

Results as ATOM
Figure 1 Results as ATOM

The same result output as JSON would be:

{"d":[
{"__metadata":{"id":"https://localhost:43447/DataService.svc/Customers(1)",
               "uri":"https://localhost:43447/DataService.svc/Customers(1)",
               "type":"DataAccessMSDNJuly2012.Customer"},
  "Id":1,
  "FirstName":"Julie",
  "LastName":"Lerman",
  "AccountNumber":"A123"}
]}

If you’re working with the raw ATOM or JSON results, the format of the output can make a big difference in the ease of coding. For example, if you’re consuming the feed in JavaScript, it’s much easier to work directly with a JSON object than have to parse XML.

There are two ways to ensure your results come back in JSON format: You can either specify application/json in the request header or you can add an OData query parameter. When composing requests in Fiddler to test, it’s pretty easy to add a header, as shown in Figure 2.

Specifying JSON in the Request Header
Figure 2 Specifying JSON in the Request Header

You can also add a header in JQuery when you build your request, but thanks to a couple of other options, you don’t have to go to that length. One of these options is to use datajs, the JavaScript library for OData, which provides many other benefits for coding against OData as well. The other option is to use the OData query parameter, $format.

Get JSON by Default with datajs

You can download datajs from datajs.codeplex.com. As of the time of writing, the current version is 1.0.3.

If you’re using Visual Studio, you can add datajs directly to your project using the NuGet package manager. NuGet will add a scripts folder to your project and put the current version of datajs (datajs-1.0.3.js) and its related min script files in that folder.

The script library provides a class called OData that makes it easy to read and write from OData-compliant feeds, and provides other features. Although you can easily insert a header into the request via the OData.read function, it’s not necessary. By default, datajs will automatically add the header because if you’re using this library, it’s likely you want JSON.

Here’s some JavaScript that calls OData.read, passes in the Uri that represents my data service query, and specifies what to do with the results (in this case, I’m storing the results in a variable named customer):

<script src="Scripts/datajs-1.0.2.js" type="text/javascript"></script>
<script type="text/javascript" charset="utf-8">
  var customer;
  OData.read({ requestUri:"https://localhost:43447/DataService.svc/Customers?$top=1"
             },
             function (data, response) {
               customer = data.results[0];
             },
             function (err) {
               alert("Error occurred: " + err.message);
             });
</script>

As I debug through this script, I can see that the result of the query is an object, as shown in Figure 3.

Debug View of JSON Result
Figure 3 Debug View of JSON Result

With Fiddler running to capture HTTP traffic, I can see that the Accept header, specifying application/json, was part of this request.

If you’re a PHP developer, you should check out the related library, OData SDK for PHP (odataphp.codeplex.com). Just as the datajs library does for JavaScript developers, the PHP SDK ensures that the results are relevant. But in this case, it does so by requesting ATOM format and then materializing the resulting ATOM data into PHP objects.

Requesting JSON with the $format Parameter

Not everyone needing JSON will be using the datajs library. But that doesn’t necessarily mean you have to use the request header to ask for JSON. The OData spec also has a query parameter, $format, that accepts json as one of its values.

Here’s the modified Uri that requests JSON directly:

https://localhost:43447/DataService.svc/Customers(1)?$format=json

You can add the $format parameter to any query. Here I’m combining the format request with a filter:

https://localhost:43447/DataService.svc/Customers?$filter=FirstName eq 'Julie'&$format=json

Forcing a Data Service to Honor the $format Parameter

Although using this format parameter to request JSON is part of the OData specification, not every data service knows how to process the parameter. In fact, when you create a .NET WCF Data Service, it returns ATOM by default. So while the service will accept the $format=json parameter, it will still return ATOM in the response. But some developers on the OData team have shared a simple solution (available at archive.msdn.microsoft.com/DataServicesJSONP) you can add into your application that enables WCF Data Services to process the $format command and return JSON.

The extension is provided in the form of a class named JSONPSupportInspector and an attribute named JSONP­SupportBehavior. Both classes leverage logic from System.Component­Model. You can add the classes directly to your project or create an assembly from them to reference. Once your WCF Data Service has access to these classes, all you need to do is add the JSONPSupportBehavior attribute to the service class, like so:

[JSONPSupportBehavior]
public class DataService : DataService<SalesContext>
{
  public static void InitializeService(DataServiceConfiguration config)
  {
    config.SetEntitySetAccessRule("Customers", EntitySetRights.All);
    config.SetEntitySetAccessRule("Orders", EntitySetRights.All);
  }

With this attribute in place, my service will respond to the $format=json parameter when I add it into the query, and my service will return JSON.

I’m writing this column on the heels of the April 2012 release of WCF Data Services 5, which still requires that you explicitly add the JSONP support to your data services. If you’d like to see this wrapped into the WCF Data Services API in the future, you can add a vote to the team’s UserVoice Feature Suggestions at bit.ly/ImeTQt.

JSON Support and OData Versions

The OData specification is currently at version 2 and evolving. Work on version 3 is underway, with beta documentation already available at OData.org. By default, your WCF Data Service will target version 1. Unfortunately, the default won’t work if you want to use an OData version 2 feature, such as server-side paging, that’s available in WCF Data Services. I’ve added the SetEntitySetPageSize configuration to my service to demonstrate:

public static void InitializeService(DataServiceConfiguration config)
  {
    config.SetEntitySetAccessRule("Customers", EntitySetRights.All);
    config.SetEntitySetAccessRule("Orders", EntitySetRights.All);
    config.SetEntitySetPageSize("Customers", 3);
  }

With this version 2 feature in place, the service will throw an exception and tell you that you have to specify MaxProtocolVersion greater than version 1. Here’s the error associated with the SetEntitySetPageSize configuration:

“Server-side paging cannot be used when the MaxProtocol­Version of the data service is set to DataServiceProtocolVersion.V1.”

To correct the problem, it’s easy enough to set the version in the service code:

public static void InitializeService(DataSeviceConfiguration config)
  {
    config.SetEntitySetAccessRule("Customers", EntitySetRights.All);
    config.SetEntitySetAccessRule("Orders", EntitySetRights.All);
    config.SetEntitySetPageSize("Customers", 3);
    config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
  }

And, in this case, it’s still possible to request JSON output either in the request header or using the $format parameter in the Uri.

However, you may want to design your service to be forward-compatible, setting the MaxProtocolVersion to DataServiceProtocol­Version.V3. But in version 3, the JSON format will be changing to a more streamlined output format called JSON Light (odata.org/2012/04/json-light-sample-payloads/). This can be a big problem if your client application is not expecting the streamlined JSON format. From the perspective of the consuming application, it will appear that the request for JSON has been ignored because ATOM will be returned and not JSON.

That’s because your client must either explicitly request the more verbose format or specify the OData version to target. Without either of these rules satisfied, the service will return ATOM. Therefore, you must do one or the other of the required tasks in order to get JSON results when the service is set to version 3.

Here’s a request header modified to specifically request the verbose format of JSON:

Accept: application/json;odata=verbose

And here’s an example of the header with a specific request that the OData response use version 2 formatting:

Accept: application/json
MaxDataServiceVersion: 2.0

What if you’re using the $format=json parameter in the query instead of requesting JSON in the header? Again, the service won’t comprehend the request and you’ll get an error stating that the MIME type is not supported. In this case you must include the MaxDataServiceVersion in your request header.

However, if you’re using datajs, you don’t need to worry. Starting with version 1.0.3 , the library is aware of the need for the version information and supplies it in its requests. With this in place, your service is ready for OData version 3 and consumers have the flexibility to request whichever version format they like.

JSON in the OData Pipeline

Thanks to its streamlined format, JSON is an increasingly important format for developers. You may recall that even some of the new document databases I wrote about in the November 2011 Data Points column, “What the Heck are Document Databases?” (msdn.microsoft.com/magazine/hh547103), store data using JSON or some twist on JSON.

If you check out last month’s Data Points column, you’ll find examples of reading and writing to data services using JSON and Knockout.js.

As we move into an age when more and more applications are disconnected, you’ll appreciate the ability to consume OData as JSON (in either verbose or the more efficient new format). And if you’re creating services, your consumers will certainly be grateful if you can make it easy for them to access your data this way.


Julie Lerman is a Microsoft MVP, .NET mentor and consultant who lives in the hills of Vermont. You can find her presenting on data access and other Microsoft .NET topics at user groups and conferences around the world. She blogs at thedatafarm.com/blog and is the author of “Programming Entity Framework” (O’Reilly Media, 2010) and “Programming Entity Framework: Code First” (O’Reilly Media, 2011). Follow her on Twitter at twitter.com/julielerman.

Thanks to the following technical expert for reviewing this article: Alejandro Trigo