【问题标题】:Getting message from exception thrown by an ASP.NET MVC controller action called from jQuery ajax从 jQuery ajax 调用的 ASP.NET MVC 控制器操作引发的异常中获取消息
【发布时间】:2011-05-05 15:00:04
【问题描述】:

我正在尝试保护使用 jQuery ajax 函数调用的控制器操作。

在我的操作方法中,代码被 try-catch 块包围,因此我捕获了所有异常并返回带有异常消息的 JSON 结果。到那时,一切都很好。

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

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

我用来获取异常消息的非常丑陋和骇人听闻的解决方案是提取 标记之间的文本。但我真的希望有更好的方法来做到这一点!

在这种情况下你会怎么做?如果不在操作方法中编写该逻辑,您将如何保护您的 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.");
}

【问题讨论】:

    标签: asp.net-mvc ajax asp.net-mvc-2 jquery


    【解决方案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 配置自定义错误设置为开启。 属性上有一行检查 customErrors 是打开还是关闭,您可以在此处返回 Json,以便在 customErrors 关闭时工作。

    【讨论】:

    • 在 ajax 调用的情况下我会返回错误代码 500 并在全局范围内处理它。您可以通过使用 ajaxSetup() 全局设置它来实现这一点,如此处所述api.jquery.com/jQuery.ajaxSetup
    • 谢谢!我最终使用了这个解决方案的变体。我只使用了注释的部分 //***** 这是新代码 *****//。我删除了其他所有内容,因为我没有使用 ErrorHandler 属性。
    • 为什么不直接调用else部分的base方法(//***从这里开始,是不是原代码又是g**//)?
    • 有道理,它会再次执行代码的第一部分,但我想这并不重要。
    【解决方案2】:

    您可以在 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”。但无论如何,谢谢,这让我很好地了解了这个问题!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-26
    • 2014-04-04
    • 2011-03-15
    • 1970-01-01
    相关资源
    最近更新 更多