OData support in ASP.NET Web API


UPDATE 2 @1:21 pm on 16th August (PST):
There is an updated version of the nuget package that resolves the previous dependency issues. Oh and my comments are now working again.

UPDATE 1 @10:00 am on 16th August (PST):
If you’ve tried using the preview nuget package and had problems, rest assured we are working on the issue (which is a dependency version issue). Essentially the preview and one of its dependencies have conflicting dependencies on specific versions of packages. The ETA for a fix that resolves this nuget issue is later today. 
Also if you’ve made a comment and it isn’t showing up, don’t worry it will. I’m currently having technical problems approving comments.

Earlier versions ASP.NET Web API included basic support for the OData, allowing you to use the OData Query Options $filter, $orderby, $top and $skip to shape the results of controller actions annotated with the [Queryable] attribute. This was very useful and worked across formats. That said true support for the OData format was rather limited.  In fact we barely scratched the surface of OData, for example there was no support for creates, updates, deletes, $metadata and code generation etc.

To address this we’ve create a preview of a new nuget package (and codeplex project) for building OData services that:

  • Continues to support the [Queryable] attribute, but also allows you to drop down to an Abstract Syntax Tree (or AST) representing $filter & $orderby.
  • Adds ways to infer a model by convention or explicitly customize a model that will be familiar to anyone who’s used Entity Framework Code First.
  • Adds support for service documents and $metadata so you can generate clients (in .NET, Windows Phone, Metro etc) for your Web API.
  • Adds support for creating, updating, partially updating and deleting entities.
  • Adds support for querying and manipulating relationships between entities.
  • Adds the ability to create relationship links that wire up to your routes.
  • Adds support for complex types.
  • Adds support for Any/All in $filter.
  • Adds the ability to control null propagation if needed (for example to avoid null refs working about LINQ to Objects).
  • Refactors everything to build upon the same foundation as WCF Data Services, namely ODataLib.

In fact this is an early preview of a new OData Server stack built to take advantage of Web APIs inherent flexibility and power which compliments WCF Data Services.

This preview ships with a companion OData sample service built using Web API. The sample includes three controllers, that each expose an OData EntitySet with varying capabilities. One is rudimentary supporting just query and create, the other two are more complete, supporting Query, Create, Update, Patch, Delete and Relationships. The first complete example does everything by hand, the second derives from a sample base controller called EntitySetController that takes care of a lot of the plumbing for you and allows you to focus on the business logic.

The rest of this blog post will introduce you to the components that make up this preview and how to stitch them together to create an OData service, using the code from this sample from the ASP.NET Web Stack Sample repository if you want to follow along.

[Queryable] aka supporting OData Query

If you want to support OData Query options, without necessarily supporting the OData Formats, all you need to do it put the [Queryable] attribute on an action that returns either IQueryable<> or IEnumerable<>, like this:

[Queryable]
public IQueryable<Supplier> GetSuppliers()
{
    return _db.Suppliers;
}

Here _db is an EntityFramework DBContext. If this action is routed to say ~/Suppliers then any OData Query options applied to that URI will be applied by an Action Filter before the result is sent to the client.

For example this: ~/Suppliers?$filter=Name eq ‘Microsoft’

Will pass the result of GetSuppliers().Where(s => s.Name == “Microsoft”) to the formatter.

This all works as it did in earlier previews, although there are a couple of, hopefully temporary, caveats:

  • The element type can’t be primitive (for example IQueryable<string>).
  • Somehow the [Queryable] attribute must find a key property. This happens automatically if your element type has an ID property, if not you might need to manually configure the model (see setting up your model).

Doing more OData

If you want to support more of OData, for example, the official OData formats or allow for more than just reads, you need to configure a few things.

The ODataService.Sample shows all of this working end to end, and the remainder of this post will talk you through what that code is doing.

Setting up your model

The first thing you need is a model. The way you do this is very similar to the Entity Framework Code First approach, but with a few OData specific tweaks:

  • The ability to configure how EditLinks, SelfLinks and Ids are generated.
  • The ability to configure how links to related entities are generated.
  • Support for multiple entity sets with the same type.

If you use the ODataConventionModelBuilder most of this is done automatically for you, all you need to do is tell the builder what sets you want, for example this code:

ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Product>(“Products”);
modelBuilder.EntitySet<ProductFamily>(“ProductFamilies”);
modelBuilder.EntitySet<Supplier>(“Suppliers”);
IEdmModel model = modelBuilder.GetEdmModel();

Builds a model with 3 EntitySets (Products, ProductFamilies and Suppliers) where the EntityTypes of those sets are inferred from the CLR types (Product, ProductFamily and Supplier) automatically, and where EditLinks, SelfLinks and IdLinks are all configured to use the default OData routes.

You can also take full control of the model by using the ODataModelBuilder class, here you explicitly add EntityTypes, Properties, Keys, NavigationProperties and how to route Links by hand. For example this code:

var products = modelBuilder.EntitySet<Product>(“Products”);
products.HasEditLink(entityContext => entityContext.UrlHelper.Link( 
    ODataRouteNames.GetById, 
    new { controller = “Products”, id = entityContext.EntityInstance.ID } 
));
var product = products.EntityType;
product.HasKey(p => p.ID);
product.Property(p => p.Name);
product.Property(p => p.ReleaseDate);
product.Property(p => p.SupportedUntil);

Explicitly adds a EntitySet called Products to the model, configures the EditLink (and unless overridden the SelfLink and IdLink) generation so that if uses the ODataRouteNames.GetById route, and then as you can see the code needed to configure the Key and Properties is very similar to the Code First.

For a more complete example take a look at the GetExplicitEdmModel() method in the sample it builds the exact same model as the ODataConventionModelBuilding by hand.

Setting up the formatters, routes and built-in controllers

To use the OData formats you first need to register an ODataMediaTypeFormatter, which will need the model you built previously:

// Create the OData formatter and give it the model
ODataMediaTypeFormatter odataFormatter = new ODataMediaTypeFormatter(model);

// Register the OData formatter
configuration.Formatters.Insert(0, odataFormatter);

Next you need to setup some routes to handle common OData requests, below are the routes required for a Read/Write OData model built using the OData Routing conventions that also supports client side code-generation (vital if you want a WCF DS client application to talk to your service).

// Metadata routes to support $metadata and code generation in the WCF Data Service client.
configuration.Routes.MapHttpRoute(
   
ODataRouteNames.Metadata,
    “$metadata”,
    new { Controller = “ODataMetadata”, Action = “GetMetadata” }
);
configuration.Routes.MapHttpRoute(
    ODataRouteNames.ServiceDocument,
    “”,
    new { Controller = “ODataMetadata”, Action = “GetServiceDocument” }
);

// Relationship routes (notice the parameters is {type}Id not id, this avoids colliding with GetById(id)).
// This code handles requests like ~/ProductFamilies(1)/Products

configuration.Routes.MapHttpRoute(ODataRouteNames.PropertyNavigation, “{controller}({parentId})/{navigationProperty}”);

// Route for manipulating links, the code allows people to create and delete relationships between entities
configuration.Routes.MapHttpRoute(ODataRouteNames.Link, “{controller}({id})/$links/{navigationProperty}”);

// Routes for urls both producing and handling urls like ~/Product(1), ~/Products() and ~/Products
configuration.Routes.MapHttpRoute(ODataRouteNames.GetById, “{controller}({id})”);
configuration.Routes.MapHttpRoute(ODataRouteNames.DefaultWithParentheses, “{controller}()”);
configuration.Routes.MapHttpRoute(ODataRouteNames.Default, “{controller}”);

One thing to note is the way that the ODataRouteNames.PropertyNavigation route attempts to handle requests to urls like ~/ProductFamilies(1)/Products and ~/Products(1)/Family etc. Essentially a single route for all Navigations. For this to work without requiring a single Action for all navigation properties, we register a custom action selector that will build the Action name using the {navigationProperty} parameter of the PropertyNavigation route:

// Register an Action selector that can include template parameters in the name
configuration.Services.Replace(typeof(IHttpActionSelector), new ODataActionSelector());

This custom action selector will dispatch a request to GET ~/ProductFamilies(1)/Products, to an action called the GetProducts(int parentId) on the ProductFamilies controller.

At this point our previous GetSupplier action can return OData formats. However it will still produce links that won’t work when dereferenced. To fix this we need to start creating our controllers.

Adding Support for OData requests

In our model we added 3 entitysets: Products, ProductFamilies and Suppliers. So first we create 3 controllers, called ProductController, ProductFamiliesController and SuppliersController respectively.

Queries

By convention your controllers should have a method called Getxxx() that returns IQueryable<T>. Here the T is the CLR type backing your EntitySet, so for example on the ProductsController which is for the Products entityset the T should be Product, and the action should look something like this:

[Queryable]
public IQueryable<Product> GetProducts()
{
    return _db.Products;
}

As you can see this is the same as before, but now it is participating in a ‘compliant’ OData service. Inside this method you can do all sorts of things, you can add additional filters based on the identity of the caller, you do auditing, you can do logging, you can aggregate data from multiple sources.

There is even a way to drop down a layer so you get to the see the OData query that has been received. Doing this allows you to process the query yourself in whatever way is appropriate for your data sources. For example you might not have an IQueryable you can use. See ODataQueryOptions for more on this.

At this point because you’ve registered a model and you’ve setup routes for $metadata and the OData Service Document, you should be able to use Visual Studio’s Add Service Reference to generate a set of client proxy classes that you could use like this:

foreach(var product in ctx.Products.Where(p => p.Name.StartsWith(“MS”))
{
     Console.WriteLine(product.Name);
}

WCF DS client will then translate this into a GET request like this:

~/Products$filter=startswith(Name,’MS’)

And everything should just work.

Get by Key

To retrieve an individual item using it’s key in OData you send a GET request to a url like this: ~/Products(1). The ODataRouteNames.GetById route handles requests like this:

public HttpResponseMessage GetById(int id)
{
    Supplier supplier = _db.Suppliers.SingleOrDefault(s => s.ID == id); 
    if (supplier == null) 
    { 
         return Request.CreateResponse(HttpStatusCode.NotFound); 
    } 
    else 
    { 
         return Request.CreateResponse(HttpStatusCode.OK, supplier); 
    }
}

As you can see the code simply attempts to retrieve an item by ID, and then return it with a 200 OK response or return 404 Not Found.

If you have WCF DS proxy classes on the client a request like this:

ctx.Products.Where(p => p.ID == 5).FirstOrDefault();

Should now hit this action.

Inserts (POST requests)

To create a new Entity in OData you POST a new Entity directly to the url that represents the EntitySet, this means that insert requests just like queries end up matching the ODataRouteNames.Default route, however Web API’s action selector picks actions prefixed with Post for POST requests to distinguish from GET requests for Queries:

public HttpResponseMessage PostProduct(Product product)
{
    product.Family = null;

    Product addedProduct = _db.Products.Add(product); 
    _db.SaveChanges(); 

    var response = Request.CreateResponse(HttpStatusCode.Created, addedProduct); 
    response.Headers.Location = new Uri(
       Url.Link(ODataRouteNames.GetById, new { Controller = “Products”, Id = addedProduct.ID })
    ); 
    return response;
}

Here we simply add the product to our Entity Framework context and then call SaveChanges(). The only interesting bit way we set the location header using the ODataRouteNames.GetById route (i.e. the default OData route for Self/Edit links).

Updates (PUT requests)

To replace an Entity in OData you PUT the updated version of the entity to the uri specified in the EditLink of the entity. This will match the GetById route, but because it is a PUT request it will look for an Action name prefixed with Put.

This example shows how to do this:

public HttpResponseMessage Put(int id, Product update)
{
    if (!_db.Products.Any(p => p.ID == id))
    {
        throw ODataErrors.EntityNotFound(Request);
    }
    update.ID = id; // ignore the ID in the entity use the ID in the URL.
    _db.Products.Attach(update);
    _db.Entry(update).State = System.Data.EntityState.Modified;
    _db.SaveChanges();
    return Request.CreateResponse(HttpStatusCode.NoContent);
}

The code is pretty simple, first we verify the entity the client is trying to update actually exists, then we ignore the ID in the entity and instead use the id extracted from the uri of the request, this stops clients updating one entity using the editlink of another entity.

Once all that validation is out of the way we attach the updated entity and tell EF it has been modified before calling save changes and finally returning a No Content response.

As you can see this leverages a helper class called ODataErrors, its job is simply to raise OData compliant errors. The sample uses the EntityNotFound method quite frequently, and it follows the general pattern for failing in an OData compliant way so lets take a peak at its implementation:

public static HttpResponseException EntityNotFound(this HttpRequestMessage request)

    return new HttpResponseException( 
        request.CreateResponse( 
            HttpStatusCode.NotFound, 
            new ODataError 
            { 
                Message = “The entity was not found.”, 
                MessageLanguage = “en-US”, 
                ErrorCode = “Entity Not Found.” 
            } 
        ) 
    );
}

We create a HttpResponseException with a nested Not Found response, the body of which contains an ODataError. By putting an ODataError in the body we get the ODataMediaTypeFormatter to format the body as a valid OData error message as per the content-type requested by the client.

NOTE: Over time we will try to teach the ODataMediaTypeFormatter to handle HttpError as well by using a default translation from HttpError to ODataError.

Partial Updates (PATCH requests)

PUT request have replace semantics which makes updates all or nothing, meaning you have to send all the properties even if only a subset have changed.This is where PATCH comes in, PATCH allows clients to send just the modified properties on the wire, essentially allowing for partial updates.

This example shows an implementation of Patch for Products:

public HttpResponseMessage PatchProduct(int id, Delta<Product> product)
{
    Product dbProduct = _db.Products.SingleOrDefault(p => p.ID == id);
    if (dbProduct == null)
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }

    product.Patch(dbProduct);
    _db.SaveChanges();

    return Request.CreateResponse(HttpStatusCode.NoContent);
}

Notice that the method receives a Delta<Product> rather than a Product.

We do this because, if we use a Product directly and set the properties that came on the wire as part of the patch request, we would not be able to tell why a property has a default value. It could be because the request set the property to its default value or it could be because the property hasn’t been set yet. This could lead to mistakenly resetting properties the client doesn’t want reset. Can anyone say ‘data corruption’?

The Delta<Product> class is a dynamic class that acts as a lightweight proxy for a Product. It allows you to set any Product property, but it also remembers which properties you have set. It uses this knowledge when you call .Patch(..) to copy across only properties that have been set.

Given this we simply retrieve the product from the database and then we call Patch to apply the changes requested to the entity in the database. Once that is done we call SaveChanges to push the changes to the database.

Deletes (DELETE requests)

To delete an entity you send a DELETE request to it’s editlink, so this is similar to GetById, Put and Patch, except this time Web API will look for an action prefixed with Delete, something like this:

public HttpResponseMessage DeleteProduct(int id)
{
    Product toDelete = _db.Products.FirstOrDefault(p => p.ID == id);
    if (toDelete == null)
    {
        throw ODataErrors.EntityNotFound(Request);
    }
    _db.Products.Remove(toDelete);
    _db.SaveChanges();

    return Request.CreateResponse(HttpStatusCode.Accepted);
}

As you can see if the entity is found it is simply deleted and we return an Acccepted response.

Following Navigations

To address related entities in OData you typically start with the Uri of a particular entity and then append the name of the navigationProperty. For example to retrieve the Family for Product 15 you do a GET request here: ~/Products(15)/Family.

Implementing this is pretty simple:

public ProductFamily GetFamily(int parentId)
{
     return _db.Products.Where(p => p.ID == parentId).Select(p => p.Family).SingleOrDefault();
}

This matches the ODataRouteNames.PropertyNavigation which you’ll notices uses {parentId} rather than {id}, if it was id, this method would also be a match for the ODataRouteNames.GetById route, which would cause problems in the action selector infrastructure. By using parentId we sidestep this issue.

Creating and Deleting links

The OData protocol allows you to get and modify links between entities as first class resources. In the preview however we only support modifications.

To create links you POST or PUT a uri (specified in the request body) to a url something like this: ~/Products(1)/$links/Family.

The uri in the body points to the resource you wish to assign to the relationship. So in this case it would be the uri of a ProductFamily you wish to assign to Product.Family (i.e. setting Product 1’s Family).

Whether you POST or PUT depends on the cardinality of the relationship, if it is a Collection you POST, if it is a single item (as Family is) you PUT. Both are mapped through the ODataRouteNames.Link route, which is set up to handle creating links for any relationship. Unlike following Navigations it feels okay to use a single method because the return type always the same, i.e. a No Content response:

public HttpResponseMessage PutLink(int id, string navigationProperty, [FromBody] Uri link)
{
    Product product = _db.Products.SingleOrDefault(p => p.ID == id);

    switch (navigationProperty)
    {
        case “Family”:
            // The utility method uses routing (ODataRoutes.GetById should match) to get the value of {id} parameter
            // which is the id of the ProductFamily.
            int relatedId = Configuration.GetKeyValue<int>(link);
            ProductFamily family = _db.ProductFamilies.SingleOrDefault(f => f.ID == relatedId);
            product.Family = family;
            break;

        default:
            throw ODataErrors.CreatingLinkNotSupported(Request, navigationProperty); 
    }
    _db.SaveChanges();

    return Request.CreateResponse(HttpStatusCode.NoContent);
}

The {navigationProperty} parameter from the route is used to decide what relationship to create, also notice the [FromBody] attribute on the Uri link parameter it is vital otherwise the link will always be null.

If you want to remove a relationship between entities you send a DELETE request to the same URL, the code for that looks like this:

public HttpResponseMessage DeleteLink(int id, string navigationProperty, [FromBody] Uri link)
{
    Product product = _db.Products.SingleOrDefault(p => p.ID == id);
    switch (navigationProperty)
    {
        case “Family”:
            product.Family = null;
            break;

        default:
            throw ODataErrors.DeletingLinkNotSupported(Request, navigationProperty);
    }
    _db.SaveChanges();
    return Request.CreateResponse(HttpStatusCode.NoContent);
}

Conclusion

If you implement all these methods for each of your OData EntitySets you should have a compliant OData service. Of course this code is only at preview quality so is likely to have bugs, that said I hope you’ll agree this provides a good foundation for creating OData services.

We are currently thinking about adding support for: inheritance, OData actions & functions, etags and JSON Light etc. And of course we want to hear your thoughts so we can incorporate your feedback!

Next up

This blog post doesn’t cover everything you can do with the Preview, you can use:

  • ODataQueryOptions rather than [Queryable] to take full control of handling the query.
  • ODataResult<> to implement OData features like Server Driven Paging and $inlinecount.
  • EntitySetController<,> to simplify creating fully compliant OData entitysets.

I’ll be blogging more about these soon.

Enjoy!

Comments (52)

  1. Anonymous says:

    I believe there is an issue with the NuGet Package.  I'm getting this message:

    Attempting to resolve dependency 'System.Spatial (= 5.0.1)'.

    Already referencing a newer version of 'System.Spatial'.

    I don't see this DLL anywhere in my Project.  So I tried adding the NuGet Package – System.Spatial -Pre, but noticed that it's version is 5.1.0, not 5.0.1 (as stated in the Dependencies on the NuGet Page). Am I doing something wrong?

  2. Anonymous says:

    Hello,

    Could we use it with EntityFramework 4.3 ?

  3. Anonymous says:

    I've just upgraded my API project to the new packages released today.

    However, when I try to install WebApi.OData, it seems to have a redundant reference to System.Spatial.

    Which breaks, because it requires the explicit 5.01 version, when I'm on System.Spatial 5.1.0-rc1.

    Here's my output:

    PM> Install-Package Microsoft.AspNet.WebApi.OData -Pre

    Attempting to resolve dependency 'Microsoft.Data.Edm (= 5.0.1)'.

    Attempting to resolve dependency 'Microsoft.Data.OData (= 5.0.1)'.

    Attempting to resolve dependency 'System.Spatial (≥ 5.0.1)'.

    Attempting to resolve dependency 'Microsoft.Net.Http (≥ 2.0.20710.0 && < 2.1)'.

    Attempting to resolve dependency 'System.Spatial (= 5.0.1)'.

    Install-Package : Already referencing a newer version of 'System.Spatial'.

    At line:1 char:16

    • Install-Package <<<<  Microsoft.AspNet.WebApi.OData -Pre

         + CategoryInfo          : NotSpecified: (:) [Install-Package], InvalidOperationException

         + FullyQualifiedErrorId : NuGetCmdletUnhandledException,NuGet.PowerShell.Commands.InstallPackageCommand

    Notice that System.Spatial is referred on lines 4 and 6.

    thanks,

    Jaume

  4. Anonymous says:

    This is really cool, looking forward to digging into it.

    The links to the sample projects in the post aren't quite right, but I was able to find them here: aspnet.codeplex.com/…/903afc4e11df

  5. Anonymous says:

    For those who tried the nuget packages earlier and had problems. There were some issues with dependencies caused by a new version of ODataLib going out at the same time as this Nuget package. This mean't some dependency version numbers didn't line up.

    Hopefully this has been resolved by the time you read this!

  6. When will projection be supported

  7. Alex D James says:

    @Steve, thanks for that I think I've updated things now.

  8. Alex D James says:

    @Doug. We are hoping to add projection support in the next 3-4 months. That said projections does present some real challenges to the existing web-api infrastructure, so I'm not quite sure how this will work yet!

  9. Alex D James says:

    @Kakone. Yes. You can use it with any backed. Basically all it needs is an implementation of IQueryable. And even that isn't mandatory if you drop down to using ODataQueryOptions instead of using [Queryable]

  10. Anonymous says:

    This is great. Happy to have it out at the release of VS 2012 and .NET 4.5 as promised.

    I have the demo up and running but I don't understand how to implement ODataResult. You mentioned an upcoming blog post but if you could just get me pointed in the right direction with a line or two of code that'd be great.

    I'm using ODataQueryOptions and that's going well. It's just ODataResult I'm struggling with. It appears to be designed as a wrapper for the OData responses adding count and paging details. I'm just not sure how to build the response or what I should be using as my controller's return type. Think I just need to see some code.

  11. Anonymous says:

    Oh, and the most important parameter is still not supported!!! $select is doa! Not good at all.

    At least with the old hack code that was in I had $filter, $top/take, $inlinecount, $format, $skip, and a few others and it worked with ALL IQueryables.

    This solution doesn't handle half of the odata spec, AND doesn't work properly with anything other than EntityFramework.

    I'd rather have the hack stuff from the RCs back!

  12. Alex D James says:

    @Mark,

    You use ODataResult<T> like this (pseudo-code):

    public ODataResults<Product> Get(ODataQueryOptions options)

    {

       var results = (options.ApplyTo(_db.Products) as IQueryable<Product>);

       var count  = results.Count;

       var limitedResults = results.Take(100).ToArray();

       return new ODataResults(results,count,null);

    }

    This basically mimics the old ResultLimit stuff. Implementing Service Driven Paging is more involved, you have to work out a nextLink (null above) that continues from this page…

    I'll need a full blog post to cover that.

    -Alex

  13. Alex D James says:

    @James

    Our goal for the preview was to get back to parity with the old implementation of [Queryable] with a better foundation which we've done. Next up is support for things like $select etc. In the meantime if you want to implement $select you can always drop down to ODataQueryOptions and work it out yourself.

    Oh and this supports any backend, not just EF, yes the sample uses EF but actually it was designed to work against any backend, including those that don't have IQueryable implementations. ODataQueryOptions gives you really low level access so you can go directly from an AST representing the query to whatever query language you have. For example you could translate the AST into SQL directly and use ADO.NET directly bypassing EF if you want.

    -Alex

  14. Anonymous says:

    Oh and $orderby doesn't support multiple fields

  15. Anonymous says:

    @Alex: The problem with ODataQueryOptions if I understand correctly is that I would have to put these on every single method to implement them. Obviously this isn't DRY. I should be able to go and intercept these in an override of the Queryable attribute and handle them myself there in a global way.

  16. Alex D James says:

    @James

    $orderby does support multiple fields. It does have some limitations though, currently it doesn't support paths i.e. $orderby=Category/Name but that will come soon.

    The point with ODataQueryOptions is you have access to exactly the sample building blocks as [Queryable], which basically just called _options.ApplyTo(query) for you. If you could easily write your own Action Filter that does this work globally if you need it before we add it to [Queryable]

  17. Anonymous says:

    Okay. I just had a namespace issue and was referencing the abstract class. This works fine:

    public ODataResult<Product> Get(ODataQueryOptions options)

    {

      var results = (options.ApplyTo(_db.Products) as IQueryable<Product>);

      var count  = results.Count;

      var limitedResults = results.Take(100).ToArray();

      return new ODataResult<Product>(results,null,count);

    }

  18. Azzlack says:

    Is it possible to use the [Queryable] attribute with HttpResponseMessage as the return type?

  19. Alex D James says:

    @Azzlack

    Currently it isn't but I can see how it would be implemented. Can you explain your scenario?

    -Alex

  20. Azzlack says:

    Yes. I need to be able to return custom response types based on the the result of a database query.

    I.e. A function Get() that is supposed to get all Buildings from a database. If there are no buildings, then I want to return a 204 No Content with a custom message as the content. If everything is ok, then return the standard 200 OK with the result as IQueryable. Secondly, I would also like to be able to set other http headers for the response.

    Also, it seems like a lot of the source code for the OData support (ODataQueryOptions and ODataQueryContext) is heavily geared towards EF, and as far as I could see, very difficult to adapt to other things like MongoDB (which is what I'm using).

    Here is a sample:

    [Queryable]

    public async Task<HttpResponseMessage> Get()

    {

           var result = await this.buildingRepository.Get();

           // Return 204 if there is no content

           if (result == null || !result.Any())

           {

                   return Request.CreateResponse(HttpStatusCode.NoContent, "No Buildings exist");

           }

           return Request.CreateResponse(HttpStatusCode.OK, result.AsQueryable());

    }

  21. Anonymous says:

    Azzlack,

    That's not how OData works when there is no data. The point of the library is to allow you to build WebApi code that also conforms to the OData spec. What you suggest would not work with an OData client library.

    It sounds like what you want is just a regular WebApi library.

    Robert McLaws

    twitter.com/robertmclaws

  22. Anonymous says:

    Azzlack,

    It sounds like what you want is to just leverage $filter but to not implement an OData compliant service – we did something similar recently (see part 4 of this series of blog posts http://bit.ly/etwebapi) – but I'm not sure this is really something the Microsoft.AspNet.WebApi.OData library should aim to really make "easy" or even possible, as it's steering the developer away from the pit of success that is being OData compliant.

    In fact I think if I did this again I would drop using the $ at the start of the parameter names to avoid the confusion with a real OData compliant service.

    @AlexJames – am I right in thinking that what I do here:

    blog.bittercoder.com

    Wouldn't be easy to build with current preview? (I haven't had a chance to install and use the preview just yet.. but looking forward to giving it a test drive this week 🙂

    Cheers,

    Alex

  23. Azzlack says:

    @Robert McLaws, @Alex Henderson Yes. I only need to be able to use $filter, $skip, etc. not be completely OData compliant.

    Your blog post looks really interesting. Maybe it will solve my problems.

  24. Alex D James says:

    @Azzlack

    I'd advise against returning 204 No Content whether you are using OData format or not. My reasoning is this, client libraries often convert the results of queries to collections or enumerations, and if you return No Content rather than an empty enumeration you are forcing clients to write special code to either deal with exceptions or use libraries that seemlessly consume No Content and convert into empty enumerations.

    We made a similar mistake with the first version of the Data Services client library, for example this query

    GET ~/Products(5) could easily return 404 Not Found, yet it is generated by this LINQ code on the client:

    ctx.Products.Where(p => p.ID == 5)

    And from the client programmers perspective there is no difference between that and say:

    ctx.Products.Where(p => p.Category.Name == "Food")

    If there were no matches for either query, you would expect the same coding patterns to apply, unfortunately in the first we raised an exception, in the second you got an empty enumeration (which is what people generally want).

    We addressed this by adding a configuration switch to convert 404's into empty enumerations…

    So if you use 204 No Content you are

    1) Not OData compliant (which it sounds you are fine with)

    2) Complicating client logic

    As for ODataQueryOptions and ODataQueryContext being tied to EF. Quite the contrary, they are not at all, indeed they are designed to be used with any backend, hence you get access to an AST. We do though make it easy to convert that AST into expressions and bind to an IQueryable (any IQueryable) if you have one.

    -Alex

  25. Azzlack says:

    I see your point. Will returning 204 No Content and an empty enumeration  be better?

    According to @Henrik F Nielsen returning result.AsQueryable() should work with OData queries. However, I keep getting errors like this one: The method 'BaseMongoEntity.get_Id' is not a property accessor" and "The type 'Time' cannot be configured as a ComplexType. It was previously configured as an EntityType.".

    The Id property from the first one is set on an abstract base class.

    The Time property is a new property, meaning that some entities in the database has the value null.

  26. Alex D James says:

    @Azzlack,

    Today you need strongly typed properties for all your OData Properties. I.e. we don't yet support using methods like get_Id(). It is however on the roadmap to add 'virtual properties' that map to a custom get() and set() method.

    Re – Time: it is hard to know which limitation you are running into: I'd be merely speculating until I get a better idea of your class structure.

    -Alex

  27. Alex D James says:

    @Azzlack,

    Oh and I don't know how you return an empty enumeration and 204 No Content, I think 204 implies no body right, so where would you put the empty enumeration?

    -Alex

  28. Alex D James says:

    @Alex H

    I think what you are doing with $filter would be trivial to support using the new bits.

    You get access to an AST representing the Filter, i.e. ODataQueryOptions.Filter.QueryNode and that you could translate directly to whatever query language you need.

    Alternatively you can use ODataQueryOptions.Filter.ApplyTo(queryable) too 🙂

    If you drop down to ODataQueryOptions you can explicitly fail if there is something in the query you don't support. ODataQueryOptions.ApplyTo basically automatically applies the Filter, OrderBy, Top, Skip for you.

    Note: unhandled OData options as raw strings are available via ODataQueryOptions.RawValues.

  29. Azzlack says:

    @Ales D James

    Here is the property it is complaining about with the get_Id error:

       public abstract class BaseMongoEntity

       {

           [BsonId]

           public string Id { get; set; }

       }

    And here is the Time class:

    public class Time

       {

           public int Hours { get; set; }

           public int Minutes { get; set; }

           public int Seconds { get; set; }

           public double Milliseconds { get; set; }

       }

  30. Anonymous says:

    Alex, I'm getting a 206 Not Acceptable for any action on a GET that passes in $ options. Any ideas on how to troubleshoot?

    Thanks!

    -Robert

  31. Having a small issue with the $metadata being produced using webapi. I am using CodeFirst and i find that when I expose my context with the EntitySetController with all of the routes set up as explained in the post, I get my metadata when requested, but it doesn't have any of the validation build in. When I expose the same CodeFirst model using WCF Data Services, the metadata contains all validation.

    For example, when using the WebApi EntitySetController, i get metadata like:

    <Property Name="Id" Type="Edm.Int32" Nullable="false"/>

    <Property Name="Email" Type="Edm.String"/>

    And when using the WCF DataServices, I get the following metadata for the exact same properties

    <Property Name="Id" Type="Edm.Int32" Nullable="false" p8:StoreGeneratedPattern="Identity"/>

    <Property Name="Email" Type="Edm.String" Nullable="true" MaxLength="4000" Unicode="true" FixedLength="false"/>

    Is this just due to the Microsoft.AspNet.WebApi.OData library being alpha, or am I missing something in how the EdmModel or ODataMediaTypeFormatter is set up?

  32. Anonymous says:

    @Alex

    Thanks for the update – definitely going to have to give the AST a further look, sounds like potentially we could leverage that with some basic translation/visitors to get the queries expressed as a query object we can pass into our business layer for those parts of the app which don't use our bespoke query language (translating to our bespoke query language would be even easier as that's expressed as an AST as well… )

    Keep up the great work 🙂

    Cheers,

    Alex H

  33. I've found that if you have a Get method decorated with the Queryable attribute it will fail with "navigation property not found" if the objects returned extend a base class with a List in it.  Ex:

    public class Base

    {

        int Id {get; set;}

        List<string> Links {get; set;}

    }

    public Company : Base

    {

        string Name { get; set;}

    }

    [Queryable]

    public IQueryable<Company> Get()

    {

    ….

    }

  34. Alex D James says:

    @clients

    I think this is a misunderstanding about how the Web API model builders work. The Model builders, both explicit and by convention, don't know about the underlying EF model, they create/infer a completely new model. So it is not surprising they are different. Perhaps there are things missing from the Model Builder I invite you to create issues for them on the CodePlex site.

    That said we would like to add a way to initialize the OData model from the EF model, but it is very important that the two can differ, i.e. you would start from the EF model and then tweak to hide properties sets etc, that shouldn't be exposed via the service.

  35. Alex D James says:

    @David

    Where does the error occur? Do you build the model explicitly or do you let [Queryable] build it for you.

    Either way it sounds like a bug. Can you report it on the CodePlex site?

  36. Alex D James says:

    @Robert

    I think I just saw a bug (and fix) for this aspnetwebstack.codeplex.com/…/327

  37. Alex D James says:

    @Ove

    Sorry from what you've shown me I have no idea what is going on. Perhaps you can isolate the issue and file a bug on the codeplex site?

  38. Alex D James says:

    @georgiosd

    Yes as a protocol designer I've run into issues with not having places to annotate data. So things like "results" wrappers are vital.

    In OData V3 the default format is something called JSON Light, which looks a lot like the JSON you might write by hand. It is structured such as to provide convenient places for annotations. The ODataLib foundation upon which the Web API OData formatter is built is currently being extended to support JSON light and annotations. Once that is done we will update Web API so that it supports this light form for JSON and we'll then look at providing mechanisms for you to annotate responses too.

    That said I think that is a little while off, probably at least 3 or so months.

    See this for more on JSON light: skydrive.live.com

  39. @Alex D James

    Thanks for the information. I will look into the Model Builder some more. I agree that being able to change the external facing model is very important.

  40. Anonymous says:

    I have a nulllable DateTimeOffset property declared in my model.  In the rc of the web api, I used a filter query as follows $filter=LastTransmissionTimestamp eq null which worked fine.  With the release version and the new odata extensions, this same filter now gives the following error:

    A binary operator with incompatible types was detected. Found operand types 'Edm.DateTimeOffset' and '<null>' for operator kind 'Equal'.

    Is there a work around/fix for this issue?

    Thanks

  41. Alex D James says:

    @Kevin.

    Unfortunately not. The issue is in ODataContrib, it has a lot of problems with DateTimeOffset at the moment,

    My best guess is that fixes for this are about 6 weeks away.

  42. Can we get an example of how to use the patch method.  I am trying something like this

    <Employee>  

      <BirthDate>1977-01-01T00:00:00</BirthDate>

    </Employee>

    However I get

    <Error><Message>The request is invalid.</Message><ModelState><updates>Error in line 1 position 12. Expecting element 'DeltaOfEmployeepwN2u9vl' from namespace 'schemas.datacontract.org/…/System.Web.Http.OData&.. Encountered 'Element'  with name 'BirthDate', namespace ''. </updates></ModelState></Error>

  43. Anonymous says:

    I'm getting the following exception, any ideas?

    Method 'get_Handler' in type 'System.Web.Http.WebHost.Routing.HostedHttpRoute' from assembly 'System.Web.Http.WebHost, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' does not have an implementation.

  44. @Alex James

    Great work on this so far! I'm liking the ease of setup combined with the ability to easily explicitly control things (like ODataQueryOptions). Do you know what the rough timeline for this to be RTM is? Thanks again, keep up the good work!

  45. Anonymous says:

    I've built a small test app and everything seems to be working fine, so I thought I'd hook it up to PowerPivot and connect to the OData feed. I get no exception thrown within my test app, but PowerPivot reports 'The remote server returned an error: (500) Internal Server Error'. Any idea what the problem might be?

  46. Anonymous says:

    Alex – a few places in the article you state that [Queryable] needs to be able to figure out the ID property, or you need to setup the model to indicate the ID property.

    What are the rules for oData figuring out the ID property?  Name? Case sensitive?  First int field?  If possible looks at EDXM model for key?

  47. Anonymous says:

    Please post an update to the microsoft.aspnet.webapi.odata package… I can't update my Odatalib nuget package because you guys have it hard coded to a specific version.

  48. Anonymous says:

    @Blake, add this source to your NuGet package source list http://www.myget.org/…/aspnetwebstacknightly

    This will allow you to install using NuGet from the nightly build

  49. I'm getting confused about OData flow:

    • Web API executes GetAllUsers() against sql server for expample
    • Database server returns to Web API a list containing all users

    • Web API filters the data returned by Database throughout linq (if the client wants to filter something)

    • Web API returns to the client the filtered Data

    Being more accurate I am asking myself:

    you send your sentence to the database server and then if you want to filter OData filter it for you BUT after receiving the data from the database server.

  50. Alex D James says:

    @SoyUnEmilio

    The filtering occurs in the database if your action returns IQueryable, and in memory if your action return IEnumerable… so you are in control here.

    -Alex

  51. Anonymous says:

    @Alex D James

    Just wondering if that 'Server side paging' blog post was far away? I am keen to implement something using WebAPI and Odata but am holding out until I can read a bit more on it