62

I was trying to return an error to the call to the controller as advised in This link so that client can take appropriate action. The controller is called by javascript via jquery AJAX. I am getting the Json object back only if I don't set the status to error. Here is the sample code

if (response.errors.Length > 0)
   Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(response);

I get the Json if I don't set the statuscode. If I set the status code I get the status code back but not the Json error object.

Update I want to send an Error object as JSON so that it can be handled error callback of ajax.

Community
  • 1
  • 1
Sarath
  • 2,719
  • 1
  • 31
  • 39

13 Answers13

44

The neatest solution I've found is to create your own JsonResult that extends the original implementation and allows you to specify a HttpStatusCode:

public class JsonHttpStatusResult : JsonResult
{
    private readonly HttpStatusCode _httpStatus;

    public JsonHttpStatusResult(object data, HttpStatusCode httpStatus)
    {
        Data = data;
        _httpStatus = httpStatus;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        context.RequestContext.HttpContext.Response.StatusCode = (int)_httpStatus;
        base.ExecuteResult(context);
    }
}

You can then use this in your controller action like so:

if(thereWereErrors)
{
    var errorModel = new { error = "There was an error" };
    return new JsonHttpStatusResult(errorModel, HttpStatusCode.InternalServerError);
}
Richard Garside
  • 87,839
  • 11
  • 80
  • 93
43

I found the solution here

I had to create a action filter to override the default behaviour of MVC

Here is my exception class

class ValidationException : ApplicationException
{
    public JsonResult exceptionDetails;
    public ValidationException(JsonResult exceptionDetails)
    {
        this.exceptionDetails = exceptionDetails;
    }
    public ValidationException(string message) : base(message) { }
    public ValidationException(string message, Exception inner) : base(message, inner) { }
    protected ValidationException(
    System.Runtime.Serialization.SerializationInfo info,
    System.Runtime.Serialization.StreamingContext context)
        : base(info, context) { }
}

Note that I have constructor which initializes my JSON. Here is the action filter

public class HandleUIExceptionAttribute : FilterAttribute, IExceptionFilter
{
    public virtual void OnException(ExceptionContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }
        if (filterContext.Exception != null)
        {
            filterContext.ExceptionHandled = true;
            filterContext.HttpContext.Response.Clear();
            filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
            filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
            filterContext.Result = ((ValidationException)filterContext.Exception).myJsonError;
        }
    }

Now that I have the action filter, I will decorate my controller with the filter attribute

[HandleUIException]
public JsonResult UpdateName(string objectToUpdate)
{
   var response = myClient.ValidateObject(objectToUpdate);
   if (response.errors.Length > 0)
     throw new ValidationException(Json(response));
}

When the error is thrown the action filter which implements IExceptionFilter get called and I get back the Json on the client on error callback.

Sarath
  • 2,719
  • 1
  • 31
  • 39
  • 18
    In case the reader is thinking "Is all that necessary?", the answer is "no". - I was in the same situation as @Sarath and wanted to return a HTTP error code and some JSON data that described the error. It turned out I could just use the lines where the response was cleared, IIS custom errors are skipped and the status code is sat. I put those 3 lines in my Action on my Controller, and after those 3 lines, I just returned my JSON data as normal. Worked like a charm. – René Feb 10 '13 at 16:17
  • 7
    Indeed that's true, but to be reusable you would want to do it as the answer indicates rather than copy/past the same code into each action. – Hades Jun 06 '13 at 15:35
  • If I set `StatusCode = 500` then it ignores my JsonResponse and instead returns "The page cannot be displayed because an internal server error has occurred.". Not sure if this is due to a difference in the OWIN pipeline or what. – AaronLS Apr 21 '16 at 19:36
  • 3
    @René I would very much like to see an answer based on that comment. I can't understand which three lines you are talking about. – Nacht Nov 12 '19 at 05:32
32

There is a very elegant solution to this problem, just configure your site via web.config:

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

Source: https://serverfault.com/questions/123729/iis-is-overriding-my-response-content-if-i-manually-set-the-response-statuscode

Community
  • 1
  • 1
Gabrielius
  • 1,045
  • 12
  • 18
  • 3
    BOOM! The answer I was looking for. It worked fine for me locally but not in the remote server. I knew it could be done with some config settings. Cheers! – ThiagoPXP Dec 08 '15 at 07:40
  • 1
    Definitely try this if it works in local but not azure! – Icycool Jan 06 '17 at 20:29
  • 1
    For the problem was that, I was getting the StatusCode and the Json Response in the local(dev) machine, but while running it from the production I was getting just the StatusCode. This solution is byfar the most easiest one. – Vikneshwar Jan 18 '18 at 05:14
13

A simple way to send a error to Json is control Http Status Code of response object and set a custom error message.

Controller

public JsonResult Create(MyObject myObject) 
{
  //AllFine
  return Json(new { IsCreated = True, Content = ViewGenerator(myObject));

  //Use input may be wrong but nothing crashed
  return Json(new { IsCreated = False, Content = ViewGenerator(myObject));  

  //Error
  Response.StatusCode = (int)HttpStatusCode.InternalServerError;
  return Json(new { IsCreated = false, ErrorMessage = 'My error message');
}

JS

$.ajax({
     type: "POST",
     dataType: "json",
     url: "MyController/Create",
     data: JSON.stringify(myObject),
     success: function (result) {
       if(result.IsCreated)
     {
    //... ALL FINE
     }
     else
     {
    //... Use input may be wrong but nothing crashed
     }
   },
    error: function (error) {
            alert("Error:" + erro.responseJSON.ErrorMessage ); //Error
        }
  });
10

Building on the answer from Richard Garside, here's the ASP.Net Core version

public class JsonErrorResult : JsonResult
{
    private readonly HttpStatusCode _statusCode;

    public JsonErrorResult(object json) : this(json, HttpStatusCode.InternalServerError)
    {
    }

    public JsonErrorResult(object json, HttpStatusCode statusCode) : base(json)
    {
        _statusCode = statusCode;
    }

    public override void ExecuteResult(ActionContext context)
    {
        context.HttpContext.Response.StatusCode = (int)_statusCode;
        base.ExecuteResult(context);
    }

    public override Task ExecuteResultAsync(ActionContext context)
    {
        context.HttpContext.Response.StatusCode = (int)_statusCode;
        return base.ExecuteResultAsync(context);
    }
}

Then in your controller, return as follows:

// Set a json object to return. The status code defaults to 500
return new JsonErrorResult(new { message = "Sorry, an internal error occurred."});

// Or you can override the status code
return new JsonErrorResult(new { foo = "bar"}, HttpStatusCode.NotFound);
Lee Oades
  • 1,638
  • 17
  • 24
  • 2
    There is no need to add this to ASP.NETCore, this functionality exists - https://stackoverflow.com/questions/42360139/asp-net-core-return-json-with-status-code – ethane Jun 08 '18 at 09:06
  • I am puzzled why you did this in ASP.NET Core? Just return `NotFound("message");` etc would do... – Rosdi Kasim Nov 28 '18 at 04:48
7

The thing that worked for me (and that I took from another stackoverflow response), is to set the flag:

Response.TrySkipIisCustomErrors = true;
Philippe
  • 28,207
  • 6
  • 54
  • 78
6

You have to return JSON error object yourself after setting the StatusCode, like so ...

if (BadRequest)
{
    Dictionary<string, object> error = new Dictionary<string, object>();
    error.Add("ErrorCode", -1);
    error.Add("ErrorMessage", "Something really bad happened");
    return Json(error);
}

Another way is to have a JsonErrorModel and populate it

public class JsonErrorModel
{
    public int ErrorCode { get; set;}

    public string ErrorMessage { get; set; }
}

public ActionResult SomeMethod()
{

    if (BadRequest)
    {
        var error = new JsonErrorModel
        {
            ErrorCode = -1,
            ErrorMessage = "Something really bad happened"
        };

        return Json(error);
    }

   //Return valid response
}

Take a look at the answer here as well

Community
  • 1
  • 1
Stefan Bossbaly
  • 6,682
  • 9
  • 53
  • 82
  • 4
    I already have the error in response object. The issue is that I get "Bad Request" and not the JSON object. If I don't set the status I get the JSON with errors but client does not know that it is an exception condition. – Sarath Jul 06 '12 at 22:50
  • @Sarath Check the link in the answer. You need to use the error attribute of the ajax method in JQuery – Stefan Bossbaly Jul 07 '12 at 14:55
  • 1
    the problem I am facing is that on the response I am not getting the JSON back if I set the status. The answer you pointed is what exactly I am doing. The problem being if I set response status I do not get the JSON. – Sarath Jul 07 '12 at 19:48
  • Weird well you might just want to set the error code in the JSON response to the http error code and then check it on the client. Don't set the error code on the server side. I will update my answer to reflect the solution – Stefan Bossbaly Jul 07 '12 at 23:23
5

Several of the responses rely on an exception being thrown and having it handled in the OnException override. In my case, I wanted to return statuses such as bad request if the user, say, had passed in a bad ID. What works for me is to use the ControllerContext:

var jsonResult = new JsonResult { JsonRequestBehavior = JsonRequestBehavior.AllowGet, Data = "whoops" };

ControllerContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;

return jsonResult;
dotnetnutty
  • 381
  • 5
  • 10
4

You need to decide if you want "HTTP level error" (that what error codes are for) or "application level error" (that what your custom JSON response is for).

Most high level objects using HTTP will never look into response stream if error code set to something that is not 2xx (success range). In your case you are explicitly setting error code to failure (I think 403 or 500) and force XMLHttp object to ignore body of the response.

To fix - either handle error conditions on client side or not set error code and return JSON with error information (see Sbossb reply for details).

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • 1
    Thanks for ur response. How will the client know that there was an exception it I don't set the status code? – Sarath Jul 06 '12 at 22:47
  • @Sarath, The same check `response.errors && response.errors.length > 0` as you are doing now on server side (assuming JavaScript client). – Alexei Levenkov Jul 06 '12 at 22:51
  • 2
    @Alexi Yes. But I would like to implicitly inform the client that an error has occurred. That is to say I would like to handle this condition on failure callback of ajax call instead of success callback and then look for errors.length. – Sarath Jul 06 '12 at 23:00
4

If you are just using MVC the simplest way is to use HttpStatusCodeResult.

public ActionResult MyAjaxRequest(string args)
    {
        string error_message = string.Empty;
        try
        {
            // successful
            return Json(args);
        }
        catch (Exception e)
        {
            error_message = e.Message;
        }

        return new HttpStatusCodeResult(500, error_message);
    }

When the error is returned to the client you can display it or action it how you like.

request.fail(function (jqXHR) {
        if (jqXHR.status == 500) {
            alert(jqXHR.statusText);
        }
    })
RJohn
  • 121
  • 3
3

And if your needs aren't as complex as Sarath's you can get away with something even simpler:

[MyError]
public JsonResult Error(string objectToUpdate)
{
   throw new Exception("ERROR!");
}

public class MyErrorAttribute : FilterAttribute, IExceptionFilter
{
   public virtual void OnException(ExceptionContext filterContext)
   {
      if (filterContext == null)
      {
         throw new ArgumentNullException("filterContext");
      }
      if (filterContext.Exception != null)
      {
         filterContext.ExceptionHandled = true;
         filterContext.HttpContext.Response.Clear();
         filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
         filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
         filterContext.Result = new JsonResult() { Data = filterContext.Exception.Message };
      }
   }
}
Hakan Fıstık
  • 16,800
  • 14
  • 110
  • 131
Brian
  • 6,910
  • 8
  • 44
  • 82
2

I was running Asp.Net Web Api 5.2.7 and it looks like the JsonResult class has changed to use generics and an asynchronous execute method. I ended up altering Richard Garside's solution:

public class JsonHttpStatusResult<T> : JsonResult<T>
{
    private readonly HttpStatusCode _httpStatus;

    public JsonHttpStatusResult(T content, JsonSerializerSettings serializer, Encoding encoding, ApiController controller, HttpStatusCode httpStatus) 
    : base(content, serializer, encoding, controller)
    {
        _httpStatus = httpStatus;
    }

    public override Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var returnTask = base.ExecuteAsync(cancellationToken);
        returnTask.Result.StatusCode = HttpStatusCode.BadRequest;
        return returnTask;
    }
}

Following Richard's example, you could then use this class like this:

if(thereWereErrors)
{
    var errorModel = new CustomErrorModel("There was an error");
    return new JsonHttpStatusResult<CustomErrorModel>(errorModel, new JsonSerializerSettings(), new UTF8Encoding(), this, HttpStatusCode.InternalServerError);
}

Unfortunately, you can't use an anonymous type for the content, as you need to pass a concrete type (ex: CustomErrorType) to the JsonHttpStatusResult initializer. If you want to use anonymous types, or you just want to be really slick, you can build on this solution by subclassing ApiController to add an HttpStatusCode param to the Json methods :)

public abstract class MyApiController : ApiController
{
    protected internal virtual JsonHttpStatusResult<T> Json<T>(T content, HttpStatusCode httpStatus, JsonSerializerSettings serializerSettings, Encoding encoding)
    {
        return new JsonHttpStatusResult<T>(content, httpStatus, serializerSettings, encoding, this);
    }

    protected internal JsonHttpStatusResult<T> Json<T>(T content, HttpStatusCode httpStatus, JsonSerializerSettings serializerSettings)
    {
        return Json(content, httpStatus, serializerSettings, new UTF8Encoding());
    }

    protected internal JsonHttpStatusResult<T> Json<T>(T content, HttpStatusCode httpStatus)
    {
        return Json(content, httpStatus, new JsonSerializerSettings());
    }
}

Then you can use it with an anonymous type like this:

if(thereWereErrors)
{
    var errorModel = new { error = "There was an error" };
    return Json(errorModel, HttpStatusCode.InternalServerError);
}
mogelbuster
  • 1,066
  • 9
  • 19
  • This works for me. But the http status was hard coded with bad request. Have to change it from return Task.Result.StatusCode = HttpStatusCode.BadRequest; to return Task.Result.StatusCode = _httpStatus; – Tochi Dec 12 '22 at 16:33
0

Here is the JsonResult override answer for ASP.NET v5+ . I have tested and it works just as well as in earlier versions.

public class JsonHttpStatusResult : JsonResult
{
    private readonly HttpStatusCode _httpStatus;

    public JsonHttpStatusResult(object data, HttpStatusCode httpStatus) : base(data)
    {
        _httpStatus = httpStatus;
    }

    public override Task ExecuteResultAsync(ActionContext context)
    {
        context.HttpContext.Response.StatusCode = (int)_httpStatus;
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        var services = context.HttpContext.RequestServices;
        var executor = services.GetRequiredService<IActionResultExecutor<JsonResult>>();
        return executor.ExecuteAsync(context, this);
    }
}
Trujllo
  • 100
  • 1
  • 10
  • 1
    Just learned that there is no reason to do this in .NET Core and therefore 5+. Instead use JsonResult: `new JsonResult("error") { status = (int)HttpStatusCode.InternalServerError }` – Trujllo May 18 '22 at 10:23