August 2010
Volume 25 Number 08
OData and AtomPub - Building an AtomPub Server Using WCF Data Services
By Chris Sells | August 2010
If you’re not familiar with it, the Open Data Protocol (OData) is a thing of beauty. OData (described in detail at odata.org) builds on the HTTP-based goodness of Atom for publishing data; AtomPub for creating, updating and deleting data; and the Microsoft Entity Data Model (EDM) for defining the types of data.
If you have a JavaScript client, you can get the data back directly in JSON instead of Atom format, and if you’ve got something else—including Excel, the .Microsoft NET Framework, PHP, AJAX and more—there are client libraries for forming OData requests and consuming OData responses. If you’re using the .NET Framework on the server side, Microsoft also provides an easy-to-use library called WCF Data Services for exposing .NET Framework types or databases supported by the Microsoft Entity Framework as OData sources. This makes it easy to expose your data over the Internet in an HTTP- and standards-based way.
Having said all that, there are some things that you might like to do with OData that aren’t quite part of the out-of-box experience, such as integrating OData with existing Atom- and AtomPub-based readers and writers. That’s what we’re going to experiment with.
A Simple Blog
As an example, let’s imagine that I’m building a simple blogging system (and, in fact, this work is based on me rewriting the content management system on sellsbrothers.com). I’m a big fan of the model-first support in Visual Studio 2010, so I created an ASP.NET MVC 2.0 project, added an ADO.NET EDM file called MyBlogDB.edmx and laid out a Post entity, as shown in Figure 1.
Figure 1 A Post Entity Created in Visual Studio 2010
More complicated blogging software will want to track more data, but the fields in Figure 1 are the basics. When I right-click on the designer surface, I can choose Generate Database From Model, which shows the SQL file that will be created for me (MyBlogDB.sql in this case) and the SQL that will be generated to create my database. Clicking Finish will create the SQL file and bind the database to the entities I created in the EDM designer. The important bits of the SQL are shown in Figure 2.
Figure 2 The SQL Code Resulting from “Generate Database From Model”
...
USE [MyBlogDB];
GO
...
-- Dropping existing tables
IF OBJECT_ID(N'[dbo].[Posts]', 'U') IS NOT NULL
DROP TABLE [dbo].[Posts];
GO
...
-- Creating table 'Posts'
CREATE TABLE [dbo].[Posts] (
[Id] int IDENTITY(1,1) NOT NULL,
[Title] nvarchar(max) NOT NULL,
[PublishDate] datetime NULL,
[Content] nvarchar(max) NOT NULL
);
GO
...
-- Creating primary key on [Id] in table 'Posts'
ALTER TABLE [dbo].[Posts]
ADD CONSTRAINT [PK_Posts]
PRIMARY KEY CLUSTERED ([Id] ASC);
GO
Basically, we’re just creating a single table from our single entity, as expected, and mapping the fields to SQL types. Notice that the PublishDate is set to NULL, which is not the default. I explicitly chose that setting in the EDM designer because I wanted it to be OK not to have a publish date (some tools don’t provide one by default).
To execute this SQL and create the database is just a matter of right-clicking on the SQL in the Visual Studio text editor and choosing Execute SQL. It will ask you for your connection information and the database name. Because this is a new database, you’ll want to type in the new name, for example, MyBlogDB, and click OK to create it when prompted. When your database has been created, you can explore it in the Server Explorer under the connection that Visual Studio has just created for you.
To make testing easier, you can add data directly into the table by right-clicking on Posts and choosing Show Table Data, which will give you a little grid, as shown in Figure 3.
Figure 3 The Show Table Data Grid Makes Testing Easier
It’s not the best editing experience in the world, but it’s better than writing SQL statements until we’ve got an end-to-end editing solution up and running (it’s coming—keep reading!).
Now that we’ve got some data, we can do a little bit of ASP.NET coding to show it by updating HomeController.cs (read more about MVC at asp.net/mvc/):
...
namespace ODataBloggingSample.Controllers {
[HandleError]
public class HomeController : Controller {
MyBlogDBContainer blogDB = new MyBlogDBContainer();
public ActionResult Index() {
return View(blogDB.Posts);
}
public ActionResult About() {
return View();
}
}
}
Here all I did was create an instance of the MyBlogDBContainer class, which is the top-level ObjectContext-derived class created from our MyBlogDB.edmx file to let us access our new database. (If you’re not familiar with the Entity Framework, you should be: see msdn.com/data/aa937723.) When the Index method is called on the HomeController class, someone is requesting the home page of our new Web application, which we’d like to use to show our new blog posts, so we route the Posts collection from the database to an instance of the Home/Index.aspx view, which we’ve modified like so:
<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<IEnumerable<ODataBloggingSample.Post>>" %>
<asp:Content ID="indexTitle" ContentPlaceHolderID="TitleContent" runat="server">
Home Page
</asp:Content>
<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
<% foreach (var post in Model) { %>
<h1><%= post.Title %></h1>
<div><%= post.Content %></div>
<p><i>Posted <%= post.PublishDate %></i></p>
<% } %>
</asp:Content>
Here we changed the base class to take a collection of the Post type generated (along with the MyBlogDBContainer class) to model our Posts table. We also replaced the home page content with a foreach statement to show each post’s title, content and publish date.
That’s all we need. Now when we execute the project (Debug | Start Debugging), the browser starts and the blog posts are shown (just one post, unless you’ve put more than that into the database), as shown in Figure 4.
Figure 4 The Completed Web Page
Now, I told you everything up to this point so I could tell you this: The reason OData is so fabulous is that, with a flick of my mouse and two shakes of my keyboard, I can expose a complete programmatic interface to this data that I can access from JavaScript, the .NET Framework, PHP and more. To see this magic happen, right-click on your project in the Solution Explorer, choose Add | New Item, choose WCF Data Service, pick a name (I used odata.svc) and click Add. What you’ll get is a skeleton piece of code in a file (odata.svc.cs in this case) that, ignoring security just now, we’d like to make look like the following:
using System.Data.Services;
using System.Data.Services.Common;
using ODataBloggingSample;
namespace ODataBloggingSample {
public class odata : DataService<MyBlogDBContainer> {
public static void InitializeService(DataServiceConfiguration config) {
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.DataServiceBehavior.MaxProtocolVersion =
DataServiceProtocolVersion.V2;
}
}
}
Notice that we’ve thrown in MyBlogDBContainer—our top-level database access class—as the template parameter to the DataService class, which is the core of server-side WCF Data Services (see msdn.com/data/bb931106). The DataService class allows us to easily expose our database via HTTP verb-based create, read, update and delete (CRUD) operations defined in the OData protocol. The type passed to the DataService is examined for public properties that expose collections. In our example, the Entity Framework-generated object context class contains the Posts collection that fits the bill nicely:
...
namespace ODataBloggingSample {
...
public partial class MyBlogDBContainer : ObjectContext {
...
public ObjectSet<Post> Posts {...}
...
}
...
public partial class Post : EntityObject {
...
public global::System.Int32 Id { get { ... } set { ... } }
public global::System.String Title { get { ... } set { ... } }
public Nullable<global::System.DateTime> PublishDate {
get { ... } set { ... } }
public global::System.String Content { get { ... } set { ... } }
...
}
}
Notice that the generated MyBlogDBContainer exposes an ObjectSet (which is just a kind of collection) called Posts that contains instances of the Post type. Furthermore, the Post type is defined to provide the mapping between the Id, Title, PublishDate and Content properties to the underlying columns on the Posts table we created earlier.
With odata.svc in place, we can surf to the service document that exposes our object context collection properties using the name of the data service endpoint file in the URL, for example, localhost:54423/odata.svc:
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<service xml:base="https://localhost:54423/odata.svc/" xmlns:atom="https://www.w3.org/2005/Atom"
xmlns:app="https://www.w3.org/2007/app" xmlns="https://www.w3.org/2007/app">
<workspace>
<atom:title>Default</atom:title>
<collection>
<atom:title>Posts</atom:title>
</collection>
</workspace>
</service>
This entire file is defined by the AtomPub specification (ietf.org/rfc/rfc5023.txt). Taking it one level deeper, we can see our posts exposed as a set of Atom entries at localhost:54423/odata.svc/Posts, as shown in Figure 5.
Figure 5 Posts Exposed as a Set of Atom Entries
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<feed xml:base="https://localhost:54423/odata.svc/"
xmlns:d="https://schemas.microsoft.com/ado/2007/08/dataservices"
xmlns:m=
"https://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns="https://www.w3.org/2005/Atom">
<title type="text">Posts</title>
<id>https://localhost:54423/odata.svc/Posts</id>
<updated>2010-03-15T00:26:40Z</updated>
<link rel="self" title="Posts" href="Posts" />
<entry>
<id>https://localhost:54423/odata.svc/Posts(1)</id>
<title type="text" />
<updated>2010-03-15T00:26:40Z</updated>
<author>
<name />
</author>
<link rel="edit" title="Post" href="Posts(1)" />
<category term="MyBlogDB.Post"
scheme=
"https://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
<content type="application/xml">
<m:properties>
<d:Id m:type="Edm.Int32">1</d:Id>
<d:Title>My first blog post</d:Title>
<d:PublishDate m:type=
"Edm.DateTime">2010-03-14T00:00:00</d:PublishDate>
<d:Content>Hi! How are you?</d:Content>
</m:properties>
</content>
</entry>
</feed>
This file is almost completely plain-vanilla Atom (ietf.org/rfc/rfc4287.txt) except for the Microsoft-based URIs, which are used to layer OData functionality into Atom. Specifically, you’ll want to notice the “properties” element inside the “content” element. You’ll recognize these properties as the same ones defined earlier in the Post entity and corresponding Posts table. This data is contained in the envelope defined by Atom and exposed via the CRUD comments, which themselves are defined by AtomPub and
allow you to create, read, update and delete via the HTTP methods POST, GET, PUT and DELETE, respectively. The problem is that this isn’t quite plain-vanilla-Atom enough. For example, if we surf to odata.svc/Posts in an Atom reader, like Internet Explorer 8, the title and content don’t come through properly, as shown in Figure 6.
Figure 6 Viewing Blog Posts in Atom Reader Shows That Title and Content Are Missing
You can see that the data is there (notice the date is right and the category is showing), but the title and content are nowhere to be seen. That’s because the places where Internet Explorer is looking for title and content—the “title” and “content” elements in each entry, logically enough—don’t contain what it expects to be there. The “title” element is blank and the “content” element is in a format that Internet Explorer doesn’t recognize. The format that Internet Explorer would really like to see looks like this:
<feed ...>
<title type="text">Posts</title>
<id>https://localhost:54423/atompub.svc/Posts</id>
<updated>2010-03-15T00:42:32Z</updated>
<link rel="self" title="Posts" href="Posts" />
<entry>
<id>https://localhost:54423/atompub.svc/Posts(1)</id>
<title type="text">My first blog post</title>
<updated>2010-03-15T00:42:32Z</updated>
...
<content type="html">Hi! How are you?</content>
<published>2010-03-14T00:00:00-08:00</published>
</entry>
</feed>
Notice that the “title” element has what used to be buried in the Title property from the OData “properties” element in the “content” element, the “content” element has been overwritten with the Content property and the “published” element was added from the value of PublishDate property. When this data is viewed in Internet Explorer, we get something much more like what we’d like to have, as shown in Figure 7.
Figure 7 Tweaking the XML Format Results in Correct Display of the Title and Content
I should mention that it’s only for support of blogging tools that we even care. Internet Explorer isn’t expecting to see a customer list or an invoice; it’s expecting to see titles and publish dates and HTML content. Sometimes it makes sense to do this mapping for customer lists and invoices, in which case Microsoft has a feature in WCF Data Services called “Friendly Feeds” (see blogs.msdn.com/astoriateam/archive/ 2008/09/28/making-feeds-friendly.aspx). It doesn’t quite do everything, however (specifically, it won’t remap the Atom “content” element), because the WCF Data Services team wants to make sure even “friendly” feeds still work with various client libraries. The goal is to make the OData feeds friendly, not abandon OData in favor of Atom/AtomPub.
In this case, however, we’re abandoning OData and just using WCF Data Services as our AtomPub endpoint, which requires a mapping between Atom and OData, as shown in Figure 8.
Figure 8 Mapping Between Atom and OData (Click to enlarge)
The trick is, how do we get this mapping to happen? We’ve obviously got the data, but we need to remap it to Atom properties so that Atom readers (and writers) know where the data is tucked away. The reason to do this is so WCF Data Services can still do the mapping to our .NET Framework types or, via Entity Framework, our databases. All we have to do is a little at-the-door mapping of Atom/AtomPub to/from OData.
The code sample that comes with this article has some code to inject into the WCF pipeline that allows just exactly this kind of message data transformation. You can read it to your heart’s content (check out ODataBlogging.cs), but I’m going to show you how to use it.
First, create a new WCF Data Services endpoint just like you did before, but with a different name (I used atompub.svc). Hook up the top-level object context class and expose whatever entity sets you like, just as before, but also tag your service class with the ODataBloggingServiceBehavior, like so:
...
using ODataBlogging;
namespace ODataBloggingSample {
[ODataBloggingServiceBehavior(typeof(MyBlogDBContainer))]
[EntityAtomMapping("Posts", "PublishDate", "published")]
public class atompub : DataService<MyBlogDBContainer> {
public static void InitializeService(DataServiceConfiguration config) {
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.DataServiceBehavior.MaxProtocolVersion =
DataServiceProtocolVersion.V2;
}
}
}
This does the mapping from Atom/AtomPub coming in—for example, “title,” “content” and “published” elements—to the corresponding OData format via the nested “properties” element inside the “content” element. By default, if the names on the entities match (ignoring case), then the mapping (and type coercion) will just happen. For example, when an entity is exposed that contains a Title property (like our Post entity), it’s mapped to the Atom “title” element.
On the other hand, if there’s no automatic mapping, you can override that behavior by providing an explicit mapping based on the entity name, as we’ve done to map the PublishDate property for objects in the “Posts” collection to the “published” atom property. Those two attributes are enough to turn our OData feed into an Atom feed, giving us the full-featured view of the data as shown in Figure 7.
This mapping is not one-way; it supports all of the HTTP methods, so you can use the AtomPub protocol to create, update and delete items in the Posts collection as well as read them. This means you can configure a tool like Windows Live Writer (WLW), which supports AtomPub as a blog API, and use it for rich text editing of your posts. For example, given the atompub.svc endpoint, in WLW, you could choose Blogs | Add blog account and fill in the following options in the dialogs that follow:
- What blog service do you use? Other blog service
- Web address of your blog: https://<<server>>:<<port>>/atompub.svc
- Username: <<username>> (required and should be implemented on your AtomPub endpoint using standard HTTP techniques)
- Password: <<password>>
- Type of blog that you’re using: Atom Publishing Protocol
- Service document URL: https://<<server>>:<<port>>/atompub.svc
- Blog nickname: <<whatever you like>>
Click Finish and you’ve got a rich text editor for managing your blog posts, as shown in Figure 9.
Figure 9 Atom/OData Mapping Facilitates Building a Rich Text Editor to Manage Your Blog Posts
Here we’ve taken the Data Services engine, which supports full CRUD functionality by packing properties into the Atom “content” element, and have done a little bit of mapping to make it support plain ol’ Atom and AtomPub, too.
The little sample library that I used to make this work (which I built with Phani Raj, a software engineer at Microsoft on the WCF Data Services team), does the barest minimum and it’s never going to be all you need to build a real blog. Here’s a list off the top of my head of things it still needs to really support just Atom and AtomPub:
- Mapping the Atom author element sub-elements, such as name, uri and e-mail.
- Handling images (although WLW allows for FTP, so that might be enough).
- Exposing capabilities to make WLW recognize these features.
If you’re interested in pushing this experiment further, Joe Cheng, a member of the WLW team, has written a series of blog posts about AtomPub support in WLW that inspired this work in the first place: jcheng.wordpress.com/2007/10/15/how-wlw-speaks-atompub-introduction.
Enjoy!
Chris Sells is a Microsoft program manager in the Business Platform Division. He’s written several books, including the coauthored “Programming WPF” (O’Reilly Media, 2007), “Windows Forms 2.0 Programming” (Addison-Wesley Professional, 2006) and “ATL Internals” (Addison-Wesley Professional, 1999). In his free time, he hosts various conferences and makes a pest of himself on Microsoft internal product team discussion lists. More information about Sells, and his various projects, is available at sellsbrothers.com.
Thanks to the following technical expert for reviewing this article: Pablo Castro