Content-Negotiation and Versioning, how?http://forums.asp.net/t/1775966.aspx/1?Content+Negotiation+and+Versioning+how+Tue, 06 Mar 2012 14:19:51 -050017759664860587http://forums.asp.net/p/1775966/4860587.aspx/1?Content+Negotiation+and+Versioning+how+Content-Negotiation and Versioning, how? <p>Lets say I have a REST service, which must be able to respond to the following URL:</p> <p>http://myserver/api/books/456</p> <p>is must be able to support the following content types and versions:</p> <p>application/vnd.company.book.v1&#43;rtf,&nbsp;application/vnd.company.book.v2&#43;rtf,&nbsp;</p> <p>application/vnd.company.book.v1&#43;pdf,&nbsp;application/vnd.company.book.v2&#43;pdf</p> <p>where v1 and v2 refer to different representations of a book, not different representations of RTF or PDF respectively</p> <p>Looking at various posts and examples, it seems like the preferred way, is to add two different MediaTypeFormatters, one for 'rtf' and one for 'pdf' each supporting the following mediatypes:</p> <p>RtfMediaTypeFormatter:</p> <p>&nbsp; &nbsp; application/vnd.company.book.v1&#43;rtf</p> <p>&nbsp; &nbsp; application/vnd.company.book.v2&#43;rtf</p> <p>PdfMediaTypeFormatter:</p> <p>&nbsp; &nbsp; application/vnd.company.book.v1&#43;pdf</p> <p>&nbsp; &nbsp; application/vnd.company.book.v2&#43;pdf</p> <p>The MediaTypeFormatters are responsible of deliver either RTF or PDF, not if it's v1 or v2 of a book that are returned... so who is responsible for that??</p> <p>...the ActionMethod I'd guess??</p> <p>But how does the ActionMethod determine what to serve?</p> <p>The simplest to do is:</p> <pre class="prettyprint">public HttpResponseMessage GetBook(int id) { var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK); if (Request.Headers.Accept.Any(a =&gt; a.MediaType.Contains(&quot;v1)&quot;))) response.CreateContent(new Book1()); else if (Request.Headers.Accept.Any(a =&gt; a.MediaType.Contains(&quot;v2)&quot;))) response.CreateContent(new Book2()); return response; }</pre> <p>But now I've started implementing Content-Negotiation in my ActionMethod.... and that has a bad smell to me... especially since Content-Negotiation already takes place in the framework (to select the correct MediaTypeFormatter)</p> <p>What is the recommended thing to do ?</p> <p></p> <p>TIA</p> <p></p> <p>Søren</p> <p></p> <p></p> <p></p> <p></p> 2012-03-02T10:47:10-05:004861009http://forums.asp.net/p/1775966/4861009.aspx/1?Re+Content+Negotiation+and+Versioning+how+Re: Content-Negotiation and Versioning, how? <p>Ideally content negotiation is the right place to do for your scenario. Can you please clarify the following?</p> <p>In case of PDF (or RTF) formatter here, does it handle both v1 and v2? if yes, then your would need to add both of these media types in the SupportedMediaTypes collection of the formatter.&nbsp;</p> <p>application/vnd.company.book.v1&#43;rtf, &nbsp;&nbsp;</p> <p>application/vnd.company.book.v2&#43;rtf</p> <p></p> <p>When a request comes in for either v1 or v2 of RTF, your formatter gets picked up for writing...now, during the writing in the OnWriteToStreamAsync method, you would have access to the actual request message by doing the following</p> <p><em>formatterContext.Response.Request</em></p> <p>After accessing the request like above, you can look at its headers to see which version of is the client requesting for and write the response in appropriate version.</p> <p>BTW, if this approach is not suitable for you, there is always the approach of having a separate v1 RTF and v2 RTF formatters and registering both of them in the formatters collection. Conneg will take care of selecting the appropriate one for you.</p> <p></p> 2012-03-02T14:55:33-05:004861435http://forums.asp.net/p/1775966/4861435.aspx/1?Re+Content+Negotiation+and+Versioning+how+Re: Content-Negotiation and Versioning, how? <p>Your action should return a Book rather than returning Book1 or Book2. The formatter should then depending on media type figure out whether to send a v1 or a v2 of that Book in either pdf or rtf format.</p> <pre class="prettyprint">public Book GetBook(int id) { return GetById(id); }</pre> <p>Having seperate formatters for v1 and v2 in my opinion is more cleaner. </p> 2012-03-02T22:32:09-05:004861445http://forums.asp.net/p/1775966/4861445.aspx/1?Re+Content+Negotiation+and+Versioning+how+Re: Content-Negotiation and Versioning, how? <p></p> <blockquote><span class="icon-blockquote"></span> <h4>raghuramn</h4> Your action should return a Book rather than returning Book1 or Book2. The formatter should then depending on media type figure out whether to send a v1 or a v2 of that Book in either pdf or rtf format.</blockquote> <p></p> <p>Can't say I agree with this at all.&nbsp; The main reason to version your resources is when you need to break compatibility, and in that case you usually end up with 2 different classes and you support the v1 for backward compatibility.</p> <p>For me to &quot;do this right&quot; would be to affect the action selection based on the requested version, I know you could do that with an operation selector in the WCF Web API, but not sure in ASP.NET Web API.&nbsp; Maybe writing a custom route constraint for http headers, and having different routes for each?</p> <p></p> 2012-03-02T22:46:47-05:004863672http://forums.asp.net/p/1775966/4863672.aspx/1?Re+Content+Negotiation+and+Versioning+how+Re: Content-Negotiation and Versioning, how? <p>Hi all</p> <p>Thanks for your replies....</p> <p>Guess another aproach, could be to return a list of Func&lt;&gt;'s from the ActionMethod, and then execute it in the MediaTypeFormatter, something like:</p> <pre class="prettyprint">public HttpResponseMessage GetBook(int productnumber) { var response = new HttpResponseMessage(); var res = new Dictionary&lt;string, Func&lt;object&gt;&gt; {{&quot;v1&quot;, () =&gt; CreateBook1(productnumber)}, {&quot;v2&quot;, () =&gt; CreateBook2(productnumber)}}; response.CreateContent(res); return response; } private Book1 CreateBook1(int productnumber) { return new Book1(); } private Book2 CreateBook2(int productnumber) { return new Book2(); }</pre> <p></p> <p>and then execute the correct Fync&lt;&gt; in the MediaTypeFormatter:</p> <pre class="prettyprint">public class PdfFormatterV2 : MediaTypeFormatter { public PdfFormatterV2() { SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.grundfos.book.v2+pdf")); } protected override Task OnWriteToStreamAsync(Type type, object value, System.IO.Stream stream, System.Net.Http.Headers.HttpContentHeaders contentHeaders, FormatterContext formatterContext, System.Net.TransportContext transportContext) { return Task.Factory.StartNew(() =&gt; { var generator = new PdfGenerator(typeof(Book2)); generator.Generate(stream, (Book2)((Dictionary<string, func="" object="">&gt;) value)["v2"].Invoke()); }); } protected override bool CanWriteType(Type type) { return type == typeof(Dictionary<string, func="" object="">&gt;); }</string,></string,>} </pre> <p></p> <p>However I might be difficult to set the HttpStatus code correctly....</p> <p>Regards,</p> <p></p> <p>Søren</p> 2012-03-05T07:13:01-05:004864442http://forums.asp.net/p/1775966/4864442.aspx/1?Re+Content+Negotiation+and+Versioning+how+Re: Content-Negotiation and Versioning, how? <p>Perhaps your CreateBook1 is more complicated than my scenario but I haven't had to do any &quot;if version&quot; checks in my controller. Instead I follow what <a title="raghuramn" href="/members/raghuramn.aspx">raghuramn</a>&nbsp;is proposing and register a number of MediaTypeFormatters (I've actually got a generic TransformingMediaTypeFormatter) that has a list of transforms (e.g. Transformer&lt;Book, BookV1&gt; and Transformer&lt;Book, BookV2&gt;)&nbsp;just for one internal&nbsp;type (in your case Book) - if I had other entity types, I would have more instances of my TransformingMediaTypeFormatter.</p> <p>My controller&nbsp;takes and&nbsp;returns internal&nbsp;Book objects (even HttpResponseMessage&lt;Book&gt; is ok) but the TransformingMediaTypeFormatter would kick in and make sure the book got turned into a v1 or v2 book but using a dictionary of transformers. Those transformers implement a simple interface of ITransform&lt;ResourceType, RepresentationType&gt; and vice versa.</p> <p>I can initialize all of the transforming media type formatters up front using some one-off reflection.</p> 2012-03-05T13:26:41-05:004865633http://forums.asp.net/p/1775966/4865633.aspx/1?Re+Content+Negotiation+and+Versioning+how+Re: Content-Negotiation and Versioning, how? <p>Hi</p> <p>In my case Book2 needs to pull more info from a DB than Book1 does, so there's more information in Book2 than in Book1, and no direct way of converting Book1 to Book2 and vise-versa. I could of course include the information from Book1 into Book2, making the conversion fro Book2 to Book1 easy, but then the client, would (if requsting Json) get more data than needed, which I'd like to avoid.</p> <p>I'd really like my ActionMethod to be the one taking care of pulling the data from my DB and have the formatter care about the PDF/RTF conversion, adhering to the principles of seperation-of-concerns, but it seem like I'll have to bend it a bit to make my stuff work.</p> <p></p> <p>Søren</p> <p>N.B. My Book service is just an example, but the real scenario is in principle the same.</p> <p></p> 2012-03-06T06:35:04-05:004865740http://forums.asp.net/p/1775966/4865740.aspx/1?Re+Content+Negotiation+and+Versioning+how+Re: Content-Negotiation and Versioning, how? <p>Surely you'd pull all the information your internal BookDTO object needs for all versions and you'd probably cache it too. The transforms would then pick and choose to populate their properties from this &quot;master&quot; data object. So for 2 versions, there are 3 data objects, BookDTO (internally populated from DB), BookV1 (created via transform of BookDTO) and BookV2 (also created via transform of BookDTO).</p> <p>&nbsp;</p> 2012-03-06T07:52:15-05:004865876http://forums.asp.net/p/1775966/4865876.aspx/1?Re+Content+Negotiation+and+Versioning+how+Re: Content-Negotiation and Versioning, how? <p></p> <blockquote><span class="icon-blockquote"></span> <h4>LittleClive</h4> urely you'd pull all the information your internal BookDTO object needs for all versions and you'd probably cache it too. The transforms would then pick and choose to populate their properties from this &quot;master&quot; data object. </blockquote> <p></p> <p>This sounds horribly inefficient, in many cases you'd be loading data or running logic that almost no one needs.&nbsp; In some cases this can work fine, if the difference between versions is small (like renaming a property or changing a data type).</p> <p>Ideally you want to monitor usage of resource versions over time, and only support X many, or drop support for older versions after some time.</p> 2012-03-06T09:09:21-05:004865909http://forums.asp.net/p/1775966/4865909.aspx/1?Re+Content+Negotiation+and+Versioning+how+Re: Content-Negotiation and Versioning, how? <p>Yes with all these approaches, monitoring and&nbsp;deprecation of unused versions would be ideal.</p> <p>It's hard to judge what you're anticipating as the changes in your Book representation over time but what you describe sounds like it isn't versioning but purposing content for different clients in which case you might want to consider partial responses:</p> <p><a href="http://googlecode.blogspot.com/2010/03/making-apis-faster-introducing-partial.html">http://googlecode.blogspot.com/2010/03/making-apis-faster-introducing-partial.html</a></p> <p>In a services scenario though, I can't see how you'll be making a saving unless you're going to the database every time&nbsp;in which case I'd strongly recommend looking at a caching layer.</p> <p>Perhaps if you could provide an example of the kind of &quot;extra logic&quot; you'd be running?</p> 2012-03-06T09:21:23-05:004866041http://forums.asp.net/p/1775966/4866041.aspx/1?Re+Content+Negotiation+and+Versioning+how+Re: Content-Negotiation and Versioning, how? <p></p> <p>Returning a BookDTO from the ActionMethod combined with some kind of transformation in a MediaTypeFormatter is probably the best approach in my situation... It will break the (yet non-existing in ASP.NET Web API) documentation feature of the 'WCF Web API' though, since this will state BookDTO as the return type of my ServiceMethod, exposing it to the public.... but maybe the documentation feature of the APS.NET Web API, will take this in account or make it possible to override it.</p> <p></p> 2012-03-06T10:33:36-05:004866124http://forums.asp.net/p/1775966/4866124.aspx/1?Re+Content+Negotiation+and+Versioning+how+Re: Content-Negotiation and Versioning, how? <p>Yes losing the anticipated documentation is a bit of a shame - I did raise this during the preview at:</p> <p><a href="http://wcf.codeplex.com/discussions/310154">http://wcf.codeplex.com/discussions/310154</a></p> <p>If we want this we'll have to do something bespoke - which I don't think is too much of a problem since our transform interface is fairly clean - no base class.</p> <p>In case it helps, one other thing I did was provide a wrapper for IEnumerable&lt;BookDTO&gt; to RepresentationListV1&lt;BookV1&gt; - the wrapper just knows about IEnumerable&lt;&gt; and RepresentationListV1&lt;&gt; and is automatically created with the correct inner transformer.</p> <p>I've also&nbsp;heard you can do some clever stuff with <a href="https://github.com/AutoMapper/AutoMapper"> https://github.com/AutoMapper/AutoMapper</a>&nbsp;but I'm feeling a slight ickiness about using it.</p> 2012-03-06T11:20:29-05:004866440http://forums.asp.net/p/1775966/4866440.aspx/1?Re+Content+Negotiation+and+Versioning+how+Re: Content-Negotiation and Versioning, how? <p>I just found a subtle but important difference of using:</p> <p>application/vnd.myapp.api.v2&#43;json</p> <p>vs.</p> <p>application/vnd.myapp.api&#43;json; version=2</p> <p>The former will pick version specific MediaTypeFormatters meaning that internally you don't need to be switching on version number to pick a transformer. The latter will match on all versions (since the version parameter is not taken into consideration) and therefore require a more complicated internal mechanism.</p> <p>Another advantage of the former is that it works with clients that don't understand or deal with parameterised Accept headers very well.</p> <p>I haven't looked to see if the selection mechanism could have been modified to take version parameters into consideration but there is an IFormatterSelector interface that you could implement&nbsp;and inject via a&nbsp;DependencyResolver.</p> 2012-03-06T14:19:51-05:00