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

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

Rate This
  • Comments 17

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.

Leave a Comment
  • Please add 8 and 4 and type the answer here:
  • Post
  • What is the performance difference between an ODataController and a WCF data service? Which one is lighter? Which one is recommended for performance?

    Thanks

  • Hi Gal,

    Whether you should be using Web API or WCF Data Services is a tough question to answer. WCF Data Services has more OData functionality implemented, while Web API is more flexible and extensible. In general, if you just have a database you want to expose using OData, WCF Data Services is still your best bet. If you want to introduce business logic and have more control over the request pipeline, then Web API may be the better option.

    Performance is hard to give a recommendation on because it's dependent on so many factors, but we've found the performance of the two stacks to be roughly comparable.

    Hope that helps

  • I can't find MapODataRoute on System.Web.Http.OData 0.3.0.0

    nor Microsoft.Data.OData.dll 5.2.0.0

    So where did it go?

  • You're still using our RC build. You'll have to update to using our nightly packages. See above for instructions.

  • Good post!!!

  • I am using the nightly build and I am running into a OData Navigation issue.

    I get the following error.

    This service doesn't support OData requests in the form '~/entityset/key/navigation'.

    What do I need to do to support the above request?

  • @KHappe, I'm planning to write a blog post that goes into a lot more depth on the OData routing conventions that we use for Web API.

    The answer to your question is that for GET requests you should define an action that looks like this:

    public Order GetOrder([FromODataUri] int key) {}

    where 'Order' is the name of your navigation property. Hope that helps.

  • Thank you sir.  That fixed my issue.

  • I'm doing a database first WebApi project and I've created my model from an existing database.  What do I use for my "model" in this line of code from your example:

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

  • Sorry, jumped the gun.  I solved my earlier question by more closely following your instructions.  Now for a follow up.  How can I easily add all of the entities from my EDMX to the model using modelBuilder?  There could potentially be hundreds of Entities.  I'd hate to add each one by hand.

  • When will the $select query options be supported?

  • @Bill, there's currently no easy way to take an EDMX model and create a model that will work for Web API OData, but we're aware of the issue and we're looking to make it easier in a future release.

    @Luke, $select and $expand support are top priorities for us. Expect to see built-in support in the next release.

  • Thanks for the reply.

    Since the next release would be weeks away, is there any hint that I can make a simple implementation of $select by myself?

  • I have Member class with navigation poperty WorkAddress.

    I can use url like this: http://localhost:65457/odata/Members(1)/WorkAddress to gt value of this property.

    Could I use post (patch) with this urlto insert (update) WorkAddress?

    When I try to do it I get

    {

       "odata.error": {

           "code": "Not implemented.",

           "message": {

               "lang": "en-US",

               "value": "This service doesn't support OData requests in the form '~/entityset/key/navigation'."

           }

       }

  • I know this post is more than a year old. But OData hasn't changed much from what its explained in this post. I'm really looking for a specific answer. you have mentioned "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."

    Do we have control over the controller that this request goes to? The problem i'm having is http://server/vroot/odata/$metadata exposes my entire database structure which I don't want to do. Is it possible to stop this? or do we have control over what is exposed in this.

    Thanks

Page 1 of 2 (17 items) 12