When performing post via ajax, Bad Request is returned instead of the JSON result
Asked Answered
B

6

25

Javascript

jqXHR = $.ajax({ url: $frm.attr("action"), type: "POST", dataType: "json", cache: false,
  headers: headers, contentType: "application/json;charset=UTF-8", data: ko.mapping.toJSON(data, map),
  beforeSend: function(x) {
    if (x && x.overrideMimeType) {
      return x.overrideMimeType("application/json;charset=UTF-8");
    }
  }
});

jqXHR.fail(function(xhr, err, msg) {  /* xhr.responseText  NEED TO BE JSON!!! */ });

In Chrome

Headers

Request Method:POST
Status Code:400 Bad Request
Request Headersview source
Accept:application/json, text/javascript, */*; q=0.01
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8,pt-BR;q=0.6,pt;q=0.4
Connection:keep-alive
Content-Length:10
Content-Type:application/json;charset=UTF-8
User-Agent:Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.117 Safari/537.36
X-Requested-With:XMLHttpRequest
Request Payloadview source {Id:0}
Response Headersview source
Cache-Control:private
Content-Length:54
Content-Type:application/json; charset=utf-8
Date:Thu, 27 Feb 2014 14:01:59 GMT
Server:Microsoft-IIS/8.0
X-AspNet-Version:4.0.30319
X-AspNetMvc-Version:5.1
X-Powered-By:ASP.NET

Response

[{"Name":"Nome","ErrorMessage":"campo obrigatório."}]

Works in chrome!


In IE8

Headers (Request)

POST /Motivos/Salvar HTTP/1.1
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: pt-br
x-requested-with: XMLHttpRequest
Content-Type: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)
Content-Length: 10
Connection: Keep-Alive
Pragma: no-cache

Headers (Response)

HTTP/1.1 400 Bad Request
Cache-Control: private
Content-Type: text/html
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 5.1
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 27 Feb 2014 13:51:46 GMT
Content-Length: 11

Bad Request

NOT WORK!!

Asp.net MVC

Filter

public class HandleExceptionAttribute : HandleErrorAttribute
{
    public override void OnException(ExceptionContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAjaxRequest() && filterContext.Exception != null)
        {
            filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
            var ex = filterContext.Exception.GetBaseException();
            filterContext.Result = new JsonResult
            {
                JsonRequestBehavior = JsonRequestBehavior.AllowGet,
                Data = new
                {
                    ex.Message,
                    ex.GetType().Name
                }
            };
            filterContext.ExceptionHandled = true;
        }
        else
        {
            base.OnException(filterContext);
        }
    }
}

Apply on GlobalFilterCollection

Controller

[ValidateJsonAntiForgeryToken, HttpPost]
public virtual JsonResult Salvar(TViewModel viewModel)
{
    if (ModelState.IsValid)
    {
        TEntity model;
        if (default(TKey).Equals(viewModel.Id))
        {
            model = Mapper.Map<TEntity>(viewModel);
            AdicionarEntidade(model, viewModel);
        }
        else
        {
            model = Repositorio.Get(viewModel.Id);
            Mapper.Map(viewModel, model, typeof(TViewModel), typeof(TEntity));
            SalvarEntidade(model, viewModel);
        }

        return SalvarResult(model);
    }

    Response.StatusCode = 400;
    return Json(ModelState.ToJson(), JsonRequestBehavior.AllowGet);
}

Extenssion

public static object ToJson(this ModelStateDictionary dic, params string[] othersMessages)
{
    var states = (from e in dic where e.Value.Errors.Count > 0
                  select new { Name = e.Key, e.Value.Errors[0].ErrorMessage }).ToList();

    if (othersMessages != null)
        foreach (var message in othersMessages)
            states.Add(new { Name = "", ErrorMessage = message });

    return states;
}

Questions

  • Why not have the xhr.resposeText object?
  • How to retrieve JSON in the same way that I recover in Chrome?

I need the JSON to populate the form!

Notes: 03/11/2014

When I add Response.TrySkipIisCustomErrors = true; in my controler, it works! responseText returns the json. Why?

Brown answered 27/2, 2014 at 14:19 Comment(6)
If you set a different content-type in $.ajax it'd be wise to set processData to false. Also, why are you overriding mime type in beforeSend? You have control over the return type on the server.Bedell
can you show what's the value in data: ko.mapping.toJSON(data, map) on both IE and chrome?Isotope
@KerryLiu Even setting processData: false, the error occurs. * About beforeSend, it was just a test. Even removed this event this error still occurs!Brown
@LeeGary The data: In IE "{"Id":"0"}" , In Chrome "{"Id":"0"}"Brown
Is it just me, or are your Chrome (response?) headers showing status code 400 Bad Request as well?Assibilate
@BrianNorth Is correct. ha that a required field is not filled, the status is correct in Chrome. responseText is is wrong in IEBrown
U
36

Think this is an issue with IIS trying to use custom error response rather sending the error message that the controller is generating.

<system.webServer>
    ...
    <httpErrors existingResponse="PassThrough"></httpErrors>
    ...
</system.webServer>

Or

Response.TrySkipIisCustomErrors = true;

Reference - https://mcmap.net/q/538959/-in-iis7-5-what-module-removes-the-body-of-a-400-bad-request

Check out this blog post http://weblog.west-wind.com/posts/2009/Apr/29/IIS-7-Error-Pages-taking-over-500-Errors

Since response code is set to 400, IIS replaces your content with its custom error page content

Uticas answered 18/3, 2014 at 11:18 Comment(0)
I
2

The issue I am seeing is that your trying to set the encoding of the JSON to UTF-8. Normally it works just fine, however, on IIS, it has a custom error to report that the UTF-8 is not necessary.

Few other things to note. You are doing a POST, so the server will be expecting a json file. If provided none, it will not know what to do.

Intermarriage answered 13/3, 2014 at 3:25 Comment(3)
1. About UTF-8 does not make sense, because it works in Chrome. I've tried removing the UTF-8 and the error still occurs!Brown
2. I am sending JSON to the server!Brown
well what is the json that you are sending? You only show the response json, response is what the server sends the client. ALso, you can only send one json file at a time. So if you have multiple forms being submited at once, it needs to be packed as one json file.Intermarriage
F
2

Your response comes back with Content-Type: text/html http header, but it should be application/json. This is not an issue in Chrome (and you don't get mismatch warnings in the console) because you're relying on overrideMimeType... which, you guessed it, is not IE-friendly. In fact, the following is never executed on older versions of IE:

if (x && x.overrideMimeType) {
  return x.overrideMimeType("application/json;charset=UTF-8");
}

Your solution could be to make sure the content is served with correct content type. If you're familiar with tampering tools like Burp Suite, you could add the correct header on-the-fly and see if that fixes the problem. I would probably avoid inlining methods like AddHeader and see if there is a way this can be fixed at a higher - routing, perhaps - level.

Frazer answered 18/3, 2014 at 4:58 Comment(0)
J
1

I have filled in a fail test that should help.

$.post($frm.attr("action"), ko.mapping.toJSON(data, map))
.done(function (dataVal) {
    //process dataVal
    var mystruct = GenerateCache($.parseJSON(dataVal));
})
.fail(function (jqxhr, textStatus, error) {
    if (jqxhr.responseText.indexOf("YourMoniker") != -1) {
        parseData($.parseJSON(jqxhr.responseText));
    } else {
        var err = textStatus + ', ' + error;
        console.log("Request Failed: " + err);
    }
});


function GenerateCache(data) {
    var obj = function () { };
    obj.prototype = data;
    return new obj();
}

Specifically look at the error handling in the .fail section.

Jenelljenelle answered 17/3, 2014 at 22:23 Comment(6)
I use $.ajax because I set headers. how to set headers with $.post?Brown
I see. That is probably the only and best way if you are going to try to set your own headers.Jenelljenelle
What is it in your headers that you need specifically?Jenelljenelle
You can send the dataType like $.post( url [, data ] [, success(data, textStatus, jqXHR) ] [, dataType ] ). You can also add data to the query string in the url like: /getData.aspx?param1=test&param2=54.... And this still let's you send the data.Jenelljenelle
Seeing as the $.ajax method sends the exact same request with method: 'POST' as $.post does, and the sent/received headers in the OP do show that IE is sending it as a POST request, changing from the one method to the other will get the exact same resultsAssibilate
But it reduces the amount of extra work if you don't actually need it.Jenelljenelle
A
0

It's not your controller, that's working fine. You're missing a required field: both IE and Chrome are returning status code 400 Bad Request - but only Chrome is properly processing the responseText and giving you [{"Name":"Nome","ErrorMessage":"campo obrigatório."}] meaning you have a missing form field.

Although I've searched all over and haven't found any reference to specific IE bugs in processing XMLHttpRequest.responseText with non-200 status codes, it looks like IE is replacing your response body with its own:

Headers (Response)

HTTP/1.1 400 Bad Request
...
Content-Length: 11

Bad Request

Indicates that the "content" as it treats it is the "Bad Request" status text, not the proper json response (which Chrome reads as content-length 54, for instance). This might mean IE is discarding your response body (I doubt it, that'd be bloody incredible) or it just isn't be processed "properly." Try dumping the rest of your jqXHR object and the arguments for your fail handler to see if you can find it in there somewhere.

Assibilate answered 17/3, 2014 at 18:40 Comment(0)
S
0

IE (all versions, including IE11) will put "Bad Request" in the status text and ignore the JSON you put in as the message.

In order to use the xhr.responseText in IE on error, you need to throw an exception instead of returning a Json or JsonResult with HttpStatusCode.BadRequest;

So... before:

Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(new { Message = "There is already a distribution set which covers part or all of this period" });

This works in Chrome, FF and any sane browser, really. After:

throw new Exception("You have posted invalid datas.");

As an unhandled exception, it will be passed to the browser as a response, this will work in Chrome, FF and even in IE. It is not graceful, same as all unhandled exceptions (or just exceptions, for that matter), but it will do the job of letting you receive an appropriate response.

Sall answered 1/4, 2016 at 14:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.