全局异常过滤器和Application_Error均未捕获未处理的异常

16

我有一个全局异常过滤器,名为LogErrorAttribute:

public class LogErrorAttribute : IExceptionFilter
{
    private ILogUtils logUtils;

    public void OnException(ExceptionContext filterContext)
    {
        if (this.logUtils == null)
        {
            this.logUtils = StructureMapConfig.Container.GetInstance<ILogUtils>();
        }

        this.logUtils.LogError(HttpContext.Current.User.Identity.GetUserId(), "Unknown error.", filterContext.Exception);
    }
}

它注册在标准的HandleErrorAttribute过滤器中:

filters.Add(new LogErrorAttribute());
filters.Add(new HandleErrorAttribute());

我是这样注册这些过滤器的:

FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);

我还有一个 Application_Error 备选方案:

protected void Application_Error()
{
    var exception = Server.GetLastError();
    Server.ClearError();
    var httpException = exception as HttpException;

    //Logging goes here

    var routeData = new RouteData();
    routeData.Values["controller"] = "Error";
    routeData.Values["action"] = "Index";

    if (httpException != null)
    {
        if (httpException.GetHttpCode() == 404)
        {
            routeData.Values["action"] = "NotFound";
        }
        Response.StatusCode = httpException.GetHttpCode();
    }
    else
    {
        Response.StatusCode = 500;
    }

    // Avoid IIS7 getting involved
    Response.TrySkipIisCustomErrors = true;

    // Execute the error controller
    if (exception != null)
    {
        this.errorLogger.Log(LogLevel.Error, "An unknown exception has occurred.", exception);
    }
    else if (httpException != null)
    {
        this.errorLogger.Log(LogLevel.Error, "An unknown HTTP exception has occurred.", httpException);
    }
    else
    {
        this.errorLogger.Log(LogLevel.Error, "An unknown error has occurred.");
    }
}

现在,我有一个 API 控制器,它从数据库中获取一些数据,然后使用 AutoMapper 将模型映射到视图模型:

var viewModels = AutoMapper.Mapper.Map(users, new List<UserViewModel>());

AutoMapper配置中,为其中一个属性执行自定义解析器:

var appModuleAssignments = this.appModuleAssignmentManager.Get(userId);
var appModules = appModuleAssignments.Select(x => this.appModuleManager.Get(x.AppModuleId));
return AutoMapper.Mapper.Map(appModules, new List<AppModuleViewModel>());

目前我正在强制appModuleManager.Get语句抛出常规异常:

throw new Exception("Testing global filter.");

这随后在 AutoMapper 中抛出异常,两者都未被处理,但全局过滤器和 Application_Error 都没有捕获到此异常。

我做错了什么?


发布后我做了几件事:

  1. Web.config 中添加了 customErrors 属性以将其打开。
  2. 删除了全局过滤器中的 HandleErrorAttribute,因为我意识到它会在运行时将错误标记为已处理。虽然我不指望它会执行,因为此错误发生在控制器之外,但它可能会在以后咬我一口。

2
我不想成为“那个人”,但你尝试过清除(bin和obj文件夹)并清除浏览器缓存吗?从初步的看法来看,你似乎做得一切都正确。 - Pseudonym
@Pseudonym 我完全理解,但是没错,我曾经有过那样的经历。 - Mike Perrenoud
唯一的Application_Error适用于我们所有的MVC应用程序,无需过滤器。我在这里没有记得任何额外的东西。 - Wiktor Zychla
你是否在使用.NET 4集成模式池的IIS上? - Wiktor Zychla
@WiktorZychla 目前我正在使用VS 2015通过IIS Express进行操作。 - Mike Perrenoud
显示剩余6条评论
2个回答

10
你添加的是MVC异常过滤器而非Web API异常过滤器。你的实现检查ExceptionContext而不是HttpActionExecutedContext
public override void OnException(HttpActionExecutedContext actionExecutedContext)

由于框架会引发 Http Exception 而不是 MVC Exception,因此您的 OnException 重写方法不会被触发。

所以,这里提供一个更完整的示例:

public class CustomExceptionFilter : ExceptionFilterAttribute

    {
       public override void OnException(HttpActionExecutedContext actionExecutedContext)

      {

        message = "Web API Error";
        status = HttpStatusCode.InternalServerError;

        actionExecutedContext.Response = new HttpResponseMessage()
        {
            Content = new StringContent(message, System.Text.Encoding.UTF8, "text/plain"),
            StatusCode = status
        };

        base.OnException(actionExecutedContext);
    }
}

另一个重要的步骤是在WebApiConfig.cs中注册全局Web API异常过滤器,在Register(HttpConfiguration config)方法中完成。

public static void Register(HttpConfiguration config)
{

...

config.Filters.Add(new CustomExceptionFilter());

}

我打算尝试这个更改。但是,即使这可以修复过滤器,为什么全局处理程序没有捕获错误呢? - Mike Perrenoud
1
@MichaelPerrenoud,我在我的答案中添加了一些内容。要点是HTTP上下文MVC上下文是不同的。为什么不同的解释相对复杂。最简单的解释方式是,Web API比完整的MVC框架更轻量级。 - Dave Alperovich
@DaveAlperovich - 我不确定你是如何得出这个结论的。OP在问题本身或标签中都没有表明他正在使用Web API。因此,既然他显然要尝试它,你的假设似乎是正确的。又是另一种情况,有人错误地标记了他们的问题,因为这似乎与ASP.NET MVC毫无关系,这是一个完全不同的框架,而不是Web API。如果在问题中包含了这些信息,我相信答案会更快地得到。 - NightOwl888
@NightOwl888,有趣的观点。只有一个提示。在OP的帖子中间,你会发现“现在,我有一个API控制器从数据库中获取一些数据”。虽然“API控制器”不一定意味着Web API,但根据惯例和OP问题的性质,我得出结论必须使用Web API而不是MVC框架。也许我们应该重新标记问题,但我正在等待最终确认... - Dave Alperovich
1
@DaveAlperovich,你说得对,这是一个Web API控制器。我今天会尝试这个解决方案。等我试完后再跟你联系。 - Mike Perrenoud
1
这是正确的,你必须使用ExceptionFilterAttribute或IFilter来实现Web API的过滤器。你不能像在MVC管道中那样捕获全局错误。 - Gurpreet

0
Dave Alperovich的回答将通过使用HttpActionExecutedContext解决您的问题。
public override void OnException(HttpActionExecutedContext context)

然而,由于您正在尝试捕获应用程序可能生成的所有可能异常,因此除了异常过滤器之外,还应该使用消息处理程序。可以在此处找到详细说明 - http://www.asp.net/web-api/overview/error-handling/web-api-global-error-handling

总之,有许多情况异常过滤器无法处理。例如:

  • 从控制器构造函数抛出的异常。
  • 从消息处理程序抛出的异常。
  • 在路由期间抛出的异常。
  • 在响应内容序列化期间抛出的异常。

因此,如果应用程序中的任何位置发生未处理的错误,则您的异常处理程序将捕获它并允许您采取特定操作

//Global exception handler that will be used to catch any error
public class MyExceptionHandler : ExceptionHandler
    {
        private class ErrorInformation
        {
            public string Message { get; set; }
            public DateTime ErrorDate { get; set; }            
        }

        public override void Handle(ExceptionHandlerContext context)
        {
            context.Result = new ResponseMessageResult(context.Request.CreateResponse(HttpStatusCode.InternalServerError, 
              new ErrorInformation { Message="An unexpected error occured. Please try again later.", ErrorDate=DateTime.UtcNow }));
        }
   }

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