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

Β· 1836 words Β· 9 minutes to read

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][1], 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][2] 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][3] 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:

[  
{  
"BlogId": 2,  
"Address": "http://west-wind.com/weblog/",  
"Author": "Rick Strahl"  
}  
]  

Response that includes $inlinecount would look:

{  
"Results": [  
{  
"BlogId": 1,  
"Address": "/",  
"Author": "Filip W"  
},  
],  
"Count": 3  
}  

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:

public class ODataMetadata<T> where T : class  
{  
private readonly long? _count;  
private IEnumerable<T> _result;

public ODataMetadata(IEnumerable<T> result, long? count)  
{  
_count = count;  
_result = result;  
}

public IEnumerable<T> Results  
{  
get {return _result;}  
}

public long? Count  
{  
get { return _count; }  
}  
}  

Adding the action πŸ”—

The action will need to return System.Web.Http.OData.ODataResult, 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.

public ODataResult<Blog> GetBlogs(ODataQueryOptions options)  
{  
long? count = null;  
var result = blogRepo.GetAll();

if (options.Filter != null)  
result = options.Filter.ApplyTo(result, true) as IQueryable<Blog>;

if (options.RawValues.InlineCount == "allpages")  
count = result.Count();

result = options.ApplyTo(result) as IQueryable<Blog>;  
return new ODataResult<Blog>(result, null, count);  
}  

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 - 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 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 with a message handler πŸ”—

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

public class ODataHandler : DelegatingHandler  
{  
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)  
{  
return base.SendAsync(request, cancellationToken).ContinueWith(  
task =>  
{  
var response = task.Result;

if (ResponseIsValid(response))  
{  
object responseObject;  
response.TryGetContentValue(out responseObject);

if (responseObject is ODataResult)  
{  
var renum = responseObject as IEnumerable

<object>
  ;<br /> var robj = responseObject as ODataResult;</p> 
  
  <p>
    if (robj.Count != null)<br /> {<br /> response = request.CreateResponse(HttpStatusCode.OK, new ODataMetadata
    
    <object>
      (renum, robj.Count));<br /> }<br /> }<br /> }</p> 
      
      <p>
        return response;<br /> });<br /> }
      </p>
      
      <p>
        private bool ResponseIsValid(HttpResponseMessage response)<br /> {<br /> if (response == null || response.StatusCode != HttpStatusCode.OK || !(response.Content is ObjectContent)) return false;<br /> return true;<br /> }<br /> }<br /> ```
      </p>
      
      <p>
        In this case, what we do is:<br /> 1) Check if the response content object is <i>ODataResult</i> - if not, do not process<br /> 2) We cast it twice, once as <i>IEnumerable</i> - to get access to the results collection<br /> 3) The second cast is to <i>ODataResult</i> which is an asbtract class exposing only the count<br /> 4) Finally, we use the result collection and the inlinecount to construct the <i>ODataMetaData</i> 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 <i>ODataResult>T<</i> to the client.
      </p>
      
      <h3>
        Trying it out
      </h3>
      
      <p>
        1. Normal - no inlinecount - /api/blog/
      </p>
      
      <p>
        <a href="/images/2012/08/odata1.png"><img src="/images/2012/08/odata1.png" alt="" title="odata1" width="411" height="349" class="aligncenter size-full wp-image-460" srcset="/images/2012/08/odata1.png 411w, /images/2012/08/odata1-300x255.png 300w" sizes="(max-width: 411px) 100vw, 411px" /></a>
      </p>
      
      <p>
        2. With $inlinecount & $top - /api/blog?$top=2&$inlinecount=allpages
      </p>
      
      <p>
        <a href="/images/2012/08/odata2.png"><img src="/images/2012/08/odata2.png" alt="" title="odata2" width="470" height="338" class="aligncenter size-full wp-image-461" srcset="/images/2012/08/odata2.png 470w, /images/2012/08/odata2-300x216.png 300w" sizes="(max-width: 470px) 100vw, 470px" /></a>
      </p>
      
      <p>
        3. With $inlinecount & $top & $skip- /api/blog?$top=2&$skip=1&$inlinecount=allpages
      </p>
      
      <p>
        <a href="/images/2012/08/odata3.png"><img src="/images/2012/08/odata3.png" alt="" title="odata3" width="530" height="313" class="aligncenter size-full wp-image-462" srcset="/images/2012/08/odata3.png 530w, /images/2012/08/odata3-300x177.png 300w" sizes="(max-width: 530px) 100vw, 530px" /></a>
      </p>
      
      <p>
        4. With $filter & $inlinecount - /api/blog?$filter=BlogId gt 1&$inlinecount=allpages<br /> In this case "count" refers to results after filter has been applied.
      </p>
      
      <p>
        <a href="/images/2012/08/odata4.png"><img src="/images/2012/08/odata4.png" alt="" title="odata4" width="585" height="328" class="aligncenter size-full wp-image-463" srcset="/images/2012/08/odata4.png 585w, /images/2012/08/odata4-300x168.png 300w" sizes="(max-width: 585px) 100vw, 585px" /></a>
      </p>
      
      <h3>
        Summary
      </h3>
      
      <p>
        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 <i>QueryableAttribute</i>) at some point anyway, but it's always interesting to hack away at things in the meantime πŸ™‚
      </p>
      
      <p>
        This is also a good replacement technique for the missing <i>ApplyResultsLimit</i> override of the <i>QueryableAttribute</i> which was the focal point of my <a href="/2012/06/extending-your-asp-net-web-api-responses-with-useful-metadata/">previous post</a> 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.
      </p>
      
      <h3>
        Update - 6h after this has been originally posted πŸ™‚
      </h3>
      
      <p>
        Following a suggestion from Alex James, let me mention a slightly different way of implementing this.
      </p>
      
      <p>
        Your action will now look like any typical action:<br /> ```csharp
<br /> [CustomQueryable]<br /> public IQueryable<Blog> GetBlogs()<br /> {<br /> var result = blogRepo.GetAll();<br /> return result;<br /> }<br /> ```
      </p>
      
      <p>
        Notice how clean it is, and that it is not concerned about any of the OData extravaganza.
      </p>
      
      <p>
        Now, instead of a handler, you can use a custom version of a <i>QueryableAttribute</i>.<br /> ```csharp
<br /> public class CustomQueryableAttribute : QueryableAttribute<br /> {<br /> public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)<br /> {<br /> long? originalsize = null;<br /> var inlinecount = HttpUtility.ParseQueryString(actionExecutedContext.Request.RequestUri.Query).Get("$inlinecount");
      </p>
      
      <p>
        object responseObject;<br /> actionExecutedContext.Response.TryGetContentValue(out responseObject);<br /> var originalquery = responseObject as IQueryable
        
        <object>
          ;</p> 
          
          <p>
            if (originalquery != null && inlinecount == "allpages")<br /> originalsize = originalquery.Count();
          </p>
          
          <p>
            base.OnActionExecuted(actionExecutedContext);
          </p>
          
          <p>
            if (ResponseIsValid(actionExecutedContext.Response))<br /> {<br /> actionExecutedContext.Response.TryGetContentValue(out responseObject);
          </p>
          
          <p>
            if (responseObject is IQueryable)<br /> {<br /> var robj = responseObject as IQueryable
            
            <object>
              ;</p> 
              
              <p>
                if (originalsize != null)<br /> {<br /> actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse(HttpStatusCode.OK, new ODataMetadata
                
                <object>
                  (robj, originalsize));<br /> }<br /> }<br /> }<br /> }</p> 
                  
                  <p>
                    public override void ValidateQuery(HttpRequestMessage request)<br /> {<br /> //everything is allowed<br /> }
                  </p>
                  
                  <p>
                    private bool ResponseIsValid(HttpResponseMessage response)<br /> {<br /> if (response == null || response.StatusCode != HttpStatusCode.OK || !(response.Content is ObjectContent)) return false;<br /> return true;<br /> }<br /> }<br /> ```
                  </p>
                  
                  <p>
                    So what's happening there:<br /> 1. We grab $inlinecount from the request querystring, and if it's equal to <i>allpages</i>, it means that the client wants to have the count - so we count all the items in the collection<br /> 2. We let the OData QueryableAttribute do its thing and apply all the queries. Notice that, I overrode <i>ValidateQuery</i>, otherwise the current version of the <i>QueryableAttribute</i> will thrown an exception when it sees $inlinecount.<br /> 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 <i>ODataMetadata</i> object, if not, we countinue just returning the collection.
                  </p>
                  
                  <p>
                    Now, if you opt for this approach you lose some, win some.
                  </p>
                  
                  <p>
                    The pluses:<br /> - your action is back to being very clean and you can again return your POCO from it<br /> - you can use an attribute instead of handler (probably more readable, and doesn't run for all requests)
                  </p>
                  
                  <p>
                    The minuses:<br /> - the $inlinecount now always refers to your entire collection <strong>prior</strong> to applying OData query, which means the $inlinecount will be calculated after $filter and before other, but rather before everything<br /> - you need to implement your own validation check against disallowed OData queries
                  </p>

 [1]: http://www.nuget.org/packages/Microsoft.AspNet.WebApi.OData
 [2]: http://blogs.msdn.com/b/alexj/archive/2012/08/15/odata-support-in-asp-net-web-api.aspx
 [3]: http://www.odata.org/documentation/uri-conventions

About


Hi! I'm Filip W., a cloud architect from ZΓΌrich πŸ‡¨πŸ‡­. I like Toronto Maple Leafs πŸ‡¨πŸ‡¦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁒󠁳󠁣󠁴󠁿.

You can find me on Github and on Mastodon.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP