ASP.NET MVC全局错误处理

11
我有一个自定义的HandleError属性,用于处理MVC管道上的错误; 我在我的Global.asax上有一个protected void Application_Error(object sender, EventArgs e)方法,可以处理来自管道外部的错误。
我遇到了一种我不知道可能发生的情况; 在实现DI时,存在一个connectionString的依赖项,该依赖项从应用程序配置文件中获取。
由于连接字符串尚不存在,因此在创建控制器时会引发错误,这通常会使Application_Error处理程序触发,并呈现适当的错误页面(通过将部分视图呈现为字符串并将其作为响应发送,以防此失败,它只是向响应中写入“致命异常。”)。
除了在这种情况下,我得到了丑陋的默认ASP.NET“运行时错误”黄色屏幕。告诉我:
``` 运行时错误
说明: 服务器上发生应用程序错误。该应用程序的当前自定义错误设置阻止查看应用程序错误的详细信息。
详细信息: 要使本特定错误消息的详细信息可在本地服务器计算机上查看,请在当前Web应用程序的根目录中创建一个名为“web.config”的配置文件中的标记。然后,此标记应将其“mode”属性设置为“RemoteOnly”。要使详细信息可以在远程机器上查看,请将“mode”设置为“Off”。 ```
我没有在customErrors中设置defaultRedirect,也没有将其设置为Off,因为我不想重定向,而是希望在用户所在的同一页上呈现错误,避免不必要的重定向。
我该如何处理这种情况?为什么它的行为方式与控制器外部的任何其他错误不同?
我意识到这种情况不太可能经常发生,但我希望能够停止YSOD(部分是因为我想隐藏我正在使用的技术,但主要是因为它不太美观,也不太用户友好)。 我甚至尝试注册一个未处理异常的处理程序,但它也没有触发。
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的情况,它们甚至不会触发;这有点令人困惑。

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

3个回答

8

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

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

customErrors 是解决问题的方法。我不明白为什么重定向会成为问题?

我正在撰写一篇关于正确错误处理的博客文章:http://blog.gauffin.org/2011/11/how-to-handle-errors-in-asp-net-mvc/


9
自定义错误的重定向是一个问题,因为它基本上是在回应浏览器(或机器人)对于缺失页面的请求时,“是的,这个页面是可以的,但请暂时访问另一个页面”,然后下一个页面说,“不,我不可以,我不存在”。然而,您的浏览器和机器人(例如Google机器人)都会将缺失的页面存储为真实存在的页面,并在Google的情况下继续尝试索引它,因为临时重定向会在未来保持对原始页面的检查。这就是为什么在所请求的原始URL上返回状态码404非常重要的原因。 - Dale K
@jgauffin,当使用PartialView处理错误和异常时,重定向也是一个问题。您能否请看一下MVC中使用PartialView进行全局错误处理 - Jack
您提供的链接已经失效了 :)! - Hassan Monjezi
谢谢提醒。需要重新启动博客 :) - jgauffin

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

0

你还没有展示你如何在Application_Error事件中处理错误,也没有展示你的自定义HandleAttribute是如何实现的,所以很难猜测问题出在哪里。在实现Application_Error时要注意的一个常见问题是,你渲染了一些错误视图,但这些视图本身又会抛出错误。例如,一个依赖于布局的错误视图,在该布局中调用Html.Action助手来呈现另一个执行数据库访问等操作的动作的内容。你的错误视图应该尽可能静态。最好为它们设置不同的布局,以避免这种情况。

我还建议你查看以下方法来处理错误。


使用PartialView怎么样?您能否看一下MVC中使用PartialView进行全局错误处理 - Jack

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