【问题标题】:Error Handling in ASP.NET MVCASP.NET MVC 中的错误处理
【发布时间】:2009-05-01 16:58:43
【问题描述】:

如何正确处理 ASP.NET MVC 中的控制器抛出的异常? HandleError 属性似乎只处理 MVC 基础架构抛出的异常,而不是我自己的代码抛出的异常。

使用这个 web.config

<customErrors mode="On">
    <error statusCode="401" redirect="/Errors/Http401" />
</customErrors>

使用以下代码

namespace MvcApplication1.Controllers
{
    [HandleError]
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            // Force a 401 exception for testing
            throw new HttpException(401, "Unauthorized");
        }
    }
}

并没有达到我所希望的结果。相反,我得到了通用的 ASP.NET 错误页面,告诉我修改我的 web.config 以查看实际的错误信息。但是,如果不是抛出异常,而是返回无效视图,我会得到 /Shared/Views/Error.aspx 页面:

return View("DoesNotExist");

像我在上面所做的那样在控制器中抛出异常似乎绕过了所有 HandleError 功能,那么创建错误页面的正确方法是什么?如何与 MVC 基础架构配合使用?

【问题讨论】:

    标签: asp.net-mvc exception


    【解决方案1】:

    感谢 kazimanzurrashaid,这是我最终在 Global.asax.cs 中所做的事情:

    protected void Application_Error()
    {
        Exception unhandledException = Server.GetLastError();
        HttpException httpException = unhandledException as HttpException;
        if (httpException == null)
        {
            Exception innerException = unhandledException.InnerException;
            httpException = innerException as HttpException;
        }
    
        if (httpException != null)
        {
            int httpCode = httpException.GetHttpCode();
            switch (httpCode)
            {
                case (int) HttpStatusCode.Unauthorized:
                    Response.Redirect("/Http/Error401");
                    break;
            }
        }
    }
    

    我将能够根据我需要支持的任何其他 HTTP 错误代码向 HttpContoller 添加更多页面。

    【讨论】:

    • 我建议你查看kigg.codeplex.com 的来源,在那里你可以找到这个httpModule 的完整源代码,我只是想把global.asax 弄乱,这就是HttpModule 的原因。
    【解决方案2】:

    Controller.OnException(ExceptionContext context)。覆盖它。

    protected override void OnException(ExceptionContext filterContext)
    {
        // Bail if we can't do anything; app will crash.
        if (filterContext == null)
            return;
            // since we're handling this, log to elmah
    
        var ex = filterContext.Exception ?? new Exception("No further information exists.");
        LogException(ex);
    
        filterContext.ExceptionHandled = true;
        var data = new ErrorPresentation
            {
                ErrorMessage = HttpUtility.HtmlEncode(ex.Message),
                TheException = ex,
                ShowMessage = !(filterContext.Exception == null),
                ShowLink = false
            };
        filterContext.Result = View("ErrorPage", data);
    }
    

    【讨论】:

    • 是的,就是这样做的。使用提供的过滤器之一,或创建自己的异常过滤器是可行的方法。您可以根据异常类型更改视图,或者通过检查自定义过滤器中的更多信息来深入了解。
    • 您确定这不是 MVC 预览版中的内容,而是针对 1.0 进行了更改吗?我没有看到这个覆盖,而是看起来你应该覆盖 OnActionExecuted 并检查 filterContext.Exception 值
    • 您需要在 global.asax 文件中为 Application_Error 提供类似的代码,以确保您处理控制器外部发生的异常(例如,请求匹配多个路由的路由异常)。在控制器中处理异常似乎不那么粗略;即使它有效,在处理同时请求的服务器上的 Server.GetLastError 也感觉不对劲。在 global.asax 中有类似的代码会提醒你所有的异常。
    • @Chandana Dunno,副手。如果您在 msdn 文档中找不到它,那么它只是一个 poco,其中包含有关用于在网页中显示该内容的异常的信息。
    【解决方案3】:

    HandleError 属性似乎只处理 MVC 基础结构抛出的异常,而不是我自己的代码抛出的异常。

    那是错误的。实际上,HandleError 只会“处理”在您自己的代码中或在您自己的代码调用的代码中抛出的异常。换句话说,只有您的操作在调用堆栈中的例外情况。

    您所看到的行为的真正解释是您抛出的特定异常。 HandleError 与 HttpException 的行为不同。来自源代码:

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

    【讨论】:

    • 您指出我的其中一个陈述是不正确的,但没有解决无法为 HTTP 错误代码提供错误处理的真正问题。
    • 我可以解释正在发生的事情,但我需要更多信息才能告诉您为您的应用程序做正确的事情。 HandleError 作用于一个动作,你通常不会在一个动作中抛出 HttpExceptions。与其猜测你实际上想要做什么并给你可能不正确的建议,我可以解释你所看到的行为背后的事实。如果您想详细说明您真正想做的事情,请随时更新问题。
    【解决方案4】:

    我认为您无法根据带有 HandleError 属性的 HttpCode 显示特定的 ErrorPage,我更愿意为此目的使用 HttpModule。假设我有文件夹“ErrorPages”,其中每个特定错误存在不同的页面,并且映射在 web.config 中指定,与常规 Web 表单应用程序相同。以下是用于显示错误页面的代码:

    public class ErrorHandler : BaseHttpModule{
    
    public override void OnError(HttpContextBase context)
    {
        Exception e = context.Server.GetLastError().GetBaseException();
        HttpException httpException = e as HttpException;
        int statusCode = (int) HttpStatusCode.InternalServerError;
    
        // Skip Page Not Found and Service not unavailable from logging
        if (httpException != null)
        {
            statusCode = httpException.GetHttpCode();
    
            if ((statusCode != (int) HttpStatusCode.NotFound) && (statusCode != (int) HttpStatusCode.ServiceUnavailable))
            {
                Log.Exception(e);
            }
        }
    
        string redirectUrl = null;
    
        if (context.IsCustomErrorEnabled)
        {
            CustomErrorsSection section = IoC.Resolve<IConfigurationManager>().GetSection<CustomErrorsSection>("system.web/customErrors");
    
            if (section != null)
            {
                redirectUrl = section.DefaultRedirect;
    
                if (httpException != null)
                {
                    if (section.Errors.Count > 0)
                    {
                        CustomError item = section.Errors[statusCode.ToString(Constants.CurrentCulture)];
    
                        if (item != null)
                        {
                            redirectUrl = item.Redirect;
                        }
                    }
                }
            }
        }
    
        context.Response.Clear();
        context.Response.StatusCode = statusCode;
        context.Response.TrySkipIisCustomErrors = true;
    
        context.ClearError();
    
        if (!string.IsNullOrEmpty(redirectUrl))
        {
            context.Server.Transfer(redirectUrl);
        }
    }
    }
    

    【讨论】:

    • 这就是我要找的。我采用了一种稍微简单的方法,并将我的代码放在 Application_Error 中,以跨控制器处理相同的错误集。我将在这个问题的答案中发布解决方案。
    【解决方案5】:

    阅读本文的其他人可能遇到的另一种可能性(在您的情况下并非如此)是您的错误页面本身抛出错误,或者没有实现:

     System.Web.Mvc.ViewPage<System.Web.Mvc.HandleErrorInfo>
    

    如果是这种情况,那么您将获得默认错误页面(否则您将获得无限循环,因为它会不断尝试将自身发送到您的自定义错误页面)。这对我来说并不是很明显。

    此模型是发送到错误页面的模型。如果您的错误页面使用与您网站的其余部分相同的母版页并且需要任何其他模型信息,那么您将需要创建自己的 [HandleError] 类型的属性或覆盖 OnException 或其他东西。

    【讨论】:

    • 您也可以在控制器或控制器方法上声明 [HandleError] 属性,但这对 HTTP 错误没有帮助。
    【解决方案6】:
         protected override void OnException (ExceptionContext filterContext )
        {
            if (filterContext != null && filterContext.Exception != null)
            {
                filterContext.ExceptionHandled = true;
                this.View("Error").ViewData["Exception"] = filterContext.Exception.Message;
                this.View("Error").ExecuteResult(this.ControllerContext);
            }
        }
    

    【讨论】:

    • 您可能希望将 this.View("Error") 的结果存储在局部变量中,而不是调用该方法 2 次。
    【解决方案7】:

    我选择了 Controller.OnException() 方法,这对我来说是合乎逻辑的选择 - 因为我选择了 ASP.NET MVC,所以我更喜欢留在框架级别,并避免弄乱底层机制,如果可能。

    我遇到了以下问题:

    如果视图中发生异常,该视图的部分输出将与错误消息一起显示在屏幕上。

    在设置 filterContext.Result 之前,我通过清除响应来解决这个问题 - 像这样:

            filterContext.HttpContext.Response.Clear(); // gets rid of any garbage
            filterContext.Result = View("ErrorPage", data);
    

    【讨论】:

    • 您似乎在复制 HandleError 属性已经提供的行为。
    • 可能。学习所有这些东西是一项痛苦的努力。有太多不好的例子和过时的文档需要过滤。我想我只是贡献了另一个坏例子;-)
    【解决方案8】:

    Jeff Atwood 的 User Friendly Exception Handling module 非常适合 MVC。您可以在 web.config 中完全配置它,根本不需要更改 MVC 项目源代码。但是,它需要稍作修改才能返回原始 HTTP 状态,而不是 200 状态。看到这个相关的forum post

    基本上,在 Handler.vb 中,您可以添加如下内容:

    ' In the header...
    Private _exHttpEx As HttpException = Nothing
    
    ' At the top of Public Sub HandleException(ByVal ex As Exception)...
    HttpContext.Current.Response.StatusCode = 500
    If TypeOf ex Is HttpException Then
        _exHttpEx = CType(ex, HttpException)
        HttpContext.Current.Response.StatusCode = _exHttpEx.GetHttpCode()
    End If
    

    【讨论】:

      猜你喜欢
      • 2010-09-16
      • 2018-02-27
      • 2011-08-25
      • 1970-01-01
      • 2011-06-10
      • 2014-03-26
      • 2012-10-04
      • 2015-02-20
      • 1970-01-01
      相关资源
      最近更新 更多