【问题标题】:ASP.NET MVC Global error handlingASP.NET MVC 全局错误处理
【发布时间】:2012-03-16 16:47:57
【问题描述】:

我有一个自定义的HandleError 属性来处理 MVC 管道上的错误;我的Global.asax 上有一个protected void Application_Error(object sender, EventArgs e) 方法,它处理来自管道外部的错误。

我遇到了一个我不知道可能发生的情况;在实现 DI 时,有一个 connectionString 的依赖关系,它取自应用程序配置文件。

由于连接字符串尚不存在,创建控制器时会引发错误,这通常会触发 Application_Error 处理程序,并呈现正确的错误页面(通过将部分视图呈现为字符串并将其发送为响应,如果失败,它只会将“致命异常”写入响应。

除了在这种情况下,我得到了令人毛骨悚然的默认 ASP.NET“运行时错误”黄屏死机。告诉我:

运行时错误

描述:在 服务器。此应用程序的当前自定义错误设置可防止 查看应用程序错误的详细信息。

详细信息:启用此特定错误消息的详细信息 可在本地服务器计算机上查看,请创建一个 位于根目录中的“web.config”配置文件中的标记 当前 Web 应用程序的目录。这个标签 然后应该将其“模式”属性设置为“RemoteOnly”。启用 要在远程机器上查看的详细信息,请将“模式”设置为 “关”。

我的customErrors 中没有设置defaultRedirect,也没有转为Off,因为我不想重定向而是在用户所在的同一页面上呈现错误,避免出现不必要的重定向。

我该如何处理这样的情况?它的行为方式与控制器之外的任何其他错误不同的原因是什么?

我意识到这不太可能经常发生,但我希望能够停止 YSOD(部分是因为我想隐藏我正在使用的技术,但主要是因为它根本不漂亮也不用户友好)

我什至尝试为 UnhandledExceptions 注册一个处理程序,但它也没有触发。

AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

最终产生这个的代码是:

return ConfigurationManager.ConnectionStrings[key].ConnectionString;,其中ConnectionStrings[key]null

更新

这是处理应用程序错误的方式:

    protected void Application_Error(object sender, EventArgs e)
    {
        this.HandleApplicationError(new ResourceController());
    }

    public static void HandleApplicationError(this HttpApplication application, BaseController controller)
    {
        if (application == null)
        {
            throw new ArgumentNullException("application");
        }
        if (controller == null)
        {
            throw new ArgumentNullException("controller");
        }
        application.Response.Clear();
        Exception exception = application.Server.GetLastError();
        LogApplicationException(application.Response, exception);
        try
        {
            RenderExceptionViewResponse(application, exception, controller);
        }
        catch (Exception exceptionRenderingView) // now we're in trouble. let's be as graceful as possible.
        {
            RenderExceptionTextResponse(application, exceptionRenderingView);
        }
        finally
        {
            application.Server.ClearError();
        }
    }

    private static void LogApplicationException(HttpResponse response, Exception exception)
    {
        if (exception is HttpException)
        {
            HttpException httpException = (HttpException)exception;
            if (httpException.GetHttpCode() == (int)HttpStatusCode.NotFound)
            {
                _log.Debug(Resources.Error.WebResourceNotFound, httpException);
                response.Status = Resources.Constants.NotFound;
                return;
            }
        }
        _log.Error(Resources.Error.UnhandledException, exception);
    }

    private static void RenderExceptionViewResponse(HttpApplication application, Exception exception, BaseController controller)
    {
        if (!RenderAsJsonResponse(application, Resources.User.UnhandledExceptionJson))
        {
            ErrorViewModel model = WebUtility.GetErrorViewModel(exception);
            string result = controller.RenderViewToString(Resources.Constants.ErrorViewName, model);
            application.Response.Write(result);
        }
    }

    private static void RenderExceptionTextResponse(HttpApplication application, Exception exceptionRenderingView)
    {
        application.Response.Clear();

        if (!RenderAsJsonResponse(application, Resources.User.FatalExceptionJson))
        {
            application.Response.Write(Resources.User.FatalException);
        }
        _log.Fatal(Resources.Error.FatalException, exceptionRenderingView);
    }

    private static bool RenderAsJsonResponse(HttpApplication application, string message)
    {
        if (application.Request.IsAjaxRequest())
        {
            application.Response.Status = Resources.Constants.HttpSuccess;
            application.Response.ContentType = Resources.Constants.JsonContentType;
            application.Response.Write(message);
            return true;
        }
        return false;
    }

这是我用来装饰基本控制器的属性:

public class ErrorHandlingAttribute : HandleErrorAttribute
{
    public Type LoggerType { get; set; }

    public ErrorHandlingAttribute()
        : this(typeof(ErrorHandlingAttribute))
    {
    }

    public ErrorHandlingAttribute(Type loggerType)
    {
        LoggerType = loggerType;
    }

    public override void OnException(ExceptionContext filterContext)
    {
        if (filterContext.ExceptionHandled)
        {
            return;
        }
        if (filterContext.HttpContext.Request.IsAjaxRequest())
        {
            OnAjaxException(filterContext);
        }
        else
        {
            OnRegularException(filterContext);
        }
    }

    internal protected void OnRegularException(ExceptionContext filterContext)
    {
        Exception exception = filterContext.Exception;

        ILog logger = LogManager.GetLogger(LoggerType);
        logger.Error(Resources.Error.UnhandledException, exception);

        filterContext.HttpContext.Response.Clear();

        ErrorViewModel model = WebUtility.GetErrorViewModel(exception);
        filterContext.Result = new ViewResult
        {
            ViewName = Resources.Constants.ErrorViewName,
            ViewData = new ViewDataDictionary(model)
        };
        filterContext.ExceptionHandled = true;
    }

    internal protected void OnAjaxException(ExceptionContext filterContext)
    {
        Exception exception = filterContext.Exception;

        ILog logger = LogManager.GetLogger(LoggerType);
        logger.Error(Resources.Error.UnhandledAjaxException, exception);

        filterContext.HttpContext.Response.Clear();
        filterContext.HttpContext.Response.Status = Resources.Constants.HttpSuccess;

        string errorMessage = WebUtility.GetUserExceptionMessage(exception, true);

        filterContext.Result = new ExceptionJsonResult(new[] { errorMessage });
        filterContext.ExceptionHandled = true;
    }
}

这是我的customErrors

<customErrors mode="On" />

如您所见,这些内容非常广泛,但是在访问不存在ConnectionStringConnectionStrings 的情况下它们甚至不会触发;这有点令人费解。

确实在任何包含异常或不在控制器内的异常的控制器中触发,所以我不明白为什么这种情况有什么不同。

【问题讨论】:

    标签: c# asp.net-mvc-3 error-handling


    【解决方案1】:

    Application_Error 可能是在 ASP.NET WebForms 中处理错误的推荐方法。但不是在 MVC 中。

    我们有错误过滤器来为我们处理错误。过滤器的问题在于它仅在调用控制器时才起作用。哪个是 404 和 401 错误(未找到和授权)和您的数据库连接问题的问题。

    customErrors 是去这里的方式。我不明白为什么重定向应该是一个问题?

    我正在博客文章中进行正确的错误处理:http://blog.gauffin.org/2011/11/how-to-handle-errors-in-asp-net-mvc/

    【讨论】:

    • 自定义错误的重定向是一个问题,因为它基本上会向浏览器(或机器人)说丢失的页面“是的,这个页面没问题,但请暂时访问这个其他页面”,然后下一页说“不,我不好,我不存在”。但是,您的浏览器和机器人(例如 google bot)都会将丢失的页面存储为真实页面,并在 google 的情况下继续尝试将其编入索引,因为临时重定向表示将来会继续检查原始页面。这就是为什么在请求的原始 URL 上返回状态 404 非常重要的原因。
    • @jgauffin 重定向在更喜欢使用 PartialView 进行错误和异常处理时也是一个问题。你能看看Global Error handling using PartialView in MVC吗?
    • 您提供的link 已损坏:)!
    • 感谢您的提醒。需要重新启动博客:)
    【解决方案2】:

    您还没有展示您是如何处理 Application_Error 事件中的错误的,也没有展示您的自定义 HandleAttribute 是如何实现的,因此很难猜测问题可能是什么。实现 Application_Error 时要注意的一个常见问题是您呈现一些错误视图,该视图本身会引发错误。例如,一个依赖于 Layout 的错误视图,在此布局中,您正在调用 Html.Action 帮助器来呈现另一个操作的内容,该操作本身进行数据库访问和填充。您的错误视图应尽可能是静态的。理想情况下,您应该为他们提供不同的布局以避免这种情况。

    我还建议您查看following approach 以处理错误。

    【讨论】:

    【解决方案3】:

    为了对我们的访问者和搜索引擎正确,如果发生错误,我们应该返回有意义的 HTTP 状态代码,面向我们的网站。出错时返回 HTTP 状态码 200 是不公平的,即使我们同时返回一个视图,说明发生了错误。如果用户输入了错误的地址(最常见的用户错误),我们应该返回 HTTP 状态码 404,而不是返回或重定向到一个 View,那里将返回状态码 200。
    有一个简短而干净的建议摘要HERE

    【讨论】:

      猜你喜欢
      • 2022-01-07
      • 1970-01-01
      • 2012-03-26
      • 2011-12-14
      • 1970-01-01
      • 2013-05-04
      • 2010-11-24
      • 1970-01-01
      相关资源
      最近更新 更多