Using jQuery with OData and WCF Data Services
Create robust, data-driven AJAX apps with little hassle
August 2, 2010
Creating solutions that leverage AJAX data retrieval are relatively easy with the tools available today. However, creating solutions that depend on AJAX updates to server-side data stores remain time-consuming at best. Luckily, we have a few tools in our repertoire to make the job significantly easier: jQuery in the browser and OData (via WCF Data Services) on the server side. These frameworks not only help make developers’ lives easier, they do so in a web-friendly manner. In this article, I'll demonstrate how these technologies can help create robust, data-driven AJAX applications with very little hassle.
Microsoft created the OData protocol to easily expose and work with data in a web-friendly manner. OData achieves this level of web-friendliness by embracing ubiquitous web protocols such as HTTP, ATOM, and (most relevant to this article) JSON to expose Collections of Entries. At the core of these concepts is the referencing of data via Links (along with a new and intuitive querying and filtering language) to provide a very REST-ful, discoverable, and navigable way to present data over the web. In this way, OData is able to further enhance the value of its underlying protocols. OData leverages many of its underlying protocols’ features by heavily leveraging standard HTTP methods and response codes as meaningful and semantic communication features.
The easiest way to express OData’s power and simplicity is showing it in action. The code snippet in Figure 1 includes what should be relatively familiar jQuery code calling the root (feed) of an OData service to retrieve the various Collections it’s exposing.
$.getJSON( 'http://odata.netflix.com/Catalog?$format=json&$callback=?', function (data) { alert(data.d.EntitySets.join(', ')); });
The jQuery call is pretty simple: the $.getJSON() utility method makes it easy to provide jQuery with a URL to execute a JSON request against, as well as a JavaScript method to execute once the response returns with data. The request URL in this example is straightforward: it’s the base URL to the NetFlix OData catalog with the special OData $format keyword instructing the service to return JSON data back to the browser, as well as the WCF Data Service keyword $callback that instructs the service to trigger our callback method.
Figure 2 shows the raw data that would be returned from this request. Because the request executed against the root feed URL, the OData protocol dictates that the NetFlix service respond with a list of the Catalogs available in this feed.
Figure 2: A JSON response from an OData service
OData’s inherent discoverability is immediately apparent because—even though the service’s root URL is the only thing the client knows at this point—it has all the information it needs to navigate all the service’s exposed data.
Once the client retrieves a list of the Catalogs that the service exposes, it's able to append any one of those Catalog names to the root service URL to retrieve the contents (Entries) of that Catalog. The snippet in Figure 3 provides an example of this concatenation, effectively copying the original request, then adding /Titles to the end of the root service URL.
$.getJSON( netflixServiceUrl + '/Titles' + '?$format=json&$callback=?', function (data) { /* Do something with the data */ });
Things really start to get interesting in Figure 4, which shows that the response containing the Catalog’s Entries provides much more than just its Entries’ basic information.
Figure 4: The OData response with helpful metadata
Notice that each Entry has a __metadata property that includes a uri property indicating that particular Entry’s URI. It's this URI that uniquely identifies this Entry, providing clients with a direct reference to the Entry. Thus, retrieving the details for a particular Entry is as easy as making another GET request to its URI (as shown in Figure 5).
function loadEntry(entryUri) {$.getJSON( entryUri + '?$format=json&$callback=?', function (data) { var renderedEntry = $('#entry-info-template').render(data.d); $('#entry-info').html(renderedEntry); } );}
What’s more, if that Entry contains references to yet more Entries, they too will be exposed using this same method. Thus, the entire model is navigable using only GET requests.
Introducing jQuery Templating
In addition to the jQuery AJAX request to retrieve a specific Entry, Figure 5 also introduces the new jQuery templating extension. Upon successful completion of the AJAX request, the framework calls the templating plugin’s .render() method, executing token replacement and some simple evaluation functions against a given jQuery template object (for example, the template markup shown in Figure 6). The render method produces a jQuery object containing the rendered and transformed template, ready to be displayed in the browser.
One nice feature of the templating extension is that it inherently supports arrays of objects. Thus, the snippet $(‘#template’).render(\[ entry1, entry2, entry 3 \]) will return a collection of rendered markup objects, one for each of the Entry objects in the source array. Similar to the previous example, these two can be collectively appended to the DOM, making list population and other common operations against arrays incredibly painless.
Token Replacement
A templating engine’s primary purpose is replacing tokens in a template with data from a given source. As such, the jQuery templating engine provides straightforward syntax, making this replacement as easy as possible: $\{\[Property\]\}. For example, given the template in Figure 6, the $\{Name\}, $\{Rating\}, $\{Runtime\}, and $\{Synopsis\} placeholders would all be replaced with their correspondingly named properties (Name, Rating, Runtime, and Synopsis) on the model provided to the templating engine. The result of this transform would produce the markup shown in Figure 7.
Figure 7: Rendered HTML result of jQuery Templating
Expression Evaluation
For those times when simple token replacement just won’t cut it, the jQuery templating engine provides a concise syntax to execute arbitrary JavaScript expressions: \{\{= \[expression\] \}\}. For example, the following snippet—placed anywhere in the template markup—would produce a very familiar JavaScript alert box:
\{\{= alert('I love jQuery!') \}\}
Additionally, the template engine natively supports the each and if/else constructs to help make template markup more readable and easier to maintain. Figure 8 shows examples of the three constructs.
{{if (Rating == 'G' || Rating == 'PG')}} Child friendly{{else}} Keep your children away!{{/if}}Cast{{each(i,actor) Cast.results}} Name: ${actor.Name}{{/each}}
The OData querying examples shown so far have been relatively straightforward, mostly requesting arbitrary sets of data. Once an application achieves anything but the most basic complexity, the application developer must be able to leverage more advanced querying technique to effectively work with the application’s data. Luckily, OData shines in this area as well.
Paging
You may have noticed that the response object returned in Figure 4 included a __next property. This is one of the ways that OData supports paging and—as with everything else in OData—this functionality is provided in a very web-friendly manner (via yet another URI). To get the next page of information, simply make the same $.getJSON() call displayed in Figure 3, replacing the fabricated URI with the URI value held in the __next property. This will return the next result set in exactly the same format (including yet another __next property to continue to the following result set, and so on).
You might also have noticed that there is no __prev property representing the previous result set. This omission is quite understandable considering that the web is a stateless medium, so each server request is isolated from any preceding it. The absence of state leaves the previous request tracking to the application developer.
In addition to the __next property paging method through which the server controls the page size, OData also supports two more query options providing clients control over which Entries to retrieve: $skip and $top. Much like their T-SQL counterparts, $skip and $take each accept a numeric parameter indicating how many Entries in the entire Collection to skip and then how many to retrieve (respectively). For example, the query ?$skip=20&$take=10 would retrieve Entries 21-30 in a Collection containing at least 30 Entries.
Sorting
Yet one more query option that should be familiar to anyone who’s written T-SQL is $orderby, which (surprisingly enough) allows ordering Entities based on a sorting expression. By leveraging this query option, a set of query parameters including $orderby=Rating asc would order the items in the Collection by the Rating property in ascending order prior to executing any other filters. Alternatively, $orderby=Rating desc would order the Entries on the same Rating property in reverse (descending) order.
Filtering
OData’s powerful querying syntax includes one last category of query options used for filtering. This set of query options provide full control over what the OData service will deliver in a response, allowing applications to retrieve exactly what they need in order to get the job done. OData supports a slew of filtering options; too many to list in this article. The OData protocol documentation (www.odata.org/developers/protocols) contains an extensive list of filtering options.
What are WCF Data Services? Built on the WCF platform’s solid foundations, the WCF Data Services (formerly ADO.NET Data Services) framework is Microsoft’s OData protocol implementation that enables developers to expose their existing data to the web in the highly consumable and web-friendly manner that is OData. The WCF Data Services framework is the perfect complement to currently deployed technologies such as Entity Framework, LINQ-to-SQL, Azure data storage solutions, and many others. In fact, in many situations exposing these existing data sources requires no changes to existing implementations and minimal configuration.
Creating a WCF Data Service to easily expose OData feeds. Up to this point, the examples in this article have focused on the client-side consumption of OData feeds. If we shift our focus to the server side, we see that WCF Data Services and Visual Studio 2010 make exposing these feeds a breeze.
As an example, let’s create an online task-tracking website we’ll call “Forget the Milk,” an application with an Entity Framework data model exposed via a WCF Data Service. Starting from scratch, create a new ASP.NET Empty Web Application. In the root of this new application, add a new ADO.NET Entity Data model entitled ForgetTheMilk.edmx (as shown in Figure 9), and choose Empty Model from the wizard.
Figure 9: Using the wizard to create an Entity Framework Model
Next, add an Entity to the data model named Task that has a Name string property and an IsComplete boolean property (as shown in Figure 10).
Figure 10: The Task Entity
Finally, from the Entity Data Model designer’s context menu, select Generate Database from Model and follow the wizard to generate a database to hold our data. The data model is now created and available for exposure as an OData feed.
Now comes the interesting part: exposing the new data model via OData. Luckily, this is very simple: right-click on the project, select Add > New Item…, and choose the WCF Data Service item type (shown in Figure 11), naming it ForgetTheMilkService.svc.
Figure 11: Using the wizard to create a WCF Data Service
This should provide a code-behind file like the one shown in Figure 12; note the highlighted areas.
using System.Data.Services;using System.Data.Services.Common;namespace ForgetTheMilk{ public class ForgetTheMilk : DataService< /* TODO: put your data source class name here */ > { // This method is called only once to initialize service-wide policies. public static void InitializeService(DataServiceConfiguration config) { // TODO: set rules to indicate which entity sets and service operations // are visible, updatable, etc. // Examples: // config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead); // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All); config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2; } }}
To expose the Entity Model, edit these two highlighted areas, setting the DataService type to ForgetTheMilkContainer (the name of the Entity Data Model container created earlier), uncomment and update the config.SetEntitySetAccessRule to refer to the previously-created Entity Set (Tasks) as well as updating the EntitySetRights enum to All. After these changes, the code-behind should what's shown in Figure 13.
using System.Data.Services;namespace ForgetTheMilk{ public class ForgetTheMilk : DataService { public static void InitializeService(DataServiceConfiguration config) { config.SetEntitySetAccessRule("Tasks", EntitySetRights.All); } }}
At this point, right-clicking on the ForgetTheMilkService.svc service and choosing View in Browser should display something similar to what's shown in Figure 14. That’s all there is to creating OData feeds using WCF Data Services.
Figure 14: The XML OData service response
For simplicity’s sake, the example in this article exposes the Tasks database with All rights exposed. This means that any client accessing this feed has full rights to insert, update, and delete all the feed’s data. Although that is exactly what this example desires, you probably don't want to expose your data in this manner. Please choose the EntitySetRights value appropriate to your scenarios.
Using jQuery to Modify OData Collections. Now that we have created a feed with write permissions, it'is time to really put OData through its paces by modifying data on a server. Once again, true to form, OData does this in a responsible, web-friendly manner via standard POST, PUT, and DELETE HTTP calls.
Adding New Entries
Most web developers have at some point provided users a way to submit data to a web server through the use of the HTML
. The most popular way to submit this form data is with the POST method, indicating to the server that the information from this form should be inserted into a database. Similarly, OData leverages the HTTP POST verb to indicate that the information included in the request should be inserted into the Collection indicated by the URL to which the POST action is applied.
Luckily, jQuery has excellent support for sending asynchronous POST requests. Figure 15 shows an example jQuery POST request. In this screenshot, jQuery first collects the data to send from the form elements, collects it in a JSON object, and sends this object as a payload to the URI of the Collection to which the new Entry should be added (ForgetTheMilk.svc/Tasks).
$('.save-button').click(function () { var name = $('.task-name').val();var task = { Name: name, IsComplete: false };$.ajax({ type: "POST", dataType: "json", contentType: "application/json", url: 'ForgetTheMilk.svc/Tasks', data: JSON.stringify(task) });});
In Figure 16, you can see that the server successfully creates the new Entry in the database, letting the client know by responding with an HTTP status code 201 (the status code for Created). This same server response also includes the newly created Entry in JSON format, including the Entry’s URI for future reference.
Figure 16: Server response to an OData POST request
Notice the usage of JSON.stringify to ensure that the data being sent has been properly escaped into correct JSON format. The server will most likely reject requests including improperly encoded JSON data. This method is part of the json2.js library written and maintained by Douglas Crockford.
Updating Existing Entries
Now that you’ve seen examples of retrieving existing OData Entries and of creating new Entries using jQuery, the common theme of HTTP methods, HTTP status codes, and URIs should be readily apparent. So it should come as no surprise that yet another HTTP method (PUT) is used to perform updates to existing Entries. In fact, the jQuery code used to do so is almost exactly like the POST method shown in the previous example. The only sections that change are the HTTP method used (change the POST to PUT) and change the URL to the URI of the Entry to update.
To see this in action, assume that immediately after creating the new Task in the previous example, you realize that the Task’s Name property was incorrect and needs to be updated. To do so, you’d execute a snippet such as the one shown in Figure 17.
$('.update-button').click(function () { var name = $('.task-name').val();var task = { Name: name };$.ajax({ type: "PUT", dataType: "json", contentType: "application/json", url: 'ForgetTheMilk.svc/Tasks(6)', data: JSON.stringify(task) });});
Notice that the snippet recreates a new JSON object containing only the fields to update, then executes the AJAX PUT request to the URI of the existing Task (ForgetTheMilk.svc/Tasks(6)). This time, the server response (captured in Figure 18) is much less extensive: it merely contains an HTTP status code 204 (No Content) to indicate that the server did indeed successfully process the update and is appropriately unaccompanied by any kind of response content (just as the status code promised!).
Figure 18: Server response to an OData PUT request
This is a good thing because it cuts down on unnecessary network chatter. In this scenario, the server’s only responsibilities are to update the Task and notify the requestor as to whether it failed or succeeded at doing so—with the least amount of communication possible. And—as a database query quickly verifies—the Task’s Name property was indeed successfully updated.
Removing Existing Entries
Now you’ve seen jQuery retrieve OData Entries via the GET HTTP method, create new Entries via the POST HTTP method, and update that same Entry via the PUT HTTP method. Given the knowledge that the HTTP protocol includes a DELETE method, can you guess how to delete an OData Entry? That’s right—simply send an HTTP DELETE request to the Entry’s URI. To see this in action, look at Figure 19, which includes what should be a recognizable AJAX DELETE request to the Task created in the original POST example.
$('.delete-button').click(function () { $.ajax({ type: "DELETE", url: 'ForgetTheMilk.svc/Tasks(6)' });});
Because this request neither sends any data to the server nor accepts any in response, for clarity’s sake it takes the liberty of removing all the extraneous AJAX request properties (dataType, contentType, and data) and specifies only the required URI and the HTTP method. The server response from the DELETE request is just as interesting as the response to a PUT request. In fact, Figure 20 shows that it's almost exactly alike (an HTTP status code 204 with no response content), except for the request type.
Figure 20: Server response to an OData DELETE request
This is just another scenario in which the server’s responsibilities are limited to processing the DELETE and indicating that it processed the request; thus, the server again responds with a bare minimum of information.
Microsoft’s new OData platform is a powerful way to manage data in a web-friendly manner. When coupled with AJAX-enabled jQuery applications, this power is easily accessible to web developers. Because OData embraces existing technologies, the learning curve is surprisingly low, allowing web developers to get advanced applications up-and-running with a minimal investment of time and effort.
Read more about:
MicrosoftAbout the Author
You May Also Like