jQuery post JSON fails when returning null from ASP.NET MVC
Asked Answered
C

1

17

I use ASP.NET MVC to post JSON from jQuery, and get some JSON back, using this little library function:

(function($) {
    $.postJson = function(url, data) {
        return $.ajax({
            url: url,

            data: JSON.stringify(data),
            type: 'POST',
            dataType: 'json',
            contentType: 'application/json; charset=utf-8'
        });
    };
})(jQuery);

So obviously I'll call this like:

$('#button').click(function() {
    $.postJson('/controller/action', { Prop1: 'hi', Prop2: 'bye' })
    .done(function(r) { alert('It worked.'); })
    .fail(function(x) { alert('Fail! ' + x.status); });
});

ASP.NET MVC 3 and ASP.NET MVC 4 support the submit side of things (before that you needed to extend ASP.NET MVC to handle submitting JSON), but the problem I'm running into is on the return. On the Controller I often return null to basically say "Success, nothing else to say," like:

[HttpPost]
public JsonResult DoSomething(string Prop1, string Prop2)
{
    if (doSomething(Prop1, Prop2)
        return Json(null); // Success

    return Json(new { Message = "It didn't work for the following reasons" });
}

I use this pattern frequently and it works fine - my success/done callback gets called and all is well. But recently I upgraded ASP.NET MVC and jQuery, and it's stopped working - instead my fail callback is getting called whenever I return Json(null);. Furthermore, I've inspected the response and the statusCode getting returned is in fact 200, so the server isn't failing - jQuery's just saying it is.

Clemmieclemmons answered 11/4, 2013 at 2:55 Comment(1)
BTW the global $(document).ajaxError... shows in exception.message: "Unexpected end of input" when Json(null) is returned (just to mention it, it didn't help me to find the reason for the problem).Neom
C
37

The problem was caused by upgrading from jQuery 1.8 to 1.9. In jQuery 1.7 and 1.8, this in MVC:

return Json(null);

was accepted as valid JSON and interpreted as null. Technically, this sends a blank string back to the client with HTTP 200, and that's good enough for jQuery <1.9.

But now (we're using jQuery 1.9.1), it attempts to parse the empty string as JSON, jQuery's JSON parser throws an exception on empty string, and that triggers a code chain that ends in a fail() callback instead.

A workaround is to instead pass this back from the server on success with no other information:

return Json(new{});

That passes muster with jQuery's JSON parser and all is well. This also works:

return Json(true);


Update

Musa notes below this behavior by MVC seems broken. This separate Stack Overflow answer to Using JSON.NET as the default JSON serializer in ASP.NET MVC 3 - is it possible? covers how to get MVC to return null for Json(null) - basically, use Json.NET instead of ASP.NET MVC's built-in JSON serializer. This is the solution I ultimately ended up using.

You need to use a slightly modified version of that answer to fix this however - code is below. Basically, don't include the if statement checking for null before passing to serialize, or you're right back in the same predicament.


Update 2

The default implementation of ISO 8601 Dates in Json.NET breaks Internet Explorer 9 and below when it attempts to parse it with new Date(...). In other words, these parse fine in Internet Explorer 9:

var date = new Date('2014-09-18T17:21:57.669');
var date = new Date('2014-09-18T17:21:57.600');

But this throws an exception:

var date = new Date('2014-09-18T17:21:57.6');

Internet Explorer 9's Date() implementation can't cope with anything but exactly three millisecond places. To fix this you have to override the Json.NET date format to force it. Included in the code below.

public class JsonNetResult : JsonResult
{
    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        var response = context.HttpContext.Response;

        response.ContentType = !String.IsNullOrEmpty(ContentType) ? ContentType : "application/json";

        if (ContentEncoding != null)
            response.ContentEncoding = ContentEncoding;

        var settings = new JsonSerializerSettings
        {
            Converters = new[] {new IsoDateTimeConverter
            {
                DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffK"
            }}
        };
        var jsonSerializer = JsonSerializer.Create(settings);

        jsonSerializer.Serialize(response.Output, Data);
    }
}

A Gist that demonstrates how to tie this into a BaseController:

https://gist.github.com/b9chris/6991b341e89bb0a4e6d801d02dfd7730

Clemmieclemmons answered 11/4, 2013 at 2:55 Comment(2)
return Json(null); returning an empty string sounds broken, it should return nullLogorrhea
@Logorrhea Probably, although at this point that might also be a breaking change - I went digging and it appears it has returned an empty string for null since the beginning days of MVC. I do agree that might be a cleaner solution to this problem though. This answer covers doing so, although the sample code checks for null and returns empty string before Json.Net can get a shot at it - remove those 2 lines and you'll get null returned unmodified. https://mcmap.net/q/46957/-using-json-net-as-the-default-json-serializer-in-asp-net-mvc-3-is-it-possibleClemmieclemmons

© 2022 - 2024 — McMap. All rights reserved.