where v1 and v2 refer to different representations of a book, not different representations of RTF or PDF respectively
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:
RtfMediaTypeFormatter:
application/vnd.company.book.v1+rtf
application/vnd.company.book.v2+rtf
PdfMediaTypeFormatter:
application/vnd.company.book.v1+pdf
application/vnd.company.book.v2+pdf
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??
...the ActionMethod I'd guess??
But how does the ActionMethod determine what to serve?
The simplest to do is:
public HttpResponseMessage GetBook(int id)
{
var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
if (Request.Headers.Accept.Any(a => a.MediaType.Contains("v1)")))
response.CreateContent(new Book1());
else if (Request.Headers.Accept.Any(a => a.MediaType.Contains("v2)")))
response.CreateContent(new Book2());
return response;
}
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)
Ideally content negotiation is the right place to do for your scenario. Can you please clarify the following?
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.
application/vnd.company.book.v1+rtf,
application/vnd.company.book.v2+rtf
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
formatterContext.Response.Request
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.
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.
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.
public Book GetBook(int id)
{
return GetById(id);
}
Having seperate formatters for v1 and v2 in my opinion is more cleaner.
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.
Can't say I agree with this at all. 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.
For me to "do this right" 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. Maybe writing a custom route constraint for http headers,
and having different routes for each?
Perhaps your CreateBook1 is more complicated than my scenario but I haven't had to do any "if version" checks in my controller. Instead I follow what
raghuramn is proposing and register a number of MediaTypeFormatters (I've actually got a generic TransformingMediaTypeFormatter) that has a list of transforms (e.g. Transformer<Book, BookV1> and Transformer<Book,
BookV2>) just for one internal type (in your case Book) - if I had other entity types, I would have more instances of my TransformingMediaTypeFormatter.
My controller takes and returns internal Book objects (even HttpResponseMessage<Book> 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<ResourceType, RepresentationType> and vice versa.
I can initialize all of the transforming media type formatters up front using some one-off reflection.
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.
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.
Søren
N.B. My Book service is just an example, but the real scenario is in principle the same.
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 "master" 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).
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 "master" data object.
This sounds horribly inefficient, in many cases you'd be loading data or running logic that almost no one needs. In some cases this can work fine, if the difference between versions is small (like renaming a property or changing a data type).
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.
Yes with all these approaches, monitoring and deprecation of unused versions would be ideal.
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:
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 in which case I'd strongly recommend looking at a caching layer.
Perhaps if you could provide an example of the kind of "extra logic" you'd be running?
smolesen
Member
43 Points
37 Posts
Content-Negotiation and Versioning, how?
Mar 02, 2012 10:47 AM|LINK
Lets say I have a REST service, which must be able to respond to the following URL:
http://myserver/api/books/456
is must be able to support the following content types and versions:
application/vnd.company.book.v1+rtf, application/vnd.company.book.v2+rtf,
application/vnd.company.book.v1+pdf, application/vnd.company.book.v2+pdf
where v1 and v2 refer to different representations of a book, not different representations of RTF or PDF respectively
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:
RtfMediaTypeFormatter:
application/vnd.company.book.v1+rtf
application/vnd.company.book.v2+rtf
PdfMediaTypeFormatter:
application/vnd.company.book.v1+pdf
application/vnd.company.book.v2+pdf
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??
...the ActionMethod I'd guess??
But how does the ActionMethod determine what to serve?
The simplest to do is:
public HttpResponseMessage GetBook(int id) { var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK); if (Request.Headers.Accept.Any(a => a.MediaType.Contains("v1)"))) response.CreateContent(new Book1()); else if (Request.Headers.Accept.Any(a => a.MediaType.Contains("v2)"))) response.CreateContent(new Book2()); return response; }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)
What is the recommended thing to do ?
TIA
Søren
Kiran Challa
Participant
1442 Points
281 Posts
Microsoft
Re: Content-Negotiation and Versioning, how?
Mar 02, 2012 02:55 PM|LINK
Ideally content negotiation is the right place to do for your scenario. Can you please clarify the following?
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.
application/vnd.company.book.v1+rtf,
application/vnd.company.book.v2+rtf
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
formatterContext.Response.Request
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.
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.
Kiran Challa
raghuramn
Member
248 Points
64 Posts
Microsoft
Re: Content-Negotiation and Versioning, how?
Mar 02, 2012 10:32 PM|LINK
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.
public Book GetBook(int id) { return GetById(id); }Having seperate formatters for v1 and v2 in my opinion is more cleaner.
SiggiGG
Member
265 Points
105 Posts
Re: Content-Negotiation and Versioning, how?
Mar 02, 2012 10:46 PM|LINK
Can't say I agree with this at all. 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.
For me to "do this right" 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. Maybe writing a custom route constraint for http headers, and having different routes for each?
smolesen
Member
43 Points
37 Posts
Re: Content-Negotiation and Versioning, how?
Mar 05, 2012 07:13 AM|LINK
Hi all
Thanks for your replies....
Guess another aproach, could be to return a list of Func<>'s from the ActionMethod, and then execute it in the MediaTypeFormatter, something like:
public HttpResponseMessage GetBook(int productnumber) { var response = new HttpResponseMessage(); var res = new Dictionary<string, Func<object>> {{"v1", () => CreateBook1(productnumber)}, {"v2", () => CreateBook2(productnumber)}}; response.CreateContent(res); return response; } private Book1 CreateBook1(int productnumber) { return new Book1(); } private Book2 CreateBook2(int productnumber) { return new Book2(); }and then execute the correct Fync<> in the MediaTypeFormatter:
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(() => { var generator = new PdfGenerator(typeof(Book2)); generator.Generate(stream, (Book2)((Dictionary>) value)["v2"].Invoke()); }); } protected override bool CanWriteType(Type type) { return type == typeof(Dictionary>); } } However I might be difficult to set the HttpStatus code correctly....
Regards,
Søren
LittleClive
Member
91 Points
65 Posts
Re: Content-Negotiation and Versioning, how?
Mar 05, 2012 01:26 PM|LINK
Perhaps your CreateBook1 is more complicated than my scenario but I haven't had to do any "if version" checks in my controller. Instead I follow what raghuramn is proposing and register a number of MediaTypeFormatters (I've actually got a generic TransformingMediaTypeFormatter) that has a list of transforms (e.g. Transformer<Book, BookV1> and Transformer<Book, BookV2>) just for one internal type (in your case Book) - if I had other entity types, I would have more instances of my TransformingMediaTypeFormatter.
My controller takes and returns internal Book objects (even HttpResponseMessage<Book> 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<ResourceType, RepresentationType> and vice versa.
I can initialize all of the transforming media type formatters up front using some one-off reflection.
smolesen
Member
43 Points
37 Posts
Re: Content-Negotiation and Versioning, how?
Mar 06, 2012 06:35 AM|LINK
Hi
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.
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.
Søren
N.B. My Book service is just an example, but the real scenario is in principle the same.
LittleClive
Member
91 Points
65 Posts
Re: Content-Negotiation and Versioning, how?
Mar 06, 2012 07:52 AM|LINK
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 "master" 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).
SiggiGG
Member
265 Points
105 Posts
Re: Content-Negotiation and Versioning, how?
Mar 06, 2012 09:09 AM|LINK
This sounds horribly inefficient, in many cases you'd be loading data or running logic that almost no one needs. In some cases this can work fine, if the difference between versions is small (like renaming a property or changing a data type).
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.
LittleClive
Member
91 Points
65 Posts
Re: Content-Negotiation and Versioning, how?
Mar 06, 2012 09:21 AM|LINK
Yes with all these approaches, monitoring and deprecation of unused versions would be ideal.
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:
http://googlecode.blogspot.com/2010/03/making-apis-faster-introducing-partial.html
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 in which case I'd strongly recommend looking at a caching layer.
Perhaps if you could provide an example of the kind of "extra logic" you'd be running?