Supporting OData $inlinecount with the new Web API OData preview package

Strath

Supporting OData $inlinecount with the new Web API OData preview package

Because OData is super useful

OData support in Web API is arguably one of its hottest features. However, it’s support in Web API has been a bumpy ride – initially, OData was supported in a limited way only, and ultimately ended up being yanked altogether from the Web API RTM. It is however stil lpossible to use OData with Web API, only in a slighly different form , as an external NuGet package, which, in its pre-release alpha format was published last Wednesday, along the Web API RTM release.

This package is called Microsoft ASP.NET Web API OData and is a joint effort by Microsoft’s Web API and OData teams. Alex James has written a great introduction to the package, so I recommend reading it.

In the meantime, let me show you how to add $inlinecount support as for the time being, it’s still not provided there out of the box.

What’s supported currently?

If you use the OData package, you get the QueryableAttribute, with which oyu should be familiar from earlier Web API releases. It works pretty much as it used to (however there are some breaking changes, i.e. the lack of ApplyResultsLimit method) and gives you support for $filter, $orderby, $skip and $top OData queries.

So, no $inlinecount. Therefore, let’s tackle the problem.

What is $inlinecount anyway?

If you need a good definition, odata.org is a great resource for OData related information. In this case it explains $inlinecount accordingly: “URI with a $inlinecount System Query Option specifies that the response to the request includes a count of the number of Entries in the Collection of Entries identified by the Resource Path section of the URI. The count must be calculated after applying any $filter System Query Options present in the URI.”

The possible options for $inlinecount are:
allpages – equivalent to asking “please show me the count of items”
none – this is equivalent to not having inlinecount in the query at all

While normal API responsewould look something like this:

Response that includes $inlinecount would look:

Which indicates the caller, that you got one “blog” object result, but there are actually 3 total in store.

Adding a simple wrapper model

In order to support inlinecount we need a wrapper model, that would wrap our IEnumerable results and expose two properties: Count and Results.

It’s very simple:

Adding the action

The action will need to return System.Web.Http.OData.ODataResult<T>, not a regular IQueryable or IEnumerable. This will allow us to package the “count” of items with it and flush it down the pipeline.

However, we will then have to intercept this object and assign it to our wrapper model. If you ask why we don’t return that custom wrapper format directly – we have to use ODataResult as return Type, as otherwise the model binder would complain about not being able to bind ODataQueryOptions:

Category=’System.Web.Http.ModelBinding’, Id=e93b9f25-4c5c-4324-8294-068a004fa597, Message=’UserMessage=’The server failed to retrieve a inner generic type from type ‘webapi_metadata.Models.ODataMetadata`1[[webapi_metadata.Models.Blog, webapi-metadata, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]’ against action ‘GetBlogs’ on controller ‘Blog’.”, Operation=ODataQueryParameterBinding.ExecuteBindingAsync, Status=500 (InternalServerError)

And by accepting ODataQueryOptions into the action, we get access to all OData parameters passed by the user.

Now, this action is pretty ugly, but that’s what you need to do to process the OData query options properly. Of course this could easily be abstracted away to a separate method to provide clean, reusable code, but that’s really not the scope of this post.

So first, we grab the IQueryable or IEnumerable collection of our data from our repository. Then we go ahead and manually apply all OData queries, such as filter, orderby, skip, top.

Notice that we count the items, for the purpose of our $inlinecount, after applying $filter. The reason for that is, as mentioned in the definition before, OData spec defines that inlinecount should be calculated after filter, but before other queries.

We return the object from the action as ODataResult<Blog> – and we pass to it the result collection, next page link (null) and the inlinecount.

Now, you might think that it’s the end, but it’s not, because ODataResult<T> is IEnumerable itself which means when it is passed to the client it will arrive only as the collection of results, and there will be no indication of count.

Hijacking ODataResult<T> with a message handler

The easiest thing to do now, is to intercept the response and reassign it from ODataResult to our custom ODataMetadata.

In this case, what we do is:
1) Check if the response content object is ODataResult – if not, do not process
2) We cast it twice, once as IEnumerable – to get access to the results collection
3) The second cast is to ODataResult which is an asbtract class exposing only the count
4) Finally, we use the result collection and the inlinecount to construct the ODataMetaData and return it as a response. Notice that I check for the count being null – if you recall fro mthe controller’s action, if the count is null it means the $inlinecount was not requested by the user. This means, we don’t need to modify the response object at all, and can continue flushing down the default ODataResult>T< to the client.

Trying it out

1. Normal – no inlinecount – /api/blog/

2. With $inlinecount & $top – /api/blog?$top=2&$inlinecount=allpages

3. With $inlinecount & $top & $skip- /api/blog?$top=2&$skip=1&$inlinecount=allpages

4. With $filter & $inlinecount – /api/blog?$filter=BlogId gt 1&$inlinecount=allpages
In this case “count” refers to results after filter has been applied.

Summary

This may not be the prettiest solution in the world, and it’s a little hacky, but I hope it will point you in the right direction. Web API OData package is going to support this out of the box (via QueryableAttribute) at some point anyway, but it’s always interesting to hack away at things in the meantime :)

This is also a good replacement technique for the missing ApplyResultsLimit override of the QueryableAttribute which was the focal point of my previous post on the OData in Web API. That solution no longer works with Web API RTM, due to the mentioned removal of “ApplyResultsLimit”, but you could reimplement that entire solution using the approach shown here.

Update – 6h after this has been originally posted :)

Following a suggestion from Alex James, let me mention a slightly different way of implementing this.

Your action will now look like any typical action:

Notice how clean it is, and that it is not concerned about any of the OData extravaganza.

Now, instead of a handler, you can use a custom version of a QueryableAttribute.

So what’s happening there:
1. We grab $inlinecount from the request querystring, and if it’s equal to allpages, it means that the client wants to have the count – so we count all the items in the collection
2. We let the OData QueryableAttribute do its thing and apply all the queries. Notice that, I overrode ValidateQuery, otherwise the current version of the QueryableAttribute will thrown an exception when it sees $inlinecount.
3. After the OData queries are applied, we can continue morphing the response to what we want. If the $inlinecount was requested by the caller, we return our custom ODataMetadata object, if not, we countinue just returning the collection.

Now, if you opt for this approach you lose some, win some.

The pluses:
– your action is back to being very clean and you can again return your POCO from it
– you can use an attribute instead of handler (probably more readable, and doesn’t run for all requests)

The minuses:
– the $inlinecount now always refers to your entire collection prior to applying OData query, which means the $inlinecount will be calculated after $filter and before other, but rather before everything
– you need to implement your own validation check against disallowed OData queries

Be Sociable, Share!

  • Pingback: Dew Drop – August 23, 2012 (#1,387) | Alvin Ashcraft's Morning Dew

  • Raghuram

    Great post. A little suggestion is that you don’t have to apply the individual query options one-by-one. you can use the method ApplyTo on ODataQueryOptions to apply all of them.

    Also, in your message handler you can use the Request.CreateResponse overloads to create the response. The solution that you have is using only one formatter and thus doesn’t handle Accept headers and other content negotiation that webapi does for you.

    • Filip W

      Thanks, a lot for this comment! I changed the code in the action as you suggested (I still check for the filter individually, as inlinecount is supposed to happen after this one is applied). This is much cleaner now.

      As far as response creation in the handler.. yes that was my overlook, I changed that as well. However for some reason conneg always resorts to JSON anyway. I suspect the reason is that DCS cannot handle ODataResult serialization, and subsequently the generic ODataMetadata<object>?

  • Pingback: Distributed Weekly 169 — Scott Banwart's Blog

  • Pingback: Dew Drop – August 24, 2012 (#1,388) | Alvin Ashcraft's Morning Dew

  • Eddie Wood

    Nice blog post and just in time!

    I’ve been struggling Infragistics NetAdvantage Jquery Grid widget and various DataSources to get them working with the WebAPI. By default their DataSource appends the $inlinecount param, which the ODataQueryOptions class chokes on. I’ve experimented in the client-side implementing my own WebAPIDataSource that inherits from RemoteDataSource, to work around this and other problems, by overriding various DataSource functions as needed. That hasn’t worked out so far.

    My next thought was to implement an alternative to the ODataQueryOptions and QueryableAttribute classes. I’m glad to see I’m not the only one who found the default implementations of these two classes wanting.

    • Filip W

      Thanks Eddie, really appreciate the comment. Ultimately $inlinecount will be supported by the package, but in the meantime we can get by with these workaround approaches.

  • Shardul Kulkarni

    Thanks for the great post!
    I played around a bit with Alex James solution and came up with an update. Please refer to https://github.com/shardulk/Public/blob/master/CustomQueryableAttribute.cs

    Its not very complicated to get the inline count AFTER the filtering has been applied.

    • Filip W

      hey, thanks!

      actually the problem is the definition of $inlinecount count says it should be the count of items *after* applying $filter but *before* applying other OData queries such as $top and so on.

      So if I ask for $top=2, and the collection size on the server is 100, your solution would return $inlinecount=2 which is incorrect, the client should get 100 – that’s why I mentioned that the attribute approach for implementing this will not be academically correct.

      I think the lesser evil is to return $inlinecount *before* applying OData query, as at least it gives the client some idea of the size of the collection, rather than after applying OData queries, since then it just counts whatever is being returned to the client and that doesn’t add any value – after all, the client could count that himself.

      • Shardul Kulkarni

        Yes. I tried with $top and the attribute based solution only returns the number of records filtered by $top.

        Thanks for the clear explanation!

  • Callum

    I’ve tried to implement your solution in a project but my API controller doesn’t seem to call ‘ODataHandler’? I’m not sure it is ‘hijacking’ it?

    • Filip W

      hi!

      make sure you register the handler in the global configuration – so in the Global.asax or your WebApiConfig class add:

      GlobalConfiguration.Configuration.MessageHandlers.Add(new ODataHandler());

      • Callum

        Hi, Thanks for the quick reply!

        I put it inside my Application_Start() method in Global.asax and my API call still only returns my objects, not the count

        • Filip W

          Could you post your action method here?

          • Callum

            My Global asax Application_Start? Or my API Get?

          • Filip W

            your Get method

  • Victor

    Can you maybe offer the solution for download? Thnx

  • Andrea

    i like your clean solution but i can’t accept the “– the $inlinecount now always refers to your entire collection prior to applying OData query, which means the $inlinecount will be calculated after $filter and before other, but rather before everything”.

    So, my solution is:
    in the OnActionExecuting i look for the inlinecount, top and skip.. if the inlinecount==allpages i remove these params from the uri.
    The OnActionExecuted, if inlinecount!=allpages, responds with the base method otherwise, if the inlinecount==allpages execute the base methods, count the records then paginate as asked by $top and &skip wrapping the result in your ODataMetadata.
    It works for me.

    What do you think about it?

    • http://twitter.com/invalid_arg Mat McLoughlin

      How do you remove the top and skip from the uri?

    • Luis Arpasi

      can you plz share your solution?

  • http://ict.ken.be Ken

    Nice work, however the Microsoft.AspNet.WebApi.OData.0.1.0-alpha-120815 does not contain a ValidateQuery to override. So it is throwing ‘The query parameter ‘$inlinecount’ is not supported.’.

    What version are you using?

  • http://ict.ken.be Ken
  • Pingback: Web API Fall Update – ODataResult<T> | Himanshu's Devblog

  • Srinivas Korvi

    Great Post! Thanks a lot! Very much helpful.

  • jack straw

    I feel that Andrea’s solution/compromise is the best approach for my needs. Like her, I am unable to live with the “– the $inlinecount now always refers to your entire collection prior to applying OData query, which means the $inlinecount will be calculated after $filter and before other, but rather before everything”.

    Something I wasn’t too happy about with her solution was that the query is always going to be for the entire, filtered result set (not taking into account the $take, $skip). But then afterwards these operations would be applied (so that first the correct count value could be obtained) and the result is returned in the response.

    Upon further inspection though, it appears that your solution is behaving in the same manner, as line 10 “count = result.Count();” inside GetBlogs() will enumerate the collection causing the query to be executed, if I’m not mistaken.

    Although I feel both of these solutions are way ahead of other technologies being employed (and I’m really digging the JS to API direct, declarative communication), I’m left feeling that the lesser of the two evils (wrt deferred query execution) would be Andrea’s, as we still are left with the very clean controller method. Also, with this logic abstracted out to the Attribute class, I feel that it will be more obvious for me to see when the API provides native support for $inlinecount….so I can rip it out of one place only, and be more obvious.

    Either way, I thought I would post this opinion on here for others looking to implement this solution.

    Flip, I thought this post was extremely informative and I’m very glad you took the time. Thanks!

    • http://www.strathweb.com/ Filip W

      thanks! and yeah, I very much agree with everything you just said, Andrea had a very nice idea

    • jack straw

      I take this back…removing the OData query string params from the request query string, make doing a Redirect necessary…think this tips the scale back to the original, proposed solution….IMO….(until this is natively supported)

      • jack straw

        Odd…When it was all said and done…I did the following:

        1) No ODataMetadata object – as I found that ODataResult provided the ‘Items’ property (with the results data) and a ‘Count’ property with the ‘$inlineCount’ value correctly calculated

        2) Went with Flip’s original approach, as I wanted to avoid redirecting (the URI minus the $take $skip query string parameters)

        3) Just letting ODataResult be returned from the controller method (as said above it provides ‘Items’ and ‘Count’)

        4) Hooked up a custom message handler to determine if the request did NOT request ‘$inlineCount’ if so, and the response is ODataResult…then I rip out the value of the ‘Items’ property and replace the response content with this…

        Overall, I’ll be keeping an eye out for when Web API’s OData implementation will support ‘$inlineCount’…also found this additional resource for anyone interested…appears they chose Andrea’s approach…

        http://thedevstop.wordpress.com/2012/04/05/adding-odata-inline-count-support-to-the-asp-net-web-api/

  • Chris McCown

    This is a great post, but with the newest release of web api, there’s a change that needs to be made, or at least noted. It looks like the ODataResult class is now called PageResult class.

  • Wayne Bradney

    With this article I thought I’d finally found a clean way (ie almost no code) to hook together the Kendo Grid with NHibernateQueryable. Unfortunately it doesn’t work if T contains an Enum property – ODataQueryOptions.Filter.ApplyTo throws:

    A binary operator with incompatible types was detected. Found operand types ‘Edm.String’ and ‘Edm.Int32′ for operator kind ‘Equal’.

    It seems that Enums are treated as strings, which is unfortunate. I can’t change the NHibernate entity – is there another way around this?

  • Wayne Bradney

    With this article I thought I’d finally found a clean way (ie almost no code) to hook together the Kendo Grid with NHibernateQueryable. Unfortunately it doesn’t work if T contains an Enum property – ODataQueryOptions.Filter.ApplyTo throws:

    A binary operator with incompatible types was detected. Found operand types ‘Edm.String’ and ‘Edm.Int32′ for operator kind ‘Equal’.

    It seems that Enums are treated as strings, which is unfortunate. I can’t change the NHibernate entity – is there another way around this?

  • Sunil Thotapalli

    Nice blog post, I am using Web Api 2.2 and trying to change the OData response in custom message handller. The incoming response is of type Expression.SelectExpandBinder.SelectSomeAndInheritance, when actionExecutedContext.Request.CreateResponse(HttpStatusCode.OK, new ODataMetadata(robj, originalsize)); method is called it returns statuscode 406 not acceptable. Any help is appreciated.
    Thanks