Queryable Web Services - Parsing Query Strings to LINQ

Tags: REST, LINQ, Query

I have previously made a stab at supposedly RESTful webservices where the response type is determined by a part of the request URI and not the Accept header as it should be. This time I have been contemplating another problem with the current approach to RESTful web services, which is the difficulty in querying them.

We all know the standard RESTful URI like:

http://domain/blogs/name/123

This will give the blog post with the specified id.

The above URI structure is also seen as:

http://domain/blogs/name

This is expected to give all posts for that particular blog.

But what if I wanted to have blog post that mention “foo”? To my knowledge there is no way to specify that in a standard way. The individual web service can allow you to specify custom parameters which may help you in filtering posts. The other approach is to download all the posts and then filtering them locally. The latter approach is of course hugely wasteful and is better done on the server.

As I mentioned, the individual web service can specify some custom capabilities, but I think a more standardized approach is needed.

The OData protocol specifies some query string options to perform filtering, sorting and projection. Much like LINQ operators, but as query parameters. From above, an OData service will be able to perform filtering so that I can get only posts containing “foo” by specifying that in a defined parameter, like so:

http://domain/blogs/name?$filter=substringof(‘foo’, Text)&$orderby=Date

Obviously one would have to learn the syntax, but there is huge potential in having a known query syntax. There are a number of OData server and client libraries. Unfortunately for .NET I couldn’t find anything that would help me use it in a custom web service or an existing ASP.NET or ASP.NET MVC site, so I wrote up a parser that reads an HTTP request and creates a LINQ query from the query strings defined in the OData specification. You can find the code on BitBucket. It's an initial drop, so be gentle :-)

Currently it supports a subset of the specification, but if you have a look at the tests, you will see that it can parse requests like:

http://domain/blah?$filter=substring(StringValue, 1) ne 'text' and IntValue eq 25 and DoubleValue le 10

into the following lambda expression:

x => (((x.StringValue.Substring(1) != "text") && (x.IntValue == 25)) && (x.DoubleValue <= 10))

The starting point is the ParameterParser class which has a Parse method which goes through the query parameters and returns a ModelFilter which holds the LINQ query generated from the query parameters. The ModelFilter class has a Filter method which applies the LINQ query to a collection of model items. This could be the blog posts from the example above. Although I haven't tested it yet, it should also be compatible with Entity Framework or NHibernate so that it is possible to apply this filter to the DbContext (Entity Framework) or the Query (Nhibernate) to generate a filtered database request. The whole point of parsing into a LINQ query is that you now have the benefit of being able to filter on the server in a consistent way.

The project includes a ModelFilterBinder class as well, so that it is possible to have the ModelFilter as a parameter to controller actions in ASP.NET MVC. For classical ASP.NET sites, the ParameterParser will have to do.

I am aware that creating a blanket implementation of this filtering may not suit all scenarios. For example a web site that only supports HTML responses may not want to support it. On the other hand a, on a blog site it may well make sense to support filtering and adapt the rendering accordingly. For web services that want to present themselves as RESTful I see little downside in supporting this. Remember that there is always the option of returning a 400 Bad Request response.

The UrlQueryParser doesn't support projection yet, and this will certainly have a bigger impact on HTML responses as certain properties from the model may not be available for rendering. For example if I project a blog into only Author and Date with no Text. Again a 400 Bad Request response is an option, another option is coming up with a flexible layout that does support this.

5 Comments

  • Sebastian Wain said

    Have you taken a look at the following articles?
    - http://lostintangent.com/post/3189655590/you-want-to-wrap-odata-around-what
    - http://wcfdstoolkit.codeplex.com/

  • jjrdk said

    Sebastian, those are interesting projects. My purpose is more to make use of the query syntax defined by the OData protocol to query data from any web provider, such as ASP.NET and ASP.NET MVC.

    Also the data doesn't have to be exposed as OData, but can be served in any format, matching the model.

  • jjrdk said

    There's my Linq2Rest project which does that for you - https://bitbucket.org/jjrdk/linq2rest

Comments have been disabled for this content.

Latest Tweets