Getting started with ASP.NET Web API OData in 3 simple steps

With the upcoming ASP.NET 2012.2 release, we’ll be adding support for OData to Web API. In this blog post, I’ll go over the three simple steps you’ll need to go through to get your first OData service up and running:

  1. Creating your EDM model
  2. Configuring an OData route
  3. Implementing an OData controller

Before we dive in, the code snippets in this post won’t work if you’re using the RC build. You can upgrade to using our latest nightly build by taking a look at this helpful blog post.

1) Creating your EDM model

First, we’ll create an EDM model to represent the data model we want to expose to the world. The ODataConventionModelBuilder class makes this this easy by using a set of conventions to reflect on your type and come up with a reasonable model. Let’s say we want to expose an entity set called Movies that represents a movie collection. In that case, we can create a model with a couple lines of code:

   1: ODataConventionModelBuilder modelBuilder = new ODataConventionModelBuilder();
   2: modelBuilder.EntitySet<Movie>("Movies");
   3: IEdmModel model = modelBuilder.GetEdmModel();

2) Configuring an OData route

Next, we’ll want to configure an OData route. Instead of using MapHttpRoute the way you would in Web API, the only difference here is that you use MapODataRoute and pass in your model. The model gets used for parsing the request URI as an OData path and routing the request to the right entity set controller and action. This would look like this:

   1: config.Routes.MapODataRoute(routeName: "OData", routePrefix: "odata", model: model);

The route prefix above is the prefix for this particular route. So it would only match request URIs that start with http://server/vroot/odata, where vroot is your virtual root. And since the model gets passed in as a parameter to the route, you can actually have multiple OData routes configured with a different model for each route.

3) Implementing an OData controller

Finally, we just have to implement our MoviesController to expose our entity set. Instead of deriving from ApiController, you’ll need to derive from ODataController. ODataController is a new base class that wires up the OData formatting and action selection for you. Here’s what an implementation might look like:

   1: public class MoviesController : ODataController
   2: {
   3:     List<Movie> _movies = TestData.Movies;
   4:  
   5:     [Queryable]
   6:     public IQueryable<Movie> GetMovies()
   7:     {
   8:         return _movies.AsQueryable();
   9:     }
  10:  
  11:     public Movie GetMovie([FromODataUri] int key)
  12:     {
  13:         return _movies[key];
  14:     }
  15:  
  16:     public Movie Patch([FromODataUri] int key, Delta<Movie> patch)
  17:     {
  18:         Movie movieToPatch = _movies[key];
  19:         patch.Patch(movieToPatch);
  20:         return movieToPatch;
  21:     }
  22: }

There’s a few things to point out here. Notice the [Queryable] attribute on the GetMovies method. This enables OData query syntax on that particular action. So you can apply filtering, sorting, and other OData query options to the results of the action. Next, we have the [FromODataUri] attributes on the key parameters. These attributes instruct Web API that the parameters come from the URI and should be parsed as OData URI parameters instead of as Web API parameters. Finally, Delta<T> is a new OData class that makes it easy to perform partial updates on entities.

One important thing to realize here is that the controller name, the action names, and the parameter names all matter. OData controller and action selection work a little differently than they do in Web API. Instead of being based on route parameters, OData controller and action selection is based on the OData meaning of the request URI. So for example if you made a request for http://server/vroot/odata/$metadata, the request would actually get dispatched to a separate special controller that returns the metadata document for the OData service. Notice how the controller name also matches the entity set name we defined previously. I’ll try to go into more depth about OData routing in a future blog post.

Instead of deriving from ODataController, you can also choose to derive from EntitySetController. EntitySetController is a convenient base class for exposing entity sets that provides simple methods you can override. It also takes care of sending back the right OData response in a variety of cases, like sending a 404 Not Found if an entity with a certain key could not be found. Here’s what the same implementation as above looks like with EntitySetController:

   1: public class MoviesController : EntitySetController<Movie, int>
   2: {
   3:     List<Movie> _movies = TestData.Movies;
   4:  
   5:     [Queryable]
   6:     public override IQueryable<Movie> Get()
   7:     {
   8:         return _movies.AsQueryable();
   9:     }
  10:  
  11:     protected override Movie GetEntityByKey(int key)
  12:     {
  13:         return _movies[key];
  14:     }
  15:  
  16:     protected override Movie PatchEntity(int key, Delta<Movie> patch)
  17:     {
  18:         Movie movieToPatch = _movies[key];
  19:         patch.Patch(movieToPatch);
  20:         return movieToPatch;
  21:     }
  22: }

Notice how you don’t need [FromODataUri] anymore because EntitySetController has already added it for you on its own action parameters. That’s just one of the several advantages of using EntitySetController as a base class.

Now that we have a working OData service, let’s try out a few requests. If you send a request to http://localhost/odata/Movies(2) with an “application/json” Accept header, you should get a response that looks like this:

   1: {
   2:   "odata.metadata": "http://localhost/odata/$metadata#Movies/@Element",
   3:   "ID": 2,
   4:   "Title": "Gladiator",
   5:   "Director": "Ridley Scott",
   6:   "YearReleased": 2000
   7: }

On the other hand, if you set an “application/atom+xml” Accept header, you might see a response that looks like this:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <entry xml:base="http://localhost/odata/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml">
   3:   <id>http://localhost/odata/Movies(2)</id>
   4:   <category term="MovieDemo.Model.Movie" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
   5:   <link rel="edit" href="http://localhost/odata/Movies(2)" />
   6:   <link rel="self" href="http://localhost/odata/Movies(2)" />
   7:   <title />
   8:   <updated>2013-01-30T19:29:57Z</updated>
   9:   <author>
  10:     <name />
  11:   </author>
  12:   <content type="application/xml">
  13:     <m:properties>
  14:       <d:ID m:type="Edm.Int32">2</d:ID>
  15:       <d:Title>Gladiator</d:Title>
  16:       <d:Director>Ridley Scott</d:Director>
  17:       <d:YearReleased m:type="Edm.Int32">2000</d:YearReleased>
  18:     </m:properties>
  19:   </content>
  20: </entry>

As you can see, the Json.NET and DataContractSerializer-based responses you’re used to getting when using Web API controllers get replaced with the OData equivalents when you derive from ODataController.