Sebazzz/SDammann.WebApi.Versioning

Strange behaviour for AcceptHeaderVersionedControllerSelector

Opened this issue · 2 comments

The relationship between AcceptHeaderVersionedControllerSelector and ASP.NET's logic for actually serializing the http response body is a bit strange. You can cause some strange situations with a header like Accept: application/json; q=0.8; version=1, application/xml; q=0.9; version=1. If I leave the default AcceptMediaType = "application/json", then AcceptHeaderVersionedControllerSelector is going to use that media type to pick the version, while ASP.NET will still return an XML body.

Things get even weirder if you change the XML's accept version: Accept: application/json; q=0.8; version=1, application/xml; q=0.9; version=2. AcceptHeaderVersionedControllerSelector still uses the JSON to pick version 1, then actually sends back version 1 XML. This could really confuse the client.

I've been scratching my head on this for a bit, thinking of changing AcceptMediaType to an IEnumerable<string>, and ordering the IEnumerable<MediaTypeWithQualityHeaderValue> acceptHeader by q value before passing in to GetVersionFromHeader(), but there's still a diconnect between AcceptHeaderVersionedControllerSelector and what format ASP.NET ultimately returns in the body.

Any ideas?

Wouldn't just ordering the IEnumerable<MediaTypeWithQualityHeaderValue> passed to AcceptHeaderVersionedControllerSelectorBase .GetVersionFromHeader resolve that problem?

ASP.NET is going to pick the MIME-type with the highest q for serializing the method body, and the AcceptHeaderVersionedControllerSelector will also pick the format with the highest q. This at least resolves the problem that the q parameter is not used.

It is still a dirty solution, I must say. The best way should be to hook into the Content Negotiation somehow. We'd need to find the controller that will be called, the action method that will be called and then call IContentNegotiatior.Negotiate to check which content type - and thus version - we should use to select a controller.

It is a bit of a chicken-egg problem. We need to select a controller based on the highest quality factor of given MIME-types, but we don't know what MIME-types are suitable without selecting an controller first. It will probably come down to some brute-force controller selection by iterating through all mime types. Pseudo code:

  1. Order MIME-types by quality factor

  2. Select next MIME-type as X

    a. Find the controller by controller name and version
    b. Find the action method that would be called
    c. If the return type of the action method can be formatted to X: go to 3; else: go to 2;

  3. Select the controller

Yeah, I think the rest of your comment goes on to explain that it's not as simple as the first paragraph suggests :) I'm not strong on content negotiation, but the page you linked helps. I was thinking in the case where I ask for Accept: application/json; q=0.8; version=1, application/xml; q=0.9; version=2, foo/bar; q=1.0; version=3 that sorting by q the AcceptHeaderVersionedControllerSelector (assuming we've told it about "foo/bar") will point at version 3, but by the time ASP.NET comes to serialize my DTOs, it has no idea what this "foo/bar" thing is, so it almost needs to be registered in two separate places. I'm not sure how that works at all on the serialization end.

So yeah, your pseudo code actually looks like a reasonable implementation and not very dirty (to my amateur eyes). It doesn't seem like a loop that will kill performance. Besides, it's a bit of an edge case of an edge case.

I originally intended to use your library to implement something as described in http://barelyenough.org/blog/2008/05/versioning-rest-web-services/ but I think I'd still need to dip into the content negotiation stuff if I start using vnd. mime types, anyway.