使用MVC返回带错误状态码的JSON

62

我想按照这个链接中的建议,向控制器返回一个错误,以便客户端可以采取适当的行动。该控制器是通过jquery AJAX从javascript中调用的。只有当我不将状态设置为错误时,才会收到Json对象。

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

如果我不设置状态码,我就会得到Json。 如果我设置状态码,我会收到状态码,但不会收到Json错误对象。

更新 我希望将一个错误对象作为JSON发送,这样可以在ajax的错误回调中处理它。

13个回答

45
我找到的最简洁的解决方案是创建自己的JsonResult,它继承了原始实现并允许您指定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);
    }
}
您可以像这样在您的控制器动作中使用它:

您可以像这样在控制器操作中使用它:

if(thereWereErrors)
{
    var errorModel = new { error = "There was an error" };
    return new JsonHttpStatusResult(errorModel, HttpStatusCode.InternalServerError);
}

1
为什么这个不再起作用了,ExecuteResult没有被调用? - kolexinfos
@kolexinfos 你正在使用哪个版本的.NET MVC? - Richard Garside
我正在使用ASP.NET MVC 5.2.4。 - kolexinfos
@kolexinfos 请参考我在下面的回答。我认为你可能正在使用与我相似的ASP.NET MVC版本,而我最终不得不更新Richard的答案以适应较新的版本。 - mogelbuster

44

我在这里找到了解决方案(链接)

我不得不创建一个操作过滤器来覆盖MVC的默认行为

这是我的异常类

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) { }
}

请注意,我有一个构造函数,用于初始化我的JSON。下面是操作过滤器:

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;
        }
    }

现在我有了这个操作过滤器,我将使用过滤器属性来装饰我的控制器

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

当抛出错误时,实现IExceptionFilter的操作筛选器将被调用,并在错误回调上向客户端返回Json。


18
如果读者在想“这些是否都必要?”,答案是“不必”。我曾经和 @Sarath 面临相同的情况,希望返回一个HTTP错误码和一些JSON数据来描述错误。结果发现,我只需要使用清除响应的代码行,IIS自定义错误会被跳过,并且状态码已被设置。我将这三行代码放在我的控制器操作中,在这三行之后,按照正常方式返回JSON数据即可。非常有效。 - René
7
确实如此,但如果您希望代码可重用性,您应该像答案所示那样操作,而不是将相同的代码复制/粘贴到每个操作中。 - Hades
如果我设置 StatusCode = 500,那么它会忽略我的JsonResponse,而是返回“由于发生内部服务器错误,无法显示页面。”。不确定这是否是由于OWIN管道的差异或其他原因。 - AaronLS
4
@René,我非常希望看到一个基于那个评论的答案。我不明白你在说哪三行代码。 - Nacht

32

3
嘭!我正在寻找的答案。在本地使用它很好,但在远程服务器上却不行。我知道可以通过一些配置设置来解决。干杯! - ThiagoPXP
1
如果在本地可以运行但在 Azure 上无法运行,一定要尝试这个! - Icycool
1
问题在于,我在本地(开发)机器上获取了StatusCode和Json响应,但在生产环境中运行时只能获取到StatusCode。这个解决方案是目前为止最简单的一个。 - Vikneshwar

13

想要向Json发送错误信息,一个简单的方法是控制响应对象的Http状态码并设置自定义的错误消息。

控制器

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

的翻译是:

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
        }
  });

为什么不返回HTTP 400? - CervEd

10

在Richard Garside的回答基础上,这是ASP.Net Core版本

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);
    }
}

然后在你的控制器中,返回如下:

// 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);

2
不需要将此功能添加到ASP.NET Core中,因为它已经存在 - https://dev59.com/q1gQ5IYBdhLWcg3wsGCZ - ethane
我很困惑,你为什么要在ASP.NET Core中这样做?只需返回NotFound("message");等即可... - Rosdi Kasim

7

6
在设置StatusCode后,您需要自己返回JSON错误对象,如下所示...
if (BadRequest)
{
    Dictionary<string, object> error = new Dictionary<string, object>();
    error.Add("ErrorCode", -1);
    error.Add("ErrorMessage", "Something really bad happened");
    return Json(error);
}

另一种方法是创建一个名为 JsonErrorModel 的模型,并对其进行填充。
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
}

请参考这里的答案。

4
我已经在响应对象中发现了错误。问题是我收到的是“Bad Request”而不是 JSON 对象。如果我不设置状态,我会得到带有错误的 JSON,但客户端不知道这是异常情况。 - Sarath
@Sarath,请查看答案中的链接。您需要在JQuery的ajax方法中使用error属性。 - Stefan Bossbaly
1
我面临的问题是,如果我设置状态,响应中就无法获取JSON。你指出的答案正是我所做的。问题在于,如果我设置响应状态,就无法获取JSON。 - Sarath
奇怪,也许你只想将JSON响应中的错误代码设置为HTTP错误代码,然后在客户端上进行检查。不要在服务器端设置错误代码。我会更新我的答案以反映解决方案。 - Stefan Bossbaly

5

有些回答依赖于抛出异常并在OnException重写中进行处理。在我的情况下,我想返回状态码,例如如果用户传递了错误的ID,则返回“坏请求”状态码。对我有效的方法是使用ControllerContext:

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

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

return jsonResult;

4
你需要决定想要"HTTP级别错误"(即错误代码)还是"应用级别错误"(即自定义JSON响应)。大多数使用HTTP的高级对象如果错误码设置为不在2xx范围内(即成功范围),则不会查看响应流。在你的情况下,你明确将错误码设置为失败(我想是403或500),并强制XMLHttp对象忽略响应主体。要解决问题- 要么在客户端处理错误条件,要么不设置错误代码,并返回包含错误信息的JSON(有关详细信息,请参见Sbossb的回复)。

1
谢谢您的回复。如果我不设置状态码,客户端如何知道发生了异常? - Sarath
@Sarath,如果你在客户端使用JavaScript的话,可以像你现在在服务器端做的那样检查response.errors && response.errors.length > 0 - Alexei Levenkov
2
@Alexi 是的。但我想隐式地通知客户端发生了错误。也就是说,我想在 ajax 调用的失败回调处理此条件,而不是成功回调,然后查找 errors.length。 - Sarath

4
如果你只是在使用MVC,最简单的方法是使用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);
    }

当错误返回给客户端时,您可以按照自己的喜好显示或处理它。
request.fail(function (jqXHR) {
        if (jqXHR.status == 500) {
            alert(jqXHR.statusText);
        }
    })

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接