OData in WebAPI – RC release

Next week we will release an official RC of the Microsoft ASP.NET WebAPI OData assembly. This marks the third release on our way to RTM. Although this post talks about code that hasn’t been officially release yet, since all development is happening in public, if you can’t wait till next week, you can always go and get one of the nightly builds or for a more bare metal experience build it yourself from our code repository.

In this post I will cover what is new since the Alpha, if you need a more complete view of what we is currently supported I recommend you read about the August Release and the Alpha release as well.

Simplified OData support

To make it easy to support full OData we’ve added a simple extension method HttpConfiguration.EnableOData(IEdmModel) that allows you to easily configure your service to support OData, simply by providing an IEdmModel and optionally supplying an OData route prefix (i.e. ~/api or similar).

The method does a number of key tasks for you, each of which you can do manually if necessary:

Registers an OData wild card route. Which will have a prefix if you specified a prefix when you called EnableOData(..). For example this:

HttpConfiguration.EnableOData(model, “api”) will position your OData service under the ~/api url prefix.

Registers the various ODataMediaTypeFormatters.

Note today this will stomp on application/json for your whole service. By RTM we aim to make this much more selective, so you get the OData version of application/json only if your request addresses an OData resource.

Stashes the IEdmModel on the configuration

Stashes the DefaultODataPathHandler on the configuration.

Registers the ODataControllerSelector and ODataActionSelectors and configures them to use default OData routing conventions. These selectors only do OData routing when the request is recognized as an OData request, otherwise they delegate to the previously registered selectors.

First class OData routing

In the alpha release to get everything (OData Actions, OData casts, OData navigations etc) working together I updated the sample to do custom routing. This routing was based on a component that understood OData Paths and dispatched to actions based on custom routing conventions. At the time we knew this was a merely a stop gap.

That code has now been refactored, improved and moved into the core code base.

The code consists of:

A class called ODataPath that represents the path part of an OData Url. An ODataPath is made up of a list of ODataPathSegments, these semantically represent the OData operations you compose to address a resource. For example something like this: ~/Customers(10)/Orders, semantically has 3 instructions that are encoded as ODataPathSegments in an ODataPath:

Start in the Customers EntitySet

Then find a single customer with the key 10

Then Navigate to the related Orders.

An interface called IODataPathHandler that has two methods:

ODataPath Parse(string odataPath) –> to take a Url and parse it into an ODataPath

string Link(ODataPath) –> to take an ODataPath and generate a Url.

A default implementation of IODataPathHandler called DefaultODataPathHandler, that implements these methods using the OData V3’s built-in conventions.

A way to register and retrieve a IODataPathHandler on your HttpConfiguration. Two things worth calling out here:

Normally you don’t have to worry about this, because calling HttpConfiguration.EnableOData(…) registers the DefaultODataPathHandler for you.

This design means that you don’t like the way OData puts keys in parenthesis (i.e.~/Customers(1)) and would prefer to put keys in their own segments (i.e. ~/Customer/1) then all you need to do is override the DefaultODataPathHandler so that it recognizes this syntax. I’ve tried this personally and doing targeted overloads like this is pretty easy.

All OData link generation conventions (for example for EditLinks or links to invoke actions) now build an ODataPath to represent the link and then ask the registered IODataPathHandler to convert that to a url using the IODataPathHandler.Link(..) method.

An ODataControllerSelector and an ODataActionSelector that route OData requests based on a configurable set of IODataRoutingConventions. Out the box EnableOData(..) will register these routing conventions:

EntitySetRoutingConvention –> for routing request to manipulate and query an EntitySet, for example:

GET ~/Customers

GET ~/Customers/Namespace.VIP

POST ~/Customers

EntityRoutingConvention –> for routing request to retrieve or manipulate an Entity, for example:

MetadataRoutingConvention –> for routing request to retrieve $metadata or the service document

GET ~/$metadata

GET ~

LinksRoutingConvention –> for routing requests to manipulate relationship, for example:

DELETE ~/Customers(1)/$links/Orders(1)

POST ~/Customers(1)/$links/Orders

PUT ~/Customers(1)/$links/RelationshipManager

ActionRoutingConvention –> for routing requests to invoke an OData Action

POST ~/Customers(1)/ConvertToVIP

UnmappedRequestRoutingConvention –> for routing requests that match no other convention to a fall back method, for example:

GET ~/Customers(1)/RelationshipManager/Customers

POST ~/Customers(1)/Orders

A new ODataPathRouteConstraint class that implements IHttpRouteConstraint an makes sure the ‘wildcard route’ that captures every request against your OData service only matches if the Url is in fact an OData url.

An EntitySetController<TEntity,TKey> class that provides a convenient starting point for creating controllers for your OData EntitySets. This class provides stub methods that follow the default IODataRoutingConventions will route requests too. Your job is simply to handle the request.

For a complete example of how to use all this new goodness check out the sample OData service which you can find at http://aspnet.codeplex.com in the source tree under: /Samples/Net4/CS/WebApi/ODataService.

Query validation

One of the most exciting new features is the ability to specify validation rules to be applied to a query. Essentially this allows you to constrain what query options you allow against your [Queryable]. Under the hood this is all implemented via the virtual ODataQueryOptions.Validate(ODataValidationSettings) method, which you can use if you need to.

That said the 90-99% scenario is simply to specify additional parameters to the [Queryable] attribute that the control what is allowed in a query, and then before the query is processed, Web API converts those settings into an ODataValidationSettings object and calls ODataQueryOptions.Validate(..). If anything not supported is found in the AST a validation error results, and your backend Queryable never gets called.

This means only Skip and Top are allowed, i.e. a $filter or $orderby would result in a validation error. By default every thing is allowed, until you mention a particular setting, then only the things you list are supported.

Another example is:

[Queryable(AllowedLogicalOperators=ODataLogicalOperators.Equal)]

This says the $filter only supports the Equal operator, so for example this:

~/Customer?$filter=Name eq ‘Bob’

will pass validation but this:

~/Customers?$filter=Name ne ‘Bob’

will fail.

Checkout the new [Queryable] attribute to find out more.

Delta<T> improvements

This now supports patching classes that derive from the T too. This is useful if you want both these requests:

At this point the only thing that we don’t support (and won’t support for RTM either) is spatial types and custom OData functions inside the filter. These will come later when support for them is added to ODataLib’sODataUriParser.

Partial support for OData’s new JSON format

This release also includes a little support for the new OData v3 json format, and of course by RTM time we will have full support. This OData V3 json format is more efficient than our older V1/V2 json format (which incidentally the OData team has started calling ‘JSON verbose’, to make sure you know we don’t like it any more 🙂

The new JSON format has sub types, which allow clients to specify how much metadata they want, ranging from FullMetadata, through MinimalMetadata to NoMetadata. The RC supports the new OData JSON format only for write scenarios (i.e. responding to requests), it doesn’t currently handle read scenarios at all (i.e. request payloads that are in the new JSON format). It supports the most common payload kinds, feeds, entries, errors and service documents, and by RTM we will flesh this out to include other payloads like properties, links etc.

In the RC we don’t support NoMetadata at all, and we treat MinimalMetadata as if it is FullMetadata. We do this because MinimalMetadata means only send metadata and links that the client can’t deduce them by convention, and in the RC we don’t have anyway to tell the formatter that you are indeed following conventions. This forces us to always emit links. By RTM we will add a way to say you are following convention, and that will allow us to support MinimalMetadata properly.

Summary

As you can see we’ve been really busy making Web API a first class stack for creating and exposing OData services, ranging from supporting just the OData query syntax, through supporting the majority of the OData protocol, all the way up to supporting extra things not in OData but enabled by Web API, like for example new formats.

Wow… looks like I jumped on this train right in time. This is really great news, Alex. Is there an official forum for this project? You answer so many questions on your blog here that I'm tempted to continue the tradition. 🙂

Is there a known way to support multiple Gets on a particular controller? I've looked around and can't find any example/guidance. That is, I'd like to have a Get() method and GetByAnother() method that both return IQueryable's.

Thanks, Christopher. I am familiar with AttributeRouting, but from what I understand it doesn't play well with the routes generated by WebAPI OData (and more specifically the data client generated by Add Service Reference). Perhaps I'm missing something… would love a reference/example!

MichaelD. I have a similar need, to support multiple Gets. But in my case, it's OK for me to limit the difference between the method signatures to custom query params (as opposed to explicitly calling a different action name). In one case, I'm returning an IQueryable based on direct repository access (no args) and the other based on a "google like" search (with a arg called "criteria"). Based on whether I provide the query param, the correct controller action is automatically being used.

I understand the WebApi OData support is not fully complete and it's very much work in progress at present; however I have a few questions with regards to the OData implementation and ApiController as they are at preset; is there any plans for the routing for apicontroller/odata to areas? and/or will odata support multiple EntityDataModels which map to different uri?

At present the current API implies that you can do what you want, i.e.

Configuration.EnableOData(BusinessModel,"api/odata/B2B");

Configuration.EnableOData(CustomerModel,"api/odata/B2C");

Unfortunately that is not the case today. That said we are working on a hook to allow you to support something like this, and will modify the API before RTM to make sure that the API doesn't lead you to draw the wrong conclusions. I'd keep an eye on the nightlies if I was you, something should make it into the code base pretty soon.

Unfortunately comments are closed on the Action blog, so I'll post here in the hope it helps someone else.

Although it's not clear from the OData spec Actions don't have to run against an individual entity, they can also run against a set.

All the examples take the form:

/api/Product(1)/Action

However, the following is valid (and very useful!):

/api/Product/SetAction

There aren't a lot of good examples, and it would be nice if the samples were updated to include Actions for a set.

For anyone else looking, the trick is to call Action not on EntityType, but on EntityType.Collection. Another gotcha is to specify the return type correctly otherwise the formatters will fail rather obscurely.

In my case, I wanted to return a collection of entities (which would be quite common from a set-based action), the ActionConfiguration class has a ReturnsCollectionFromEntitySet method that makes that possible.

The sample using 0.2.0 build OK. But 0.3.0 doesn't, there's a missing reference to ODataRouteNames. Were those definitions moved somewhere else? I can't find them. How to build Explicit models without it. Thanks!

I figured out the Sample changes (had to update NuGet to the nightly builds)

Do you expect $expand support to be in this release?

If not, is there any way to force the output to expand a navigation property? I understand supporting the generic case is hard, but we have a few specific scenarios in our API specification where $expand is really important…

I like the way OData handles the keys. However, I may have to implement keys in separate segments. Can you point me to the best way to do this?

"This design means that you don’t like the way OData puts keys in parenthesis (i.e.~/Customers(1)) and would prefer to put keys in their own segments (i.e. ~/Customer/1) then all you need to do is override the DefaultODataPathHandler so that it recognizes this syntax. I’ve tried this personally and doing targeted overloads like this is pretty easy"