JSON
We’ve spent a great deal of time talking about .NET, both on the client and on the server-side. However, OData is a cross-platform, cross-language, standards-based protocol, so can be as easily consumed in a number of places. One of the best places to use OData from is JavaScript.
Browsers have provided something called the “XMLHttpRequest” object from quite a while now. It allows you to make an HTTP request to any HTTP endpoint, including a data service. By default, the results from an OData service come back as Atom, as we’ve seen, but if you set the HTTP Accept header to “application/json”, it will come back in the native data format of JavaScript: JavaScript Object Notation aka JSON[29]. In a modern browser that supports the native JSON parser, you can grab results from an OData call like so:
<h1>Magic Excuse Ball</h1> <button onclick="getRandomExcuses()">Get Excuses</button> <ol> <li id="excuse0">TBD</li> <li id="excuse1">TBD</li> <li id="excuse2">TBD</li> </ol> <script type="text/javascript"> function getRandomExcuses() { var xdr = new XMLHttpRequest(); xdr.open("GET", "/ExcuseService.svc/GetRandomExcuses?n=3", true); xdr.setRequestHeader("Accept", "application/json"); xdr.onreadystatechange = function () { if (xdr.readyState == 4 && xdr.status == 200) { var response = JSON.parse(xdr.responseText); for (var i = 0; i < 3; i++) { var li = document.getElementById("excuse" + i); li.innerText = response.d[i].Text; } } }; xdr.send(); } </script>
Here, we’re using the XMLHttpRequestion object to retrieve data from our sample data service. When the results come back, our readystatechange function will be called with the responseText set to JSON data, which looks like this:
{ "d" : [ { "__metadata": { "uri": "http://localhost:7475/ExcuseService.svc/Excuses(16)", "type": "MagicExcuseBall.Excuse" }, "Id": 16, "Text": "I Was Mugged" }, { "__metadata": { "uri": "http://localhost:7475/ExcuseService.svc/Excuses(2)", "type": "MagicExcuseBall.Excuse" }, "Id": 2, "Text": "Abducted by Aliens" }, { "__metadata": { "uri": "http://localhost:7475/ExcuseService.svc/Excuses(15)", "type": "MagicExcuseBall.Excuse" }, "Id": 15, "Text": "My Fish Died" } ] }
The data should be familiar, even if the format isn’t. The Data Services engine on the server has noticed that we’ve asked for JSON data and has provided it, augmented with the per-entry “__metadata”. This isn’t a normal practice in JSON-formatted data, but it is a handy way to provide OData extras and certainly works just fine from JavaScript.
The good news is that, because the data is formatted in JSON and because HTML5[30] defines a JSON object, JSON text is easy to turn to turn into native JavaScript objects, which means that digging into the data looks like JavaScript array and property accessors, i.e. this is the code that does the real work:
li.innerText = response.d[i].Text;
Our tiny sample HTML, when included in our excuses web application’s home page, looks like Figure 22.
Figure 22: HTML front-end to OData service endpoint
Because XMLHttpRequest doesn’t support “cross-site scripting” aka XSS[31], you can’t make calls from JavaScript in an HTML page to anywhere but back to the server from whence the page came. The solution is to use something called JSON-P[32], which stands for “JSON Padded” and does not have the same security restrictions. OData itself doesn’t specify support for JSON-P, but Data Services can be augmented to handle it[33]. Hopefully in future versions, Data Services will support JSONP out of the box.
Footnotes
[29] You can read about JSON at http://json.org.
[30] You can read the current draft of the HTML5 specification here: http://whatwg.org/specs/web-apps/current-work/multipage (http://tinysells.com/153).
[31] Cross-site scripting: http://en.wikipedia.org/wiki/Cross-site_scripting (http://tinysells.com/154).
[32] You can read about JSON-P here: http://json-p.org.
[33] JSONP and URL-controlled format support for ADO.NET Data Services: http://code.msdn.microsoft.com/DataServicesJSONP (http://tinysells.com/155).