ASP.NET Web API: Always Set Content-Type

I lost a few hours on my project one night last week because of this.

Backstory

I have been really enjoying my latest project using Asp.NET Web API & EF5. Although I’ve had a bit of a (mostly EF) learning curve, it’s gone swimmingly, and I’ve been blown away at the parsing magic performed by the Web API (The last one I built used the WCF Rest Starter kit. The differences in ease of implementation & sheer intuitiveness are mind blowing).

Long story short: This HAD been working earlier that afternoon. After I accidentally closed Fiddler & manually rebuilt my post, my Web API call was no longer “magically” parsing my (strongly typed) object from the body of my post. The object wasn’t parsing at all. It was JUST NULL.

Thankfully, Glenn Block was nice enough to look at my Fiddler Request (via Skype from a bus in Shanghai, even. We live in amazing times) & saw I had left the Content-Type: application/json header out of my post.

My Misunderstanding:

Because I no longer have to explicitly set the Accept: application/json header to receive json (“Json.NET is the default JSON serializer used by ASP.NET Web API” ), I also interpreted that as no longer needing to SEND the Content-Type: application/json for json-formatted body content [POSTs, PUTs].

That was a BAD, BAD, TIME-CONSUMING MISTAKE, but it *does* make sense, and this is why.

Section 7.2.1 of the HTTP/1.1 RFC 2616 spec says this about Entity Body Type:

7.2.1 Type

When an entity-body is included with a message, the data type of that body is determined via the header fields Content-Type and Content- Encoding. These define a two-layer, ordered encoding model:

entity-body := Content-Encoding( Content-Type( data ) )

Content-Type specifies the media type of the underlying data. Content-Encoding may be used to indicate any additional content codings applied to the data, usually for the purpose of data compression, that are a property of the requested resource. There is no default encoding.

Any HTTP/1.1 message containing an entity-body SHOULD include a Content-Type header field defining the media type of that body. If and only if the media type is not given by a Content-Type field, the recipient MAY attempt to guess the media type via inspection of its content and/or the name extension(s) of the URI used to identify the resource. If the media type remains unknown, the recipient SHOULD treat it as type “application/octet-stream”.

What Could Have Helped Me Troubleshoot My Post Mistake?

The Web Api did exactly what it was supposed to do. The fault here was my ignorance, but I have to wonder, if I made that assumption, what’s to prevent those consuming my api who’ve never heard of http put or delete from missing that header (other than using a library that covers it automatically)?

The way QT addresses it is by returning an error message that says, “content-type missing in HTTP POST, defaulting to application/octet-stream.” It would have been quite helpful if the Web Api issued a message similar to that.

I just wrote a simple extension method I’m calling when my expected object isn’t parsed:

Conditional:

if (registrant == null)
{
    return request.CheckContentTypeHeader();
}

Extension Method (returns verb-specific message for POST or PUT)

public static HttpResponseMessage CheckContentTypeHeader(this 
    HttpRequestMessage request)
{
    var postedContentType = request.Content.Headers.ContentType;
    long? contentLength = request.Content.Headers.ContentLength;
    string httpVerb = request.Method.Method;
 
    var responseMessage = request.CreateResponse(HttpStatusCode.BadRequest);
    if (postedContentType == null && contentLength.HasValue && contentLength > 0)
    {
        responseMessage.ReasonPhrase = string.Format("Unable to parse {0} because Content-
        Type is missing in HTTP {0}, which defaults content to application/octet-stream;
        Add Content-Type: application/json to your {0} header.", httpVerb);
    }
    else
    {
        responseMessage.ReasonPhrase = "Unable to parse " + httpVerb + ". Please check format.";
    }
    return responseMessage;
}

Great advice –> Glenn suggested making a message handler (“since it’s low level & applies to http”) that detects a body length greater than zero & throws an exception if missing the Content-Type Header if an alternate more global, catch-all solution is preferred.

In Fiddler, if you have content in the body & change the verb dropdown to PUT or DELETE, the entire window turns red. (p.s. Did you know you can upload files to post in Fiddler? Cool!) As mind-blowing as Fiddler is, I’m surprised it didn’t either tell me the Content-Type header was missing or explicitly populate it as application/octet in the Composer when I hit Execute (the way it does for Content-Length).

image

By the way, there’s now a Fiddler book (woo hoo!). It just came out in June (6/15/2012) & it’s only 10 bucks.  I just got it & am very excited to read it.

Summary / Conclusion

Unless you want your post / put to come through as application/octect, ALWAYS include Content-Type in your HTTP Requests.

I had to learn the hard way. Hopefully you won’t (of if you do, maybe this blog post will turn up in your search results Smile)

Fiddler Book

4 Comments on “ASP.NET Web API: Always Set Content-Type”

  1. TruncatedCoDr Says:

    Update: Because I don’t want my desire to stop processing at that point to be misconstrued, I modified the extension method to throw an httpresponseexception instead of returning the httpresponsemessage:

    public static HttpResponseMessage CheckContentTypeHeader(this HttpRequestMessage request)
    {
    var postedContentType = request.Content.Headers.ContentType; // = null
    long? contentLength = request.Content.Headers.ContentLength; // > 0
    string httpVerb = request.Method.Method;

    string errorMessage = string.Empty;
    if (postedContentType == null && contentLength.HasValue && contentLength > 0)
    {
    errorMessage = string.Format(“Unable to parse {0} because Content-Type is missing in HTTP {0}, which defaults content to application/octet-stream; Add Content-Type: application/json to your {0} header.”, httpVerb);
    }
    else
    {
    errorMessage = “Unable to parse ” + httpVerb + “. Please check format.”;
    }
    throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.BadRequest,errorMessage));
    }

    This link goes into more detail: http://codebetter.com/glennblock/2012/05/24/two-ways-to-work-with-http-responses-in-apicontroller-httpresponsemessage-and-httpresponseexception/

    Reply

  2. Darrel Miller (@darrel_miller) Says:

    Here is an alternative way to respond:

    throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.UnsupportedMediaType)
    {ReasonPhrase=”application/octet-stream not supported, did you forget the Content-Type header?”});

    Reply

  3. codecharm Says:

    >> I’m surprised it didn’t either tell me the Content-Type header was missing or explicitly populate it as application/octet in the Composer when I hit Execute (the way it does for Content-Length).

    I prefer that it doesn’t. Content-Length is just annoying to compute, and I often use Fiddler to deliberately create bad requests just to make sure that the service responds to bad conditions correctly. Content-Type isn’t any more special than any other header (i.e. it’s not a MUST), and it would get annoying having to change every time.

    Reply

Leave a comment