从由jQuery ajax调用的ASP.NET MVC控制器操作抛出的异常中获取消息

3

我正在尝试保护一个由jQuery ajax函数调用的控制器动作。

在我的动作方法中,我将代码放置在try-catch块中,因此我捕获了所有异常并返回带有异常消息的JSON结果。到这一点为止一切顺利。

问题出现在我在try-catch块之外抛出异常时,例如,如果异常在过滤器操作属性内引发。在这种情况下,我无法返回JSON结果,因为流会突然停止。

jQuery会在错误回调函数中捕获该异常。但是我唯一成功看到异常消息的地方是在xhr.responseText中,但它包含asp.net的整个“黄页”错误信息。

我使用的非常丑陋和hackish的解决方案是提取标题标签之间的文本。但我真的希望有更好的方法来做这件事!

在此情况下,您会怎么做?如何在不在动作方法内编写该逻辑的情况下保护您的ajax操作? 如何向用户显示由从jQuery ajax调用的ASP.NET MVC控制器动作抛出的未处理异常的消息?

jQuery Ajax调用:

$.ajax({
  type: "POST",
  contentType: "application/json; charset=utf-8",
  dataType: "json",
  url: url + "?id=" + id,
  success: function(data) {
                if(data.success){
                    alert('Success');
                } else {
                    alert('Fail: ' + data.message);
                },
  error: function(xhr, status, err) {
    // There has to be a better way to do this!!
    var title = xhr.responseText.split("<title>")[1].split("</title>")[0];
    alert(title);
  }
});

控制器操作:

[MyAttribute]
public ActionResult MyAction(int id)
{
    try
    {
        // Do something

        return Json(new { success = true }, JsonRequestBehavior.AllowGet);
    }
    catch (Exception exception)
    {
        return Json(new { success = false, message = exception.Message }, JsonRequestBehavior.AllowGet);
    }
}

操作过滤器属性:

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
    // This causes a 401 Unauthorized HTTP error.
    throw new UnauthorizedAccessException("Access Denied.");
}
2个回答

3
您可以在 Global.asax 中使用 Application_Error 方法:
protected void Application_Error(object sender, EventArgs e)
{
    HttpApplication app = (HttpApplication)sender;
    HttpContext context = app.Context;
    Exception ex = context.Server.GetLastError();
    bool isAjaxCall = string.Equals(
        "XMLHttpRequest", 
        context.Request.Headers["x-requested-with"], 
        StringComparison.OrdinalIgnoreCase
    );
    if (isAjaxCall)
    {
        context.Response.StatusCode = 200;
        context.Response.ContentType = "application/json";
        var json = new JavaScriptSerializer().Serialize(new { error = ex.Message });
        context.Response.Write(json);
    }
    else
    {
        // TODO: Handle the case of non async calls
    }
}

由于某些原因,我仍然会得到ASP.NET错误页面,即使我尝试添加“TrySkipIisCustomErrors = true”。但无论如何,非常感谢,这让我对问题有了很好的了解! - macrobug

1
也许你可以从HandleError属性中实现一个继承类,并使其在任何异常情况下返回Json,我将检查MVC代码并稍后编辑此答案。
* 编辑 * 请查看这个类。
public class ErrorHandlingJSon : HandleErrorAttribute
{
    public override void OnException(ExceptionContext filterContext)
    {
        /*****  Original code from MVC source  ******/

        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }
        if (filterContext.IsChildAction)
        {
            return;
        }

        // If custom errors are disabled, we need to let the normal ASP.NET exception handler
        // execute so that the user can see useful debugging information.
        if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
        {
            return;
        }

        Exception exception = filterContext.Exception;

        // If this is not an HTTP 500 (for example, if somebody throws an HTTP 404 from an action method),
        // ignore it.
        if (new HttpException(null, exception).GetHttpCode() != 500)
        {
            return;
        }

        if (!ExceptionType.IsInstanceOfType(exception))
        {
            return;
        }

        //***** This is the new code  *****//
        if (filterContext.HttpContext.Request.IsAjaxRequest()) // If it's a ajax request
        {
            filterContext.Result = new JsonResult // Set the response to JSon
            {
                Data = new { success = false, message = exception.Message }
            };

            filterContext.ExceptionHandled = true;
            filterContext.HttpContext.Response.Clear();
            filterContext.HttpContext.Response.StatusCode = 200;  // Maybe it should be 500, but this way you handle the JQuery on the success event
            filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
        }
        else //*** From here, is the original code againg **//
        {
            string controllerName = (string)filterContext.RouteData.Values["controller"];
            string actionName = (string)filterContext.RouteData.Values["action"];
            HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
            filterContext.Result = new ViewResult
            {
                ViewName = View,
                MasterName = Master,
                ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
                TempData = filterContext.Controller.TempData
            };
            filterContext.ExceptionHandled = true;
            filterContext.HttpContext.Response.Clear();
            filterContext.HttpContext.Response.StatusCode = 500;

            // Certain versions of IIS will sometimes use their own error page when
            // they detect a server error. Setting this property indicates that we
            // want it to try to render ASP.NET MVC's error page instead.
            filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
        }
    }

我使用了MVC源代码中相同的代码,但是在请求为Ajax时添加了不同的响应,因此它返回一个JSon结果。我将状态码设置为200,因此您可以在JQuery成功选项上处理异常。如果返回500,则应在错误选项上处理异常,这可能是更好的方法。

要使此功能正常工作,请在控制器顶部使用[ErrorHandlingJSon]。您必须将web config自定义错误设置为打开。 属性中有一行检查customErrors是否打开或关闭,您可以在此处返回Json,以便在customErrors关闭时正常工作。


1
如果发生ajax调用错误,我会返回错误代码500并在全局处理它。您可以通过在全局范围内设置ajaxSetup()来实现此目的,具体请参见http://api.jquery.com/jQuery.ajaxSetup/。 - uvita
谢谢!我最终使用了这个解决方案的一个变体。我只使用了被注释为//***** This is the new code *****//的那部分内容。因为我并没有使用ErrorHandler属性,所以删除了所有其他内容。 - macrobug
为什么不在else部分调用基本方法(//*** 从这里开始,是原始代码 **//)? - toebens
有道理,不过它会再次执行代码的第一部分,但我想这并不是很重要。 - David Martinez

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