Joel Feaster emailed to say he was having trouble with the Accept header handling of the web service. When he used jQuery’s getJSON method it would use the following Accept header:
application/json, text/javascript, */*
You’ll notice there are no q parameters in that header, so according to the HTTP 1.1 specification, they should all be treated as having a q value of 1. However, the specification does not dictate which format should take precedence when all acceptable formats have the same q value. But looking at that header and knowing it comes from a method named getJSON, you might hope the web service would pick application/json.
Accept Header Processing
Deep within the web service, the method GetAcceptHeaderContentTypes() will take an Accept header and turn it into a list of ContentType objects ordered by the clients preference. However, it uses the List<T>.Sort method, the help for which states:
This method uses Array.Sort, which uses the QuickSort algorithm. This implementation performs an unstable sort; that is, if two elements are equal, their order might not be preserved. In contrast, a stable sort preserves the order of elements that are equal.
So, the Sort method could (and in Joel’s case does) jumble up the formats which have the same q value. Whilst this is not wrong according to the specification, some clients are indicating their preference by the Accept header ordering. Therefore, I decided to look at improving the GetAcceptHeaderContentTypes to preserve the original order if no other preference is given.
I couldn’t (at least not quickly) find a stable sort algorithm within the .Net Framework. I could have copied one from the web or written one myself… but I decided to use a simpler approach. Instead of a list of ContentType objects, I created an object to wrap each ContentType object along with its original position. I then changed the implementation of IComparer so that if there is nothing else to choose between two formats, the original position would be used.
So, surely that would fix Joel’s problem. Well, not quite. Whilst looking again at the IComparer implementation, I realised there were a few more issues with it. I worked on it until all the samples provided in the HTTP 1.1 specification were being interpreted correctly.
Areas Support
I’ve recently been working on a large web service and wanted to make use of the new Areas feature that was included with ASP.Net MVC 2. So, I’ve also extended the VirtualPathProviderXsltEngine class (which is used to look up the XSLT view files) to understand the concept of Areas. I also added a set extension methods to allow the Areas registration to take into account Restful routes. Now you can use these in your AreaRegistration classes. For example:
public class XYZAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "XYZ";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRestfulRoute(
"Xyz_default",
"Xyz/{controller}",
new { controller = "Xyz", action = "List" }
);
}
}
The latest version of the code is available here: RESTfulMVCWebService23.zip (127.01 kb)