TLDR; You can now achieve content-based action selection in ASP.NET Web API
You probably will not remember but last year, I proposed an implementation of media type based on parameters that separates different levels of media type - Five Levels of Media Type or 5LMT. This discussion is mainly theoretical and focuses on media type semantics and problems of the popular (yet non-canonical) approaches to media types that are prevalent in the community. The article identified 5 levels (human illegibility, format, schema, domain model and version) and separating these will help different clients with different capabilities to use levels they are capable of understanding without having to fully comprehend all levels.
But really, is this not just nitpicking on trivial issues and arguing issues which have to practical importance? Well, I think not - and here is why. If you would like to see some code instead of endless dry discussions, you will not be disappointed.
If you visit StackOverflow and look for ASP.NET Web API (or alternatively REST APIs), there are many questions on REST resource design. Coming from an RPC mindset, it is difficult to map all methods to GET, POST, PUT and DELETE. And sometimes we run out of verbs and we resort back to RPC-style APIs.
Let's imagine you are designing a digital music player API and you have identified these resources and operations:
Play PUT /api/player Stop DELETE /api/player Vol up POST /api/player/volume Vol down POST /api/player/volume Next POST /api/player/song Previous POST /api/player/song Back to first PUT /api/player/song
At this time, some finally give in to defining /api/player/song/next and /api/player/song/previous i.e RPC-style URLs. And this is a downward spiral when you resources are verbs rather than noun.
The solution is to go back to REST. One of the tenets of REST is self-descriptive messages as such we should be able to express our intention with the HTTP messages. In other words, sending this message to up the volume:
POST /api/player/volume HTTP/1.1 Content-Type: application/json;domain-model=IncreaseVolumeCommand {"notch":"11"}And that is it. Our HTTP message provides enough information for our Web API to distinguish as long as our action has been defined as something like
public void Post(IncreaseVolumeCommand command) { ... }or
public void Post(HttpRequestMessage request, IncreaseVolumeCommand command, ...) { ... }And how is this achieved? Well, a little utility in 5LMT library called MediaTypeBasedActionSelector does it. To get the library, just add the NuGet package:
PM> Install-Package AspNetWebApi.FiveLevelsOfMediaTypeAnd then your WebApiConfig class, add the line below:
config.AddFiveLevelsOfMediaType();What this line does is to replace default action selector with MediaTypeBasedActionSelector and wrap your media type formatters in a decorator. As such, make sure you add this line after you have done all changes to your media type formatters.
After this change, your API will send 5LMT parameters. For example:
Content-Type:application/json; charset=utf-8; domain-model=InventoryItemDetail; version=1.0.0.0; format=application%2fjson; schema=application%2fjson; is-text=True
Another useful feature is the support for non-canonical media types. For example you can send this HTTP request instead of the POST discussed above:
POST /api/player/volume HTTP/1.1 Content-Type: application/vnd.MyCompany.IncreaseVolumeCommand-1.0.0.0+json {"notch":"11"}
While use of this media type is discouraged by RFC 2616 (since above media type is not registered by IANA), this still works with 5LMT library.
One last thing is that this library is dependent on AspNetWebApi.ApiActionSelector which makes action selection implementation of ASP.NET Web API extensible. Unfortunately, most of the implementation is internal and does not expose extensibility points and AspNetWebApi.ApiActionSelector had to achieve this by a lot of cope and paste :)
The code for this library is hosted on GitHub.
Happy coding...
No comments:
Post a Comment
Note: only a member of this blog may post a comment.