Home > Articles > Programming

Programming Data: The Open Data Protocol

In this excerpt from his book, Chris Sells explains the Open Data Protocol (OData), which is the web-based equivalent of ODBC, OLEDB, ADO.NET and JDBC. In a web-based world, OData is the data access API you need to know.

This excerpt is from the Rough Cuts version of the book and may not represent the final version of this material.

From the Rough Cut

From the Rough Cut

Since the world has chosen to keep a large percentage of its data in structured format, whether it’s on a mainframe, a mini, a server farm or a PC, we have always needed standardized APIs for dealing with data in that format. If the data is relational, the Structured Query Language (SQL) provides a set of operations for querying data as well as updating it, but not all data is relational. Even data this is relational isn’t often exposed for use in processing SQL statements over intranets, let alone the world-wide internet. The structured data of the world is the crown jewels of our businesses, so as technology moves forward, so must data access technologies.

The Open Data Protocol (OData) is the web-based equivalent of ODBC, OLEDB, ADO.NET and JDBC. And while it’s relatively new, it’s mature enough to be implemented by IBM’s WebSphere, SQL Server Azure, Microsoft SharePoint and Microsoft’s “Dallas” information marketplace, to be the protocol of choice for the Open Government Data Initiative and is supported by .NET 4.0 via the WCF Data Services framework. In addition, it can be consumed by Excel’s PowerPivot, plain vanilla JavaScript and Microsoft’s own Visual Studio development tool.

In a web-based world, OData is the data access API you need to know.

Data Services on the Server

OData is defined by the “Atom Publishing Protocol: Data Services URI and Payload Extensions” specification (catchy title, eh?)[1], although you’ll be much happier starting with the OData overview documentation[2]. The OData specification defines how the Atom Publishing Protocol (AtomPub[3]) is used to standardize a typed, resource-oriented CRUD interface for manipulating data sources.

In previous chapters, we’ve been exposing data from our sample web site’s database as HTML. The HTML itself is generated inside the firewall of a trusted server using the TDS protocol (Tabular Data Stream) exposed by SQL Server. That works great if you’re on a computer with network access and permissions to the SQL Server, but the number of computers in the world that have such access to my ISP’s databases is very small. That’s fine for managing advertisers and ads, because that’s an administrative function, so I’m likely to have direct access to the SQL Server.

However, what if I wanted to provide access to the post data from my web site to be hosted elsewhere? Scraping that data out of my HTML makes it very hard for anyone to do that.

Or, what about the set of tinysells.com links you see in the footnotes of this book? Those are just entries in a database, but they don’t just come from me, they come from my co-authors, too. I could provide a secure web interface, but it gets complicated quickly if I want to enable things like grid-editing or bulk upload. I don’t want to build out such an interface based on the workflow preferences of my various co-authors; instead, I’d like to provide a secure API to that data from my web site so that they can write programs themselves. Further, I don’t want to provide them direct access to the site, because I don’t want to have to worry about them doing something bad, like “accidently” deleting tinysells.com links from competing books (my co-authors tend to be tricky!).

The easiest way to get your data on the web in a secure way with the OData protocol is using Windows Communication Foundation (WCF) Data Services built into .NET 4.0.

Defining Your Data

If we want to enable someone to type in a “tiny” URL, that means we want to take a URL of the form “http://yourdomain.com/linkId” into whatever the long form of the link is. To do that work, we need a mapping between id and link, like so:

public class TinyLink {
  public TinyLink() { IsActive = true; }
  public int ID { get; set; }
  public string Url { get; set; }
  public bool IsActive { get; set; }
}

I threw in an IsActive flag in case we want to stop the forwarding without actually deleting a link and set its default to true, but otherwise, this simple data model says it all. In keeping with the techniques that we learned from the Entity Framework chapters, let’s also build a simple container to expose links:

namespace TinyLinkAdmin.Models {
  public class TinyLinkContainer {
    public TinyLinkContainer() {
      // seeding in-memory store (for now)
      links.Add(new TinyLink() { ID = 1, Url = "http://bing.com" });
      links.Add(new TinyLink() { ID = 2, Url = "http://msdn.com" });
    }

    List<TinyLink> links = new List<TinyLink>();
    public IQueryable<TinyLink> Links     { get { return links.AsQueryable<TinyLink>(); } }
  }
}

We’re exposing our data using IQueryable, which you’ll recall from Chapter 3: The Entity Data Model: Entities. We’re using IQueryable instead of exposing our data as IList or IEnumerable because it allows us to be more efficient about stacking LINQ query methods like Where and Select for a variety of implementations (although ours is simple at the moment) and because it’ll make things work for us when we get to OData. The container is all we really need to implement the functionality we’re after:

using System.Linq;
using System.Web.Mvc;
using TinyLinkAdmin.Models;

namespace TinyLinkAdmin.Controllers {
  public class HomeController : Controller {
    public ActionResult Index(int? id) {
      using (var container = new TinyLinkContainer()) {
        TinyLink link =
          container.TinyLinks.SingleOrDefault(l => l.ID == id && l.IsActive);

        if (link != null) {
          Response.Redirect(link.Url, true);
        }
        else {
          Response.StatusCode = 404;
          Response.StatusDescription = "Not found";
        }
      }

      return null;
    }
  }
}

Since I like MVC 2, that’s what I’m using here by replacing the HomeController class’s Index method. All that’s happening is that when we surf to an URL of the form “http://myhost/1” is that the number is picked at the end of the URL and used to look up a link. If an active link is found with that ID, we redirect, otherwise we complain. Also, because I’m using MVC 2, I need to update the default routing entry in the Global.asax.cs file to match:

public class MvcApplication : System.Web.HttpApplication {

  public static void RegisterRoutes(RouteCollection routes) {
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        "Default", // Route name
        "{id}", // URL with parameters used to be "{controller}/{action}/{id}"
        new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
  }
  ...
}

Now, surfing to “http://myhost/1” takes you to bing.com, 2 takes you to msdn.com and anything else should result in a 404. So, our link forwarding service works nicely but the management of the data is currently non-existent; how does the data get into the container’s Links collection?

Exposing Your Data Model

We have several options when it comes to exposing our links data for management. If we wanted to, we could plunk it down into a SQL Server database and folks could have at it. However, database administrators don’t like to put their database directly onto the internet; something about reducing attack vectors (it all seems paranoid to me). Anyway, there are lots of other good reasons to put a layer between the internet-facing interface to your database besides security, including the ability to version the storage layer independently of the interface layer and the ability to optimize the interface layer for use on the internet.

In fact, it’s this last one that drives most of the thinking behind OData. If the optimized binary TDS, RPC, DCOM or CORBA protocols was friendly for folks building web pages, one of them might just have become the way we do things. However, since the language of the internet is largely text-based, a binary protocol doesn’t fly. The text-based nature of the desired solution has led to several protocols, including XML-RPC, SOAP-based web services, RSS and ATOM. They’re all text-based, but they all have issues.

For example, XML-RPC and SOAP can be used to expose all number of methods that do any number of things, but a quick study of popular public web services shows that the vast majority of those methods fall into the Create, Read, Update or Delete categories, e.g. GetCustomer or AddInvoice. On the other hand, RSS (along with various protocols like MetaWeblog) and ATOM (including its cousin AtomPub) provide for CRUD very nicely, but only really for blog content, not general-purpose entities like Customers or Invoices. Further, none of these protocols provide anything like a querying language that you don’t have to design all on your own or any data format except XML, e.g. JSON for the JavaScript guys building web pages.

It’s this set of criteria that led to OData:

  • Secure
  • Text-based
  • Optimized around CRUD, but supports other operations
  • Supports both XML and JSON data formats
  • Defines a standard query language
  • Based on standards like XML, JSON, TCP, HTTP, ATOM and AtomPub

And to top it all off, .NET provides several implementations all under the name of WCF Data Services, including .NET and Silverlight clients and a .NET server-side implementation.

So, without further ado, let’s see how to take our simple TinyLink data and expose it as an OData endpoint. To start, right-click on the project in the Solution Explorer again and choose Add | New Item, then WCF Data Service, choose a name you like and press Add (Figure 1).

Figure 1: Adding a Data Services endpoint

The generated .svc (service) file will have a C# or VB file that you need to edit, as I have here:

using System;
using System.Data.Services;
using System.Data.Services.Common;
using TinyLinkAdmin.Models;
using System.ServiceModel;

[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
namespace TinyLinkAdmin {
  public class TinyLinkService : DataService<TinyLinkContainer> {

    public static void InitializeService(DataServiceConfiguration config) {
      config.SetEntitySetAccessRule("TinyLinks", EntitySetRights.AllRead);
      config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
    }
  }
}

The DataService base class is provided by WCF Data Services and is parameterized around a container, which is really just any class that exposes one or more IQueryable properties (I told you IQueryable would be useful here). The InitializeService method is called once per service endpoint for as long as Data Services keeps the endpoint cached. This method is used to set per-endpoint properties, like which collection properties we’re exposing from the container class (just “TinyLinks” in our case), what kind of access we’d like to provide (we’re starting with read-only) and what version of the protocol we’d like to support (.NET 4 supports versions 2 and earlier of the OData protocol[4]).

When you first surf to the endpoint, http://.../TinyLinkService.svc, you’ll see Figure 2 (depending on your browser and its settings, you may have to use View Source to see the XML).

Figure 2: OData Service document

Figure 2 shows the OData service document, which is really an AtomPub service document.

Service Documents & Feeds

AtomPub is a set of services exposing one or more Atom feeds (which we’ll see directly) as “collections” and allowing the entries to be created, read, updated or deleted. Collections are gathered together into one or more “workspaces” (aka containers) which themselves are gathered into a service document, like Figure 2.

Notice the AtomPub namespace of “http://www.w3.org/2007/app” (“app” stands for “Atom Publishing Protocol”), which is where the definition of the server, workspace and collection elements come from. The title element is from the Atom namespace (http://www.w3.org/2005/Atom).

While it’s possible for the service document to describe more about each feed, like what MIME types each collection holds, the most important thing is the href attribute of the collection element, which defines the URL for the Atom feed document where entries from each collection can be found. In our example, we’re only exposing one collection, i.e. TinyLinks. If we follow the href of the TinyLinks collection from this AtomPub service document, we find an Atom feed, as Figure 3 shows[5]

Figure 3: OData Feed document

Following the href from the AtomPub collection entry in the service document leads to an Atom “feed,” which provides some per-feed data, like a title, a unique ID and the data the feed was last updated, along with a set of “entry” elements. In our case, each entry is one of our TinyLink objects represented in the Atom Syndication Format[6] (aka “Atom” specified as RFC 4287).

Atom was originally invented in the world of energetic bloggers as a way to keep readers up to date on who had something new to say. For example, the following Atom feed document example is pulled from sellsbrothers.com:

<!-- http://sellsbrothers.com/posts/?format=atom10 -->
<?xml version="1.0" encoding="utf-8"?>[7]
<feed xml:lang="en-us" xmlns="http://http://www.w3.org/2005/Atom">
  <title type="text">Marquee de Sells: Chris's insight outlet via ATOM 1.0</title>
  <id>uuid:96d1ea8a-465f-40c5-a482-3b7aecbda37f</id>
  <updated>2010-12-18T17:02:22-08:00</updated>
  ...
  <author>
    <name>Chris Sells</name>
    <uri>http://http://www.sellsbrothers.com</uri>
    <email>csells@sellsbrothers.com</email>
  </author>
  <entry>
    <id>http://http://www.sellsbrothers.com/posts/Details/12695</id>
    <title type="text">Windows Phone 7: Beating Expectations</title>
    <published>2010-12-18T17:02:22-08:00</published>
    <updated>2010-12-18T17:02:22-08:00</updated>
    <author>
      <name>Chris Sells</name>
    </author>
    <link rel="edit" href="http://http://www.sellsbrothers.com/posts/AtomDetails/12695" />
    <link rel="alternate" type="text/html"
       href="http://http://www.sellsbrothers.com/posts/Details/12695" />
    <content type="html">&lt;p&gt;Years ago, when I was...</content>
  </entry>
  <entry>
    <id>http://http://www.sellsbrothers.com/posts/Details/12694</id>
    <title type="text">If you want something from eBay, don't bid on it!</title>
    ...
  </entry>
  ...
</feed>

You’ll notice the Atom namespace declaration again at the top level. The top level feed element has a title, id, updated and author child elements, all of which are required. Other elements, e.g. link, category, subtitle, etc., can be provided if they are useful for the data entries contained. The feed element is really there to contain the set of entry child elements, which contain the important data.

The id associated with an entry must be unique for that feed. This allows clients to do things like notice new entries. The id must be in a specific format[8] and is universally unique[9]. The id elements are just identifiers and therefore you can only compare them – you can’t use them to get to anything. If you want to do that, you need to use the link elements.

In our case, we’ve got two link elements: one for updating or deleting (designated as rel="edit") and one for surfing to via a browser (designated as rel="alternate"). Or, to think of it another way, the two links are provided for programmatic use and human use, respectively.

Finally, the content element is where the real “payload” of each entry is. In the case of our blog, the content element is marked as type="html", which means exactly what you think it means. If you’re a blog author, this is a useful format in which to be able to publish in, as are the “xhtml” and “text” types that the content element of Atom also supports.

However, the type of a content element can be any arbitrary MIME type, which is what OData uses to pack arbitrary data into an entry instead of just blog-style content. Looking again at Figure 3, you’ll notice that the content element isn’t any of the blogging types, but rather type="application/xml":

<!-- http://localhost:1675/TinyLinkService.svc/TinyLinks -->
<feed xml:base="http://localhost:1675/TinyLinkService.svc/"
       xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
       xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
       xmlns="http://http://www.w3.org/2005/Atom">
  <title type="text">TinyLinks</title>
  <id>http://localhost:1675/TinyLinkService.svc/TinyLinks</id>
  <updated>2011-01-02T05:51:56Z</updated>
  <link rel="self" title="TinyLinks" href="TinyLinks" />
  <entry>
    <id>http://localhost:1675/TinyLinkService.svc/TinyLinks(1)</id>
    ...
    <link rel="edit" title="TinyLink" href="TinyLinks(1)" />
    <category term="TinyLinkAdmin.Models.TinyLink"
               scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />

    <content type="application/xml">
      <m:properties>
        <d:ID m:type="Edm.Int32">1</d:Id>
        <d:Url>http://bing.com</d:Url>
        <d:IsActive m:type="Edm.Boolean">true</d:IsActive>
      </m:properties>
    </content>

  </entry>
  ...
</feed>

This feed from our OData endpoint is an Atom feed which has been extended with two new namespace declarations from the OData specification. These namespaces are used to decorate the contents of the content element with the set of TinyLink properties, i.e. ID, Url and IsActive. That’s one of the chief differences between OData and Atom, i.e. that the OData content element can be used to hold any name/value data, not just blog content.

Further, notice that the two properties that aren’t strings are marked with a type property. Those types, based on the EDM type model discussed in previous chapters, allow a client to coerce the values into something besides strings for a type-safe programming experience.

Metadata

The types used to designate properties in the content element of an Atom entry are defined by the “Conceptual Schema Definition File Format” (CSDL) specification[10]. CSDL defines Microsoft’s Entity Data Model (EDM), which is also the data model of OData. That type data comes with each entity returned (as you just saw), but an OData endpoint can optionally provide metadata all up at a special link off the main service document called “$metadata”:

<!-- http://localhost:1675/TinyLinkService.svc/$metadata -->
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
  <edmx:DataServices
     xmlns:m=http://schemas.microsoft.com/ado/2007/08/dataservices/metadata
     m:DataServiceVersion="1.0">
    <Schema Namespace="TinyLinkAdmin.Models"
       xmlns:d=http://schemas.microsoft.com/ado/2007/08/dataservices
       xmlns:m=http://schemas.microsoft.com/ado/2007/08/dataservices/metadata
       xmlns="http://schemas.microsoft.com/ado/2006/04/edm">

      <EntityType Name="TinyLink">
        <Key>
          <PropertyRef Name="ID" />
        </Key>
        <Property Name="ID" Type="Edm.Int32" Nullable="false" />
        <Property Name="Url" Type="Edm.String" Nullable="true" />
        <Property Name="IsActive" Type="Edm.Boolean" Nullable="false" />
      </EntityType>
      <EntityContainer Name="TinyLinkContainer" m:IsDefaultEntityContainer="true">
        <EntitySet Name="TinyLinks" EntityType="TinyLinkAdmin.Models.TinyLink" />
      </EntityContainer>

    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

Notice that our .NET class TinyLink has been surfaced as an Entity Type in the OData data model, exposing the ID property as the key and providing name, type and nullability information for all three of the properties. Further, we’ve defined an EntityContainer called TinyLinkContainer which will be exposes as the “Default” collection from the AtomPub service document, which itself has a single collection named Links that it exposes.

All of this is, of course, mapped from our original class definition, as shown in Figure 4.

Figure 4: The mapping of the container class and collections to the OData service and feed documents

You’re not likely to ever use this metadata directly, but your tools will make use of it as we’ll see later. Anyway, it’s clear how the .NET types mapped to the EDM types, I think, but the fact that the ID property has become the key might be a bit confusing. As it turns out, OData requires each entity type to have a uniquely identifying key or it can’t do its job; specifically it can’t compose unique id elements that Atom and AtomPub require, nor can it produce the edit links (which we’ll get to directly). But, like the eternal question of how a thermos that keeps hot things hot and cold things cold, how does it know?

In this case, WCF Data Services is using “convention” (which is a fancy way of saying that it’s guessing). In this case, the convention is a property named “ID” or “<<ClassName>>ID”, e.g. TinyLinkID would work as well. Or, if you’d like to use “Id” (like I do), you can be explicit about it by decorating the TinyLink class with the DataServiceKey attribute:

using System.Data.Services.Common;

[DataServiceKey("Id")]
public class TinyLink {
  public TinyLink() { IsActive = true; }
  public int Id { get; set; }
  public string Link { get; set; }
  public bool IsActive { get; set; }
}

With the DataServiceKey attribute, you don’t have to follow the Data Services conventions if you’d rather not.

Associations

The other place where you’ll see the structure of your .NET classes affect the metadata and structure of your entries is when you’ve got a reference between types. For example, imagine that we’d like to keep track of who’s adding tiny links:

[DataServiceKey("Id")]
public class TinyUser {
  List<TinyLink> links = new List<TinyLink>();

  public int Id { get; set; }
  public string Name { get; set; }
  public ICollection<TinyLink> Links { get { return links; } }
}

Here we’ve added a new type, TinyUser, that has an association with a set of links, i.e. every user can have zero or more tiny links associated with him/her. Further, we’ll want to update the tiny link to be able to access the associated tiny user:

[DataServiceKey("Id")]
public class TinyLink {
  TinyUser user;
  public TinyLink() { IsActive = true; }

  public int Id { get; set; }
  public string Url { get; set; }
  public bool IsActive { get; set; }

  public TinyUser User {
    get { return user; }
    set {
      if (user != null) { user.Links.Remove(this); }
      user = value;
      if (user != null) { user.Links.Add(this); }
    }
  }
}

Now, whenever a link’s user is set, we reach up over to that user and add to the set of links associated with that user. Figure 5 shows how these fit together.

Figure 5: A simple data model

Updating our OData endpoint, we’ll want to expose the list of users from our container and set up some more fake data (although we’re gonna have to fix that pretty soon):

public class TinyLinkContainer {
  public TinyLinkContainer() {
    // still seeding in-memory store (for now)
    var user = new TinyUser() { Id = 1, Name = "Chris" };
    users.Add(user);

    links.Add(new TinyLink() { Id = 1, Url = "http://bing.com", User = user });
    links.Add(new TinyLink() { Id = 2, Url = "http://msdn.com", User = user });
  }

  List<TinyLink> links = new List<TinyLink>();
  List<TinyUser> users = new List<TinyUser>();
  public IQueryable<TinyLink> Links { get { return links.AsQueryable<TinyLink>(); } }
  public IQueryable<TinyUser> Users { get { return users.AsQueryable<TinyUser>(); } }
}

With this in place, we just have to expose the new entity set from our Data Services endpoint:
namespace TinyLinkAdmin {
  [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
  public class TinyLinkService : DataService<TinyLinkContainer> {
    
    public static void InitializeService(DataServiceConfiguration config) {
      config.SetEntitySetAccessRule("TinyLinks", EntitySetRights.AllRead);
      config.SetEntitySetAccessRule("TinyUsers", EntitySetRights.AllRead);
      config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
    }
  }
}

Now, as expected, our service document contains tiny users as well as links:
<!-- http://localhost:1675/TinyLinkService.svc -->
<service ...>
  <workspace>
    <atom:title>Default</atom:title>
    <collection href="TinyLinks">
      <atom:title>TinyLinks</atom:title>
    </collection>
    <collection href="TinyUsers">
      <atom:title>TinyUsers</atom:title>
    </collection>
  </workspace>
</service>

Following the TinyLinks href now looks a little different:

<!-- http://localhost:1675/TinyLinkService.svc/TinyLinks -->
<feed ...>
  ...
  <entry>
    <id>http://localhost:1675/TinyLinkService.svc/TinyLinks(1)</id>
    ...
    <link rel="edit" title="TinyLink" href="TinyLinks(1)" />
    <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/User"
           type="application/atom+xml;type=entry" title="User" href="TinyLinks(1)/User" />
    <category term="TinyLinkAdmin.Models.TinyLink"
               scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <content type="application/xml">
      <m:properties>
        <d:Id m:type="Edm.Int32">1</d:Id>
        <d:Url>http://bing.com</d:Url>
        <d:IsActive m:type="Edm.Boolean">true</d:IsActive>
      </m:properties>
    </content>
  </entry>
  ...
</feed>

In addition to the same stuff we saw before, each TinyLink has a new link element with a type of application/atom+xml;type=entry. This is how an association between a TinyLink and the TinyLink.User is exposed: via a link element with an href pointing to an associated TinyUser.

Entries

If we follow the “edit” link from the TinyLink entry in the feeds document, e.g. TinyLinks(1), or we following the “entry” link from the TinyLink entry, e.g. TinyLinks(1)/User, we’ll end up at a new kind of document; an entry document:

<!-- http://localhost:1675/TinyLinkService.svc/TinyLinks(1) -->
<entry xml:base="http://localhost:1675/TinyLinkService.svc/"
        xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
        xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
        xmlns="http://http://www.w3.org/2005/Atom">
  <id>http://localhost:1675/TinyLinkService.svc/TinyLinks(1)</id>
  <title type="text"></title>
  <updated>2011-01-02T06:11:26Z</updated>
  <author><name /></author>
  <link rel="edit" title="TinyLink" href="TinyLinks(1)" />
  <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/User"
         type="application/atom+xml;type=entry" title="User" href="TinyLinks(1)/User" />
  <category term="TinyLinkAdmin.Models.TinyLink"
             scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  <content type="application/xml">
    <m:properties>
      <d:Id m:type="Edm.Int32">1</d:Id>
      <d:Url>http://bing.com</d:Url>
      <d:IsActive m:type="Edm.Boolean">true</d:IsActive>
    </m:properties>
  </content>
</entry>

The “entry” document is just the same as an entry element in a feeds document except that it’s been promoted to the root. As you’ll recall, each entry is one of the objects from our data model and has a unique URL, e.g. TinyLinks(1). In addition to providing a way to get to exactly one object, it also provides the URL for processing update and delete operations via HTTP PUT and DELETE operations, which we’ll make use of soon.

More interesting for our current discussion, however, is how those association links are formed and for that, we need to talk about query navigation.

Query Navigation

Given the amount of time we’ve spent poring over XML documents defining services, feeds, entries and metadata, you will probably have noticed that the “edit” link of each entry is a relative URL that contains the name of the collection and the unique identifier from that collection, e.g.

<link rel="edit" title="TinyLink" href="TinyLinks(1)" />

The href value – TinyLinks(1) – is actually an OData query using the name of the collection combined with the ID of a specific entry to retrieve from that collection. This is just one tiny part of the query language that OData provides in the URL format. This is very different from AtomPub, which makes no statement about the format of the entry links, promising only that you can follow an “edit” link to the full data representation of the entry suitable for update and delete. This guarantee is also true in OData, but OData allows you to form URLs based on what you know about the underlying data from $metadata or through observation. For example, you can start at the top level service document and keep drilling as we’ve already seen:

  • TinyLinkService.svc yields a service document with a list of collections/feeds.
  • TinyLinkService.svc/TinyLinks yields a feed document with a list of entries.
  • TinyLinkService.svc/TinyLinks(1) yields a specific entry with a set of properties and associated entries.
  • TinyLinkService.svc/TinyLinks(1)/User yields the associated TinyUser entry for a specific TinyLink.
  • TinyLinkService.svc/TinyLinks(1)/User/Links yields the associated TinyLink entries for the TinyUser associated with a specific TinyLink.
  • etc.

It’s because we know the query language of OData that we can form links that follow the path as deep as we like.

Properties and More

The idea of the OData query language is to let the server do as much work for us as possible and to get back just the data we need. In addition to the ability to form query URLs to navigate between entities, there are query options for filtering, sorting, projection, etc. For example, if you’d like to drill into a specific property of an entry, you can do so with the name of that property:

<!-- http://localhost:1675/TinyLinkService.svc/TinyLinks(1)/Url -->
<Url xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices">http://bing.com</Url>

In this case, there are no Atom or AtomPub namespaces to be seen because this is just a document that contains the specific piece of information we’re after. You can go ever further and just ask for the value itself with the $value query option, e.g. TinyLinks(1)/Url/$value:

http://bing.com

Here, there’s no XML at all – just the raw value. Likewise, if you’d like a count of entities in a collection instead of the entities themselves, you can use $count, e.g. TinyLinks/$count:

2

In addition to $value and $count, OData defines other query operator. For example, $orderby allows you to order a query result by one or more properties in ascending or descending order:

http://.../TinyLinkService.svc/TinyLinks?$orderby=Url desc

In this case, we’re ordering by the Url property of each TinyLink in descending order. If we want to combine query options, we can do so using the standard “&” query string operator:

http://.../TinyLinks?$orderby=Url desc&$filter=IsActive eq true

Here we’re sorting by the Url property and filtering for active links using the IsActive property. Further, if you’d like to return the associated set of users that go with our sorted, filtered links, you can do so with the $expand operator:

http://.../TinyLinks?$orderby=Url desc&$filter=IsActive eq true&$expand=User

The $expand option is handy to reduce round-trips so that you don’t have to write programs that ask for a set of things and then ask for a related set. It works by allowing you to specify a set of related entries by their link title. When the resulting entries are returned, in addition to the links, you also get the data for the related entries as inline data:

<entry>
  <id>http://localhost:1675/TinyLinkService.svc/TinyLinks(2)</id>
  ...
  <link rel="edit" title="TinyLink" href="TinyLinks(2)" />
  <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/User"
         type="application/atom+xml;type=entry" title="User" href="TinyLinks(2)/User">
    <m:inline>
      <entry>
        <id>http://localhost:1675/TinyLinkService.svc/TinyUsers(1)</id>
        ...
        <link rel="edit" title="TinyUser" href="TinyUsers(1)" />
        <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Links"
               type="application/atom+xml;type=feed" title="Links"
               href="TinyUsers(1)/Links" />
        <category term="TinyLinkAdmin.Models.TinyUser"
           scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
        <content type="application/xml">
          <m:properties>
            <d:Id m:type="Edm.Int32">1</d:Id>
            <d:Name>Chris</d:Name>
          </m:properties>
        </content>
      </entry>
    </m:inline>
  </link>
  <category term="TinyLinkAdmin.Models.TinyLink"
             scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  <content type="application/xml">
    <m:properties>
      <d:Id m:type="Edm.Int32">2</d:Id>
      <d:Url>http://msdn.com</d:Url>
      <d:IsActive m:type="Edm.Boolean">true</d:IsActive>
    </m:properties>
  </content>
</entry>

Another query option you might be interested is in $links, which produces a “uri” document:

<!-- http://localhost:1675/TinyLinkService.svc/TinyLinks(1)/$links/User -->
<uri xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices">
   http://localhost:1676/TinyLinkService.svc/TinyUsers(1)</uri>

The $links option lets you pick an entry, like TinyLinks(1), and list the links that are associated with it via a specific property, e.g. User in our sample. These links are the associations from one entry to all of the related entries of a specific kind. If you like, you can add a link yourself, just like adding a new entry or setting a property value, although we’ll see a much simpler way to go about this later on.

Other query options include $format (like “atom”, the default, and “json”, which we’ll talk about later), $select and $skip. For a full description of the OData query language, as well as the specifications of the protocol itself, I highly recommend http://odata.org.


Footnotes

[1] The Open Data protocol specifications: http://odata.org/docs/[MC-APDSU].htm (http://tinysells.com/138).

[2] The OData protocol overview: http://odata.org/developers/protocols/overview.aspx (http://tinysells.com/139).

[3] AtomPub is defined by RFC 5023: http://tools.ietf.org/html/rfc5023 (http://tinysells.com/140).

[4] You can read about OData protocol versioning here: http://odata.org/developers/protocols/overview (http://tinysells.com/141).

[5] Some browsers try to pretty up anything that looks like an Atom feed, so you’ll have to turn that option off or use View Source to see the raw XML.

[6] Atom was inspired by RSS (the Really Simple Syndication protocol) as a more flexible and more rigorous standardized replacement. Most blog-based tools support both RSS and Atom, although AtomPub and OData only support Atom itself.

[7] Please assume the <?xml /> directive at the top of the XML documents you see in the rest of this article so we can avoid the extra noise.

[8] Specifically, it must be in the form of an IRI (International Resource Identifier), which is an international version of the URI (Uniform Resource Identifier), which is like a URL (Uniform Resource Locator) that you see in the address bar of your browser, but a URI doesn’t have to reference a resource on the network.

[9] These strings often look like the output of the uuidgen.exe tool available to Windows developers.

[10] http://odata.org/media/6652/[mc-csdl][1].htm (http://tinysells.com/142).


InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020