TLDR; [This is a short post but anyhow] You can now achieve content negotiation on Domain Model or even Version (levels 4 and 5) in the same ASP.NET Web API controller.
In the last post we explored how we can have a Roy-correct resource organisation (URLs I mean, of course) and take advantage of content-based action selection which is not natively possible in ASP.NET Web API.
Content Negotiation is an important aspect of HTTP. This is the dance between client and server to negotiate on the optimum media type to get a resource. Most common implementation of content negotiation is the server-side one which server decides the format based on the client's preferences expressed by the content of Accept, Accept-Language, Accept-Encoding, etc headers.
For example, the page you are viewing is probably the result of this content negotiation:
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
This basically mean give me HTML or XHTML if you can (q=1.0 is implied here), if not XML should be OK, and if not then I can live with image/webp format - which is Google's experimental media type as I was using Chrome.
So, let's have a look at media types expressed above
text/html -> Format application/xhtml+xml -> Format/Schema image/webp -> Format
So as we can see, content negotiation at this request mainly deals with format and schema (levels 2 and 3). This level of content negotiation comes out of the box in ASP.NET Web API. Responsibility of this server-side content negotiation falls on MediaTypeFormatters and more importantly IContentNegotiator implementations. The latter is a simple interface taking the type to be serialised, the request and a list of formatters and returns a ContentNegotiationResult:
IContentNegotiator
public interface IContentNegotiator { ContentNegotiationResult Negotiate(Type type, HttpRequestMessage request, IEnumerable<mediatypeformatter> formatters); }
This is all well and good. But what if we want to ask for SomeDomainModel or AnotherDomainModel(level 4)? Or even more complex: asking for a particular version of the media type (level 5)? Unfortunately ASP.NET Web API does not provide this feature out of the box, however, using 5LMT this is very easy.
Let's imagine you have two different representation of the same resource - perhaps one was added recently and can be used by some clients. This representation can be a class with added properties or even more likely, removal of some existing properties which results in a breaking change. How would you approach this? Using 5LMT, we add a new action to the same controller returning the new representation:
public class MyController { // old action public SomeDomainModel Get(int id) { ... } // new action added public AnotherDomainModel GetTheNewOne(int id) { ... } }And the request to return each of will be based on the value of the accept
GET /api/My Accept: application/SomeDomainModel+jsonor to get the new one:
GET /api/My Accept: application/AnotherDomainModel+jsonIn order to achieve this, all you have to do is to replace your IHttpActionSelector in the Services of your configuration with MediaTypeBasedActionSelector.
This is the non-canonical representation of media type. You may also use the 5LMT representation which I believe is superior:
GET /api/My Accept: application/json;domain-model=SomeDomainModelor to get the new one:
GET /api/My Accept: application/json;domain-model=AnotherDomainModel
In order to use this feature, get the NuGet package:
PM> Install-Package AspNetWebApi.FiveLevelsOfMediaTypeand then add this line to your WebApiConfig.cs:
config.AddFiveLevelsOfMediaType();MediaTypeBasedActionSelector class provides two extensibility points:
- domainNameToTypeNameMapper which is a delegate and gets the name passed in the domain-name parameter and returns the class name of the type to be returned (if they are not the same although it is best to keep them the same)
- Another one which deserves its own blog post, allows for custom versioning.
Happy coding...