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:
This will give the blog post with the specified id.
The above URI structure is also seen as:
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:
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.