32
votes

I'm using ASP.NET WebAPI RC, and hosting an API controller with nothing fancy about it. Everything works fine with JSON, but I'm testing requesting different formats using the Accepts header, and that's where I'm having trouble.

I'm using jQuery to issue an AJAX request, and setting the 'dataType' parameter of the request. This correctly sets the appropriate Accept header as you will see below.

$.ajax({
    type: method,
    url: url,
    dataType: "xml",
    data: data || null,
    success: function (data) {
        // omitted
    }
});

Here is a save of the fiddler request/response. As you can see the Accept header says application/xml, but WebAPI returned JSON. I have also tried manually setting the Accept header to just "application/xml" (so it doesn't have the text/html stuff as well), but to no avail.

What the heck am I missing? (note: I've snipped some confidential info in the data but didn't tweak it otherwise)

GET http://localhost/insp**snip**6716 HTTP/1.1
Host: localhost
Connection: keep-alive
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.60 Safari/537.1
Accept: application/xml, text/xml, */*; q=0.01
Referer: http://localhost/inspector/api/test?
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
Cookie: m=34e2:|2c69:t|47ba:t|4e99:t; .INSPECTOR3COOKIE=08BA683091E2A457B1832E9B*snip*F911D9ED97076


HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
Persistent-Auth: true
X-Powered-By: ASP.NET
Date: Fri, 03 Aug 2012 22:27:42 GMT
Content-Length: 1816

{"Id":2416716,"ProjectId":36,"Url":"http://ins *snip, but obviously this is JSON not XML *

I'd like to point out I'm not tweaking any formatters in AppStart or anything, so as far as I understand, the JSON and XML formatters should be enabled by default.

UPDATE: I figured it out -- check my own answer below

7

7 Answers

42
votes

I figured it out!

I had this in my AppStart, because I wanted the Xml serializer not the DataContract serializer:

GlobalConfiguration.Configuration.Formatters.XmlFormatter.UseXmlSerializer = true;

HOWEVER... apparently there is something about my model that makes the Xml Serializer think it can't serialize it. I am guessing that is causing WebAPI to decide to use the JSON formatter instead.

It's completely non-intuitive that this harmless looking setting could actually affect which formatter is used. Hope the WebAPI people see this :)

Some kind of tool that let you understand the inputs and outputs of content negotiation process so you can debug issues like this would be nice.

14
votes

I had the same issue but fixed it by adding default constructors to all the models that I was returning.

The XML serializer creates blank model objects and then populates it via the setters on the properties. If the setters are protected or private then that property will not get serialized either

12
votes

The current answers in this thread already call out a lot of the reasons but just to summarize, the XmlSerializer only supports a limited number of types.

When looking for the "best" formatter, the DefaultContentNegotiator, as correctly described by AASoft, asks each of the formatters whether they can support a particular type. It then matches those formatters against the accept headers in the request.

If it doesn't find any match based on the accept headers then it picks the first that can serialize the type, in this case the JSON formatter. However, you can configure the DefaultContentNegotiator to instead of returning a default format then return a 406 None Accepted status code. This indicates to the client that no matching representation could be found and instead of sending data that the client may not be able to use it generates an error response.

Setting this option is described in the blog "ASP.NET Web API Updates – May 14" [1] under the section "Content Negotiation Improvements".

Hope this helps,

Henrik

[1] http://blogs.msdn.com/b/henrikn/archive/2012/05/14/asp-net-web-api-updates-may-14.aspx

5
votes

The answer has already been provided but I thought I'd put my findings so that it might be helpful to anyone coming later.

The culprit was IEnumerable. For example, returning object of Class Album containing IEnumerable and never getting XML return - only JSON.

I used

GlobalConfiguration.Configuration.Formatters.XmlFormatter.UseXmlSerializer = true; 

in the Global.asax as well. Which is actually necessary for xml returns. Still I wasn't getting XML back.

Then I Changed the IEnumerable to List and it worked fine. Looks like the XML Formatter in Web API cannot process the IEnumerable in return objects.

Hope this helps.

3
votes

Just as a followup to this. We had this problem when we had a List of objects in our return model but the object in the list didn't have a parameterless constructor. Our code looked like this:

public class ReturnClass
{
    public int Value { get; set; }

    public List<OtherClass> ListOfThings { get; set; }
}

public class OtherClass 
{
    public int OtherValue { get; set; }

    public OtherClass(OtherObject o) 
    {
       this.OtherValue = o.OtherValue;
    }  
}

We simply had to add a parameterless constructor for the SubClass object.

public class ReturnClass
{
    public int Value { get; set; }

    public List<OtherClass> ListOfThings { get; set; }
}

public class OtherClass 
{
    public int OtherValue { get; set; }

    public OtherClass(OtherObject o) 
    {
       this.OtherValue = o.OtherValue;
    }

    public OtherClass()
    {
    }

}
0
votes

Be careful using nullable int for any of the properties you may be serializing. A nullable int with config.Formatters.XmlFormatter.UseXmlSerializer = true set will cause Web API to return JSON no matter what your accept header says

0
votes

I just want to add one more reason this might happen, properties with internal get or set. The kind that are generated by VS when you are adding properties to a class by pressing Ctrl-.

like public string Foo {get; internal set;}