March 2010

Volume 25 Number 03

Cutting Edge - ASP.NET Ajax Library and WCF Data Services

By Dino Esposito | March 2010

It’s been a few years since the Web industry praised the advent of AJAX as the beginning of a new era of programming prosperity, and now a powerful set of programming tools is finally available to Web developers: the ASP.NET Ajax Library and WCF Data Services. Developers can stop relying on the browser as a distinct runtime environment and perform from the Web a number of tricks that were previously only possible through smart clients.

The ability to place calls to a remote HTTP endpoint is a common feature that many applications take advantage of today. Such applications use Windows Communication Foundation (WCF) services to download JavaScript Object Notation (JSON) data streams, and parse that content into JavaScript objects that are then rendered into the current HTML Document Object Model (DOM). However, the WCF service on the server side—and the JavaScript code on the client side—work on different data types, forcing you to create two distinct object models.

You typically need a domain model on the server side, which is how your middle tier handles and represents entities. Entity Framework and LINQ to SQL are two excellent tools for designing your server-side object model, either from scratch or by inferring it from an existing database. At some point, though, you need to transfer this data to the client as the response of a WCF service call.

One of the most popular mantras of service-oriented architecture (SOA) maintains that you should always transfer data contracts, not classes, as part of decoupling the presentation and business tiers. So you need another completely distinct object model: the view model for the presentation.

A common problem is detecting and communicating to the server any changes to the data that have occurred on the client. Communicating only changes ensures that the least amount of data possible is transferred across the wire, and optimized data access operations can be performed against the database.

An end-to-end solution for data access and manipulation is therefore required. WCF Data Services (formerly ADO.NET Data Services) and the ASP.NET Ajax Library combine to deliver a comprehensive framework that allows you to download data, work on it and return updates to the server. In this article, I’ll look at the ASP.NET Ajax  JavaScript components for effective client-side data access.

WCF Data Services in a Nutshell

The key idea behind the cutting-edge, end-to-end data access solution in WCF Data Services is that you build a server-side data source and expose it through a special flavor of a WCF service: a WCF Data Service. (You’ll find an excellent introduction to WCF Data Services in the August 2008 edition of MSDN Magazine, available at msdn.microsoft.com/magazine/cc748663.)

You typically start by creating the business domain model using Entity Framework, then create a WCF service around it. I’ll be using the familiar Northwind database and working on a small subset of its tables: Customers and Orders.

First, you create a new class library project and add a new item of type ADO.NET Entity Data Model. Next, you compile the project and reference the assembly from within a new ASP.NET application. In the sample code, I use an ASP.NET MVC project. You merge any connection string settings with the web.config of the host Web application and add a new “ADO.NET Data Service” item to the Web project. (The old name is still being used in Visual Studio 2008 and in the beta 2 of Visual Studio 2010.) You now have a class library with the data source and a WCF Data Service that, hosted in the ASP.NET application, exposes the content to the clients.

In the simplest case, this is all the code you need to have in the WCF Data Service:

public class NorthwindService : DataService<NorthwindEntities>
{
   public static void InitializeService(IDataServiceConfiguration config)
   {
      config.SetEntitySetAccessRule("Customers", EntitySetRights.All);
    }
}

As you progress on your project, you’ll probably need to add more access rules on entity sets and also (why not?) support for additional service operations. A WCF Data Service is a plain WCF service that is consumed in a REST way. Nothing, therefore, prevents you from adding one or more new service operations, each representing a coarse-grained operation on the data, such as a complex query or a sophisticated update. Aside from that, as shown in the previous code, the service will provide client access to the Customers entity set, with no restrictions on operations. This means that you won’t be able to query for customers and orders, even though the Orders entity set does exist in the embedded entity model. A new access rule is required to enable client access to orders.

Until you add new REST methods to the WCF data service, the only operations allowed are generic create, read, update and delete (CRUD) operations expressed using an ad-hoc URI format. (See msdn.microsoft.com/data/cc668792 for more details on the syntax.) The URI format allows applications to query for entities, traverse the relationships between entities, and apply any changes. Each CRUD operation is mapped to a different HTTP verb: GET for queries, POST for insertions, PUT for updates and DELETE for deletions.

A client that gets a reference to a WCF Data Service receives a proxy class created by the datasvcutil.exe utility or transparently created by the Add Service Reference wizard in Visual Studio.

Invoking a WCF Data Service from a smart client platform couldn’t be easier, whether Silverlight, Windows or Windows Presentation Foundation (WPF). The same can be said for server-side binding to ASP.NET. What about Web clients based on JavaScript and AJAX?

Consuming Data Services via ASP.NET Ajax Library

In the ASP.NET Ajax Library, there are two JavaScript components related to WCF Data Services: OpenDataServiceProxy and OpenDataContext.

OpenDataContext is essentially designed for managing CRUD operations from within the Web client. It can be thought of as the JavaScript counterpart of the DataServiceContext class. Defined in the System.Data.Services.Client namespace, DataServiceContext represents the runtime context of the specified WCF Data Service. OpenDataContext tracks changes to the entities being used and can intelligently generate commands against the back-end service.

The OpenDataServiceProxy class is intended as a lightweight additional proxy for a WCF Data Service. It essentially manages read-only scenarios but can be used to invoke additional service operations exposed in the service. You initialize the OpenDataServiceProxy class as follows:

var proxy = new Sys.Data.OpenDataServiceProxy(url);

At this point, the class is up and running. You typically need to spend more time configuring an OpenDataContext object. As far as the connection to the service is concerned, though, it happens in a  similar way:

var dataContext = new Sys.Data.OpenDataContext();   
dataContext.set_serviceUri(url);

Both classes can be used as data providers for a DataView. Which one you use depends on what you want to do. If any CRUD activity has to happen via the service, you’re probably better off going with a proxy. If you have logic on the client and intend to perform a bunch of CRUD operations before you apply changes, then data context is preferable. Let’s focus on OpenDataContext.

Using the OpenDataContext Class

Here’s how to create and initialize an instance of the OpenDataContext class:

<script type="text/javascript">
  var dataContext;
    
  Sys.require([Sys.components.dataView, Sys.components.openDataContext]);

  Sys.onReady(function() {
    dataContext = Sys.create.openDataContext(
      {
         serviceUri: "/NorthwindService.svc",
         mergeOption: Sys.Data.MergeOption.appendOnly
      });

    });
</script>

Note the use of the Sys.require function to dynamically link only script files that serve the purpose of components being used. If you opt for the Sys.require approach, the only script file you need to link in the traditional way is start.js:

(see code <script src="../../Scripts/MicrosoftAjax/Start.js" 
  type="text/javascript">
</script>

All files you use, though, must be available on the server or referenced through the Microsoft Content Delivery Network (CDN).

Looking ahead to Figure 2, you can see that in the document’s ready event, you create a new instance of the OpenDataContext class. Note again the use of the newest abbreviated syntax to define code for common events and instantiate common objects. The factory of the OpenDataContext class receives the URL of the service and some additional settings. At this point, you’re ready to use the data context as the data provider to some DataView UI components in the page, as shown in Figure 1.

Figure 1 Using Data Context as a Data Provider

<table>
    <tr class="tableHeader">
        <td>ID</td>
        <td>Name</td>
        <td>Contact</td>
    </tr>
    <tbody sys:attach="dataview" 
           class="sys-template"
           dataview:dataprovider="{{ dataContext }}"
           dataview:fetchoperation="Customers"
           dataview:autofetch="true">
         <tr>
             <td>{{ CustomerID }}</td>
             <td>{{ CompanyName }}</td>
             <td>{{ ContactName }}</td> 
         </tr>
     </tbody>
</table>

An instance of the DataView component is created and used to populate the template it’s attached to. The DataView provides the glue code necessary to download data via the WCF Data Service and bind it to the HTML template. Where is the decision made about the data to be downloaded? In other words, how do you specify the query string for the data you want back?

The fetch operation property of the DataView component indicates the name of the service operation to be invoked. If the data provider is a plain proxy for the service, then the fetchoperation property takes the name of a public method on the service. If you instead go through the OpenDataContext class, the value for fetchoperation is expected to be a string that the runtime of the WCF Data Service can understand. It can be an expression like any of these:

Customers
Customers('ALFKI')
Customers('ALFKI')?$expand=Orders
Customers('ALFKI')?$expand=Orders&$orderBy=City

If you simply specify the name of a valid entity set, you’ll get the entire list of entities. Other keywords, such as $expand, $orderBy and $filter, allow you to include related entity sets (sort of an inner join), order on a property and filter returned entities based on a Boolean condition.

You can compose the query manually as a string, being respectful of the underlying URI format. Or you can use the built-in OpenDataQueryBuilder JavaScript object, as shown in Figure 2.

Figure 2 Using the AdoNetQueryBuilder Object

<script type="text/javascript">
    var dataContext;
    var queryObject;   

    Sys.require([Sys.components.dataView, 
                Sys.components.openDataContext]);

    Sys.onReady(function() {
        dataContext = Sys.create.openDataContext(
           {
               serviceUri: "/NorthwindService.svc",
               mergeOption: Sys.Data.MergeOption.appendOnly
           });
        queryObject = new Sys.Data.OpenDataQueryBuilder("Customers");
        queryObject.set_orderby("ContactName");    
        queryObject.set_filter("City eq " + "’London’");  
        queryObject.set_expand("Orders");      
    });
</script>

The query builder can be used to build complete URLs, or just the query part of the URL. In this case, the query builder gets the name of the entity set to query. It also offers a bunch of properties for setting any required expansion, filters and orders. The criteria set via the query builder object must then be serialized to an effective query string when that fetchoperation is set, as shown here:

<tbody sys:attach="dataview" 
       class="sys-template"
       dataview:dataprovider="{{ dataContext }}"
       dataview:fetchoperation="{{ queryObject.toString() }}"
       dataview:autofetch="true">

You use the toString method to extract the query string from the query builder. In the sample code, the resulting query string is

Customers?$expand=Orders&$filter="City eq 'London'"&$orderby=ContactName

The service returns a collection of composite objects that embed customer demographics plus some order information. Figure 3 shows the output.


Figure 3 Querying Data Using a WCF Data Service

The numbers in the last column indicate the number of orders that have been placed by the customer. Because of the $expand attribute in the query, the JSON data stream contains an array of orders. The HTML template refers to the length of the array and populates the column like this:

<td>{{ Orders.length }}</td>

Note that in order to successfully retrieve order information, you should first go back to the source code of the WCF Data Service and enable access to the Orders entity set:

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

Let’s see how it works if you have more ambitious plans and want to update data on the client before returning it to the service.

Dealing with Updates

Figure 4 shows the HTML template for a page fragment used to query for a customer. The user enters the ID, clicks the button and gets fresh, editable data.

Figure 4 Querying a Data Service

<div id="Demo2">
<table>
   <tr class="tableHeader"><td>Customer ID</td></tr>
   <tr><td>
     <%= Html.TextBox("CustomerID", "ALFKI") %>
     <input type="button" value="Load" onclick="doLoad()" />
   </td></tr>
 </table>
    
 <br /><br />
    
 <table sys:attach="dataview" id="Editor"
       class="sys-template"
       dataview:dataprovider="{{ dataContext }}"
       dataview:autofetch="false">
   <tr>
     <td class="caption">ID</td>
     <td>{{ CustomerID }}</td>
   </tr>
   <tr>
     <td class="caption">Company</td>
     <td>{{ CompanyName }}</td>
   </tr>    
   <tr>            
     <td class="caption">Address</td>
     <td>
        <%=Html.SysTextBox("Address", "{binding Address}")%></td>
   </tr>    
   <tr>       
     <td class="caption">City</td>         
     <td>{{ City }}</td>
    </tr>  
</table>
</div>

Data loading occurs on demand. Here’s the code that takes care of invoking the data service:

function doLoad() {
  var id = Sys.get("#CustomerID").value;

  // Prepare the query
  var queryObject = new Sys.Data.OpenDataQueryBuilder("Customers");
  queryObject.set_filter("CustomerID eq '" + id + "’");
  var command = queryObject.toString();

  // Set up the DataView
  var dataView = Sys.get("$Editor").component();
  dataView.set_fetchOperation(command);
  dataView.fetchData();

You first get the input data you need—specifically, the text the user typed in the input field. Next, you prepare the query using a new OpenDataQueryBuilder object. Finally, you instruct the DataView (in turn configured to use the WCF Data Service) to download data for the query.

Any data retrieved is displayed using ASP.NET Ajax Library live binding, which guarantees live updates to any involved JavaScript objects (see Figure 5).


Figure 5 Editing an Object Locally

The text box in which you edit the address of the customer is defined as:

<td class="caption">Address</td>
<td><%= Html.SysTextBox("Address", "{binding Address}") %></td>

In addition to the use of the {binding} expression, note the custom HTML helper being used in ASP.NET MVC. You might have a similar situation if you attempt to use live binding and AJAX templates in the context of a Web Forms application. So what’s the problem?

For data binding to work, involved attributes must be prefixed with the sys: namespace. Therefore, to bind some text to a text box, you need to ensure that the following HTML markup is emitted:

<input type="text" ... sys:value="{binding Address}" />

In both ASP.NET MVC and Web Forms, you can brilliantly solve the problem by entering HTML literals. Otherwise, you need an adapted version of the tools that the ASP.NET framework of choice offers for abstracting pieces of markup: HTML helpers or server controls. In particular, in ASP.NET MVC, you may resort to a custom HTML helper that emits the sys:value attribute, as shown in Figure 6.

Figure 6 A Custom HTML Helper

public static string SysTextBox(this HtmlHelper htmlHelper, 
    string name, 
    string value, 
   IDictionary<string, object> htmlAttributes)
{
    var builder = new TagBuilder("input");
    builder.MergeAttributes(htmlAttributes);
    builder.MergeAttribute("type", "text");
    builder.MergeAttribute("name", name, true);
    builder.MergeAttribute("id", name, true);
    builder.MergeAttribute("sys:value", value, true);
    return builder.ToString(TagRenderMode.SelfClosing);
}

Changes to the address of the displayed customer are recorded as they happen and are tracked by the data context object. Note that this is possible only if you use the data context object as the data provider of the DataView used for rendering. This is the additional work that the OpenDataContext object can do for you with respect to the aforementioned OpenDataServiceProxy object.

How can you save changes? To ensure that the modified delta of the downloaded data is served back to the data service, all you need to do is invoke the saveChanges method on the data context instance. Depending on the type of application you are building, though, you might want to add some extra layers of control. For example, you might want to add a “commit” button that first summarizes what’s going on and asks users to confirm that they want to save pending changes. Figure 7 shows the JavaScript code for such a commit button.

Figure 7 A Commit Button to Confirm Changes

function doCommit() {
    var pendingChanges = dataContext.get_hasChanges();
    if (pendingChanges !== true) {
        alert("No pending changes to save.");
        return;
    }

    var changes = dataContext.get_changes();
    var buffer = "";
    for (var i = 0; i < changes.length; i++) {
        ch = changes[i];

      // Function makeReadable just converts the action to readable text
        buffer += makeReadable(ch.action) +
                  " --> " + 
                  ch.item["Address"];
        buffer += "\n";
    }
    if (confirm(buffer))
        dataContext.saveChanges();
}

The function checks with the current data context to see if there are any pending changes. If so, it builds a summary of detected changes. The get_changes method on the data context returns an array of objects with information about the type of action (insert, remove or update) and the local object that was involved in the change. Figure 8 shows the dialog box that results from the preceding code when you attempt to commit pending changes.


Figure 8 Pending Changes Detected

It should be noted that every time you select a new customer, you lose the changes of the previous one. This is because the data context is emptied and refilled with other data. Persisting changes in some other object just doesn’t make sense—you’ll be rewriting a clone of the data context yourself.

The power of the client-side proxy of a WCF Data Service doesn’t show up really well through a single-object user interface. In the ASP.NET Ajax Library Beta kit, you’ll find an excellent way to test this feature: the ImageOrganizer example. However, I can give you the gist of what I mean by simply extending the present example a bit. Let’s suppose you have a master-detail view and can switch from one customer’s view to the next without leaving the page and without 
necessarily having to save changes. The download occurs only once (or periodically), and for the time it remains in memory, all changes your user interface allows are correctly tracked (see Figure 9).


Figure 9 Tracking Client-Side Changes

Insertions and Deletions

So far, I’ve only focused on updates. But what about insertions and deletions? These have some slight differences, and require a bit more work. First and foremost, you can’t rely on data binding to make changes to the underlying object being displayed. Your responsibility is to update the in-memory collection (or object) you received from the data context being used within the user interface. For inserts, you simply need to create a new local instance of the object that is good for display and add it to the bound collection. At this point, if your user interface is fully data-bound, it should be able to reflect the change. Next, you need to inform the data context that a new object has been added to an entity set and needs to be tracked for persistence. Here’s the typical code you need to attach to the JavaScript button that inserts an object:

// Create a new local object
var newCustomer = { ID: "DINOE", CompanyName: "...", ... };

// Add it to the collection used for data binding
dataView.get_data().add(newCustomer);

// Inform the data context object
dataContext.insertEntity(newCustomer, "Customers");

Removing an object is even simpler. You remove the object from the in-memory collection and call the removeEntity method on the data context.

var index = customerList.get_selectedIndex();
var customer = dataView.get_data()[index];
dataContext.removeEntity(customer);
imageData.remove(customer);

Avoid Confusion

The OpenDataContext and DataView objects work well together but should not be confused with each other. The OpenDataContext object represents the client-side proxy of a remote WCF Data Service. It’s a very special type of proxy, however. It implements the “Unit of work” pattern on the client side as it tracks changes being made to any entities it helped retrieve. The data context is an excellent data provider for the DataView component. The DataView component is exclusively concerned with rendering. It offers plug-ins for templates to invoke remote operations easily, but that’s just a facility for developers. No such CRUD and data management logic belong to the DataView.

This article didn’t delve into the intricacies of WCF Data Services and didn’t touch on aspects such as concurrency, lazy loading and security. It didn’t discuss data transfers, either. Hopefully, this article serves as an up-to-date summary of how to do some important things with the ASP.NET Ajax Library and WCF Data Services. The remainder is good fodder for future articles. Stay tuned!


 

Dino Esposito  is the author of the upcoming “Programming ASP.NET MVC” from Microsoft Press and is the co-author of “Microsoft .NET: Architecting Applications for the Enterprise” (Microsoft Press, 2008). Based in Italy, Esposito is a frequent speaker at industry events worldwide. Join his blog at weblogs.asp.net/despos.

Thanks to the following technical experts for reviewing this article:  Boris Rivers-Moore and Stephen Walther