Programming Data: The Open Data Protocol
This excerpt is from the Rough Cuts version of the book and may not represent the final version of this material.
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"><p>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).