REST Versioning: HttpOperationHandler giving "will never receive..."

Topics: Web Api
Feb 7, 2012 at 3:46 PM
Edited Feb 7, 2012 at 3:46 PM

 

I am trying to transform an input representation to the actual type required by the service using an HttpOperationHandler in a similar fashion (I hope) to the Geopoint sample in the CHM docs but I get:

The request HttpOperationHandler 'OldToNewHttpOperationHandler[book, out book]' of service operation 'AddBook' will never receive a value for input parameter 'book' of type 'OldBook'. Ensure that a request HttpOperationHandler that executes prior to the 'OldToNewHttpOperationHandler' HttpOperationHandler has an output parameter with a type assignable to 'OldBook'.

I created a simple service as follows:

    public class LibraryService

    {

        [WebInvoke(UriTemplate = "books", Method = "POST")]

        public HttpResponseMessage AddBook(NewBook book)

        {           

            return new HttpResponseMessage(HttpStatusCode.OK);

        }

    }

 

    public class NewBook

    {

        public string Name { get; set; }

    }

 

    public class OldBook

    {

        public string Title { get; set; }

    }

 

    public class OldToNewHttpOperationHandler : HttpOperationHandler<OldBook, NewBook>

    {

        protected override NewBook OnHandle(OldBook book)

        {

            return new NewBook() { Name = book.Title };

        }

 

        public OldToNewHttpOperationHandler(string param)

            : base(param)

        {           

        }

    }

 

In my global.asax I hook it up to the HttpConfiguration like so:

config.RequestHandlers = (handlers, endpoint, operation) =>

    {                  

        if (operation.InputParameters != null && operation.InputParameters.Count == 1 && operation.InputParameters[0].ParameterType == typeof(NewBook))

        {

            handlers.Add(new OldToNewHttpOperationHandler(operation.InputParameters[0].Name));

        }                   

    };

Feb 7, 2012 at 4:36 PM

There is no registered input parameter OldBook. You will need to accept an HttpRequestMessage, not a OldBook

Feb 7, 2012 at 4:48 PM

As digitalpacman indicated, you are missing the transform from HttpRequestMessage to OldBook.  You can either update your existing handler to <HttpRequestMessage, NewBook> or you can add another handler of type <HttpRequestMessage, OldBook>.  The former is probably preferable unless you have some other secondary transform of OldBook to something else, like FooBook, where an intermediate transform is leveraged by more than one handler.

Feb 7, 2012 at 4:53 PM

One other thing to mention, if your example is as simple as you indicated, it would likely be better to allow that auto-deserialization to your POCO to occur and just create a NewBookAdapter class that takes an OldBook and returns a NewBook.  Otherwise, you're throwing away the value of the built-in formatter in this case.

Feb 7, 2012 at 4:55 PM
Edited Feb 8, 2012 at 8:50 AM

UPDATE - just read davidpeden3's latest comment which asserts that doing it the following way will lose the benefit of formatters etc.

Thanks to both - ok so if I wanted to also take into account the MediaTypeFormatter currently associated with the config (not shown above), how would I do this?

       protected override NewBook OnHandle(HttpRequestMessage book)

       {

           OldBook oldBook = book.Content.ReadAsAsync<OldBook>().Result;

           return new NewBook() { Name = oldBook.Title };

       }

This doesn't seem to use the custom formatters registered in config.Formatters.

Also just to tie things up it'd be good if you could clarify if the "Example: Type Conversion" in the docs is therefore out of date or whether this only copes with strings? Here's the link if that helps!

WCFWebAPI_Con.chm::/html/1685990d-d957-42b1-9f2a-9c5a3a4c701c.htm#example2

Feb 7, 2012 at 4:57 PM
Edited Feb 7, 2012 at 4:58 PM

Are you using DTOs man? This sounds like a facade.

Is oldbook just a DTO so you dont expose newbook?

If this is the case:

1) Make a facade layer (the outside accessible API)

2) Learn ValueInjector or AutoMapper

3) Make a Service/Application layer (this is what the facade layer calls down to)

4) In every call to the facade, take in DTO, use ValueInjector/AutoMapper to build real objects from the DTOs, and pass them to Application layer calls

5) Eat cake

Feb 7, 2012 at 5:03 PM
Edited Feb 7, 2012 at 5:24 PM

Ok you (david) say adapter class - I assume you mean in the GOF style. Where would you plug that in? I assume you mean in the Service method and therefore change the signature of the service method to accept OldBook.

As you rightly assume the example is deliberately simple and what I'm actually looking for is a way to transform incoming classes based on a version in the Accept header in order to support legacy objects. Ideally the customer would like a single Service with transforms for older versions of these POCOs. So if you are suggesting a basic adapter rather than something WCF WEB API specific then where would you perform such adaption?

You might well ask why not just use two services and version them (go on! :-)). In terms of versioning the only way I've found to version the service would be to host each one on different urls (e.g. /api/v1/books and /api/v2/books) but this apparently isn't very RESTful.

http://barelyenough.org/blog/2008/05/versioning-rest-web-services/

Of course if this approach is not practical with WCF WEB API then please put me out of my misery :-)

I certainly couldn't register two service routes both with "/books" and use a RouteConstraint.

Feb 7, 2012 at 5:25 PM
Edited Feb 7, 2012 at 5:26 PM

digitalpacman's suggestion of using something like AutoMapper can simplify and/or eliminate the concrete implementation of IBookMapper.  This is what I'm talking about:

    public class OldBookDto
    {
        public string Title
        {
            get;
            set;
        }
    }

    public class NewBook
    {
        public string Name
        {
            get;
            set;
        }
    }

    public interface IBookService
    {
        void DoSomething(NewBook book);
    }

    public class BookService : IBookService
    {
        public void DoSomething(NewBook book)
        {
            throw new NotImplementedException();
        }
    }

    public interface IBookMapper
    {
        NewBook Map(OldBookDto source);
    }

    public class BookMapper : IBookMapper
    {
        public NewBook Map(OldBookDto source)
        {
            return new NewBook { Name = source.Title };
        }
    }

    public class BookApi
    {
        private readonly IBookService bookService;
        private readonly IBookMapper bookMapper;

        public BookApi(IBookService bookService, IBookMapper bookMapper)
        {
            this.bookService = bookService;
            this.bookMapper = bookMapper;
        }

        [WebInvoke(UriTemplate = "books", Method = "POST")]
        public HttpResponseMessage AddBook(OldBookDto book)
        {
            NewBook newBook = bookMapper.Map(book);
            bookService.DoSomething(newBook);
            return new HttpResponseMessage(HttpStatusCode.OK);
        }
    }

As far as versioning is concerned, see Darrel Miller's response (most votes, not the accepted answer) at http://stackoverflow.com/questions/972226/how-to-version-rest-uris.  He posts on here a lot and is well respected in the community.

Feb 8, 2012 at 9:09 AM

Thanks for taking the time to put the example down, yes that's what I assumed you meant and in this case you now have two services, one that takes an OldBook and one that takes a NewBook.

So my question is, can this approach be extended to implement what Darrel Miller and Peter Williams are proposing? It'd be good to see how naturally this fits with WCF Web Api.

To route a POST request to /library/books and have it hit the correct service based on a header check would require something custom - I'm currently looking at the MvcContrib SimplyRestfulRouteHandler but I don't know how nicely this approach will play with WCF Web Api.

Feb 8, 2012 at 3:20 PM
LittleColin wrote:

Thanks for taking the time to put the example down, yes that's what I assumed you meant and in this case you now have two services, one that takes an OldBook and one that takes a NewBook.

It's important to distinguish them differently.  One is your public API that takes DTOs.  The other is an internal service (application service or facade) that works with proper domain entities.  They are not the same.  You could expose another operation on your API that takes in a NewBookDto.  The API deals in terms of HTTP.  The application service deals in terms of the domain.

Regarding versioning through media types, your mapping code would actually go into a custom media formatter and would switch on the Accept header to know which transformation/mapping to perform.  I'm not sure why you are looking at anything in the MvcContrib project.  Personally, I wouldn't want to take on that dependency.  Seems unnecessary but I'm not personally familiar with it.
 

Feb 10, 2012 at 11:27 AM
Edited Feb 10, 2012 at 4:16 PM

Sure I think we're probably talking slightly crossed purposes here. My OldBook/NewBook weren't exactly a DTO abstraction but rather a v1/v2 of a public facing representation (but my example should really have taken both of them and transformed them into a DTO but I was trying to keep it simple to solve the problem I had at the time).

To extend the discussion on versioning I don't believe a MediaTypeFormatter will help as this will apply to all incoming messages, I'd have to do some lengthy checking to apply the right transform for the right content and I'd need one for XML and JSON. It's more geared towards the format rather than content of the message.

I believe we've now solved it by hooking together HttpOperationHandlers, each check if they can handle the message for that particular version and if so, they do otherwise they leave it untouched for the next handler. These are then hooked up to operations of the appropriate response/request type. The key was understanding how the input parameters and output parameters could be exploited to do this and steering clear of the pre-canned generic ones (just derive from HttpOperationHandler directly).

A minor downside to all these approaches is that the auto documentation feature won't take any of this into account when generating the sample messages. It appears to only look at the service inputs and outputs itself without taking the handlers into consideration.

Feb 10, 2012 at 3:42 PM
Edited Feb 10, 2012 at 3:42 PM
LittleColin wrote:

To extend the discussion on versioning I don't believe a MediaTypeFormatter will help as this will apply to all incoming messages, I'd have to do some lengthy checking to apply the right transform for the right content and I'd need one for XML and JSON. It's more geared towards the format rather than content of the message.


I think you haven't quite grasped the concept.  You would not explicitly support XML or JSON directly.  You would define a custom media type and version it with specific MediaTypeFormatters for each new version of your API.  See Steve Michelotti's blog post:

http://geekswithblogs.net/michelotti/archive/2011/06/06/understanding-custom-wcf-web-api-media-type-processors-on-both.aspx

This is a concrete example of what Darrel was discussing on Stack Overflow.  Steve even mentions Darrel in the post.  ;)

Anyway, sounds like you solved your primary concern so that's cool.  Hope this helps, regardless.  :D

Feb 10, 2012 at 4:01 PM

That looks like a good article but addressing a different problem where the format of all messages has changed or needs to be custom for whatever reason.

I need a pattern for dealing with individual changes to return values and input parameters of the service operations. I've only covered a change in a single field in my example of title to name, but there could be other changes like OldBook has a single Author field but now in v2 we need to support a list of authors. Also there are more than just book resources in the API (e.g. users, products, cars etc.) each of which will have independent evolving datamodels as the business requirements change.

But even using my rubbish sample code, I don't see how a media type formatter is going to change the fieldname of "Name" to "Title" without affecting all resources (not shown in the example) in the API. I think I should have started with a better example ;-)

Feb 10, 2012 at 4:07 PM
LittleColin wrote:

I need a pattern for dealing with individual changes to return values and input parameters of the service operations. I've only covered a change in a single field in my example of title to name, but there could be other changes like OldBook has a single Author field but now in v2 we need to support a list of authors. Also there are more than just book resources in the API (e.g. users, products, cars etc.) each of which will have independent evolving datamodels as the business requirements change.

Ask and ye shall receive...

http://barelyenough.org/blog/2008/05/versioning-rest-web-services/

This post covers your exact case and builds upon the stuff I posted above.  ;)

Feb 10, 2012 at 4:13 PM

Umm...isn't this the same link I posted on Tuesday? Yes this is the approach I have been after but until recently have struggled to get it working in WCF/ASP.NET Web Api. Works now though :-)

Thanks for your input.

Feb 10, 2012 at 5:09 PM
LittleColin wrote:

Umm...isn't this the same link I posted on Tuesday? Yes this is the approach I have been after but until recently have struggled to get it working in WCF/ASP.NET Web Api. Works now though :-)

Thanks for your input.


Alas, it is.  Sorry, I missed that.  Got a lot going on outside of trolling this forum.  ;)