在 ASP.NET Web Api 中捕获所有未处理的异常

Posted

技术标签:

【中文标题】在 ASP.NET Web Api 中捕获所有未处理的异常【英文标题】:catch all unhandled exceptions in ASP.NET Web Api 【发布时间】:2013-04-08 09:04:55 【问题描述】:

如何捕获 所有 发生在 ASP.NET Web Api 中的未处理异常以便记录它们?

到目前为止我已经尝试过:

创建并注册ExceptionHandlingAttributeGlobal.asax.cs 中实现Application_Error 方法 订阅AppDomain.CurrentDomain.UnhandledException 订阅TaskScheduler.UnobservedTaskException

ExceptionHandlingAttribute 成功处理了控制器动作方法和动作过滤器中抛出的异常,但没有处理其他异常,例如:

操作方法返回的IQueryable 执行失败时引发的异常 消息处理程序抛出的异常(即HttpConfiguration.MessageHandlers) 创建控制器实例时抛出异常

基本上,如果异常会导致 500 Internal Server Error 返回给客户端,我希望它被记录下来。实现 Application_Error 在 Web 表单和 MVC 中做得很好 - 我可以在 Web Api 中使用什么?

【问题讨论】:

您是否尝试过使用ASP.NET Health Monitoring?只需启用它并查看您的异常是否未记录到事件日志中。 健康监控捕获我的 MVC 管道异常,但没有捕获我的 Web Api 管道异常。 谢谢 - 我花了一段时间才弄清楚为什么我无法记录我的构造函数/依赖注入问题,我以为我已经对 WebAPI 日志进行了排序...... 【参考方案1】:

这现在可以通过 WebAPI 2.1 实现(参见 What's New):

创建一个或多个 IExceptionLogger 实现。例如:

public class TraceExceptionLogger : ExceptionLogger

    public override void Log(ExceptionLoggerContext context)
    
        Trace.TraceError(context.ExceptionContext.Exception.ToString());
    

然后在您的应用程序的 HttpConfiguration 中注册,在这样的配置回调中:

config.Services.Add(typeof(IExceptionLogger), new TraceExceptionLogger());

或直接:

GlobalConfiguration.Configuration.Services.Add(typeof(IExceptionLogger), new TraceExceptionLogger());

【讨论】:

@NeilBarnwell 是的,Web API 2.1 对应 System.Web.Http 程序集版本 5.1.0。因此,您需要此版本或更高版本才能使用此处描述的解决方案。见the nuget package versions 某些 500 错误仍然不会被此捕获,例如。 HttpException - 远程主机关闭了连接。 global.asax Application_Error 是否还有地方可以处理 web api 处理之外的错误? 我喜欢官方文档在 msdn 上的详细程度,99% 的开发人员真正想要的只是记录错误的 8 行代码。 我确信它仍然无法捕获所有 500 错误:例如,如果在自定义 Owin 中间件的 Invoke 方法中抛出错误!【参考方案2】:

要回答我自己的问题,这是不可能的!

处理所有导致内部服务器错误的异常似乎是 Web API 应该具备的基本能力,因此我向 Microsoft 提出了Web API 的全局错误处理程序的请求:

https://aspnetwebstack.codeplex.com/workitem/1001

如果您同意,请转到该链接并投票!

同时,优秀的文章ASP.NET Web API Exception Handling 展示了几种不同的方法来捕获不同类别的错误。它比应有的复杂得多,而且它不会捕获所有内部服务器错误,但它是当今可用的最佳方法。

更新:全局错误处理现已实现并可在夜间构建中使用!它将在 ASP.NET MVC v5.1 中发布。以下是它的工作原理:https://aspnetwebstack.codeplex.com/wikipage?title=Global%20Error%20Handling

【讨论】:

似乎是使用控制器进行 ajax 调用而不是 web api 的理由。边界已经很模糊了。虽然如果 ELMAH 能够捕获它,也许有办法 Web API 2.1 中现在添加了全局错误处理。有关详细信息,请参阅我的答案。【参考方案3】:

Yuval 的答案是自定义对 Web API 捕获的未处理异常的响应,而不是用于记录,如链接的 page 中所述。有关详细信息,请参阅页面上的“何时使用”部分。始终调用记录器,但仅在可以发送响应时才调用处理程序。简而言之,使用logger 记录日志并使用处理程序自定义响应。

顺便说一句,我使用的是程序集 v5.2.3,而 ExceptionHandler 类没有 HandleCore 方法。我认为等价的是Handle。但是,简单地继承 ExceptionHandler (如 Yuval 的回答)是行不通的。就我而言,我必须按如下方式实现IExceptionHandler

internal class OopsExceptionHandler : IExceptionHandler

    private readonly IExceptionHandler _innerHandler;

    public OopsExceptionHandler (IExceptionHandler innerHandler)
    
        if (innerHandler == null)
            throw new ArgumentNullException(nameof(innerHandler));

        _innerHandler = innerHandler;
    

    public IExceptionHandler InnerHandler
    
        get  return _innerHandler; 
    

    public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
    
        Handle(context);

        return Task.FromResult<object>(null);
    

    public void Handle(ExceptionHandlerContext context)
    
        // Create your own custom result here...
        // In dev, you might want to null out the result
        // to display the YSOD.
        // context.Result = null;
        context.Result = new InternalServerErrorResult(context.Request);
    

请注意,与记录器不同的是,您通过替换默认处理程序而不是添加来注册处理程序。

config.Services.Replace(typeof(IExceptionHandler),
    new OopsExceptionHandler(config.Services.GetExceptionHandler()));

【讨论】:

这是一个很好的解决方案,这应该是“捕获或记录所有错误”的公认解决方案。当我只是扩展 ExceptionHandler 时,我永远无法弄清楚为什么它对我不起作用。 很好的解决方案。一旦为请求加载了 MVC 管道,这将非常有效。在此之前,IIS 仍会处理异常,包括在 startup.cs 中启动 OWIN 时。但是,在 spin up 完成处理 startup.cs 后的某个时刻,它的工作非常出色。【参考方案4】:

您还可以通过实现IExceptionHandler 接口(或继承ExceptionHandler 基类)来创建全局异常处理程序。它将是执行链中最后被调用的,毕竟注册了IExceptionLogger

IExceptionHandler 处理所有未处理的异常 控制器。这是列表中的最后一个。如果发生异常,则 IExceptionLogger 将首先被调用,然后是控制器 ExceptionFilters,如果仍未处理,IExceptionHandler 实施。

public class OopsExceptionHandler : ExceptionHandler

    public override void HandleCore(ExceptionHandlerContext context)
    
        context.Result = new TextPlainErrorResult
        
            Request = context.ExceptionContext.Request,
            Content = "Oops! Sorry! Something went wrong."        
        ;
    

    private class TextPlainErrorResult : IHttpActionResult
    
        public HttpRequestMessage Request  get; set; 

        public string Content  get; set; 

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        
            HttpResponseMessage response = 
                             new HttpResponseMessage(HttpStatusCode.InternalServerError);
            response.Content = new StringContent(Content);
            response.RequestMessage = Request;
            return Task.FromResult(response);
        
    

更多关于 here.

【讨论】:

只是好奇,这是在哪里注册的? Handler 可以注册到你的依赖注入框架中【参考方案5】:

您可能有自己不知道的现有 try-catch 块。

我认为我的新 global.asax.Application_Error 方法在我们的旧代码中没有被一致地调用以处理未处理的异常。

然后我在调用堆栈的中间发现了一些 try-catch 块,它们在异常文本上调用了 Response.Write。就是这样。将文本转储到屏幕上然后杀死异常石死。

所以异常被处理了,但是处理没有做任何有用的事情。一旦我删除了这些 try-catch 块,异常就会按预期传播到 Application_Error 方法。

【讨论】:

以上是关于在 ASP.NET Web Api 中捕获所有未处理的异常的主要内容,如果未能解决你的问题,请参考以下文章

在 ASP.NET 应用程序中捕获所有异常的最佳方法是啥?

ASP.NET Web API、405 状态码和异常处理

ASP.Net MVC 路由捕获所有 *.aspx 请求

所有 ASP.NET Web API 控制器都返回 404

Web API1.1 ASP.NET Web API入门

尝试在 ASP.net Web API 中发生授权之前使用 OWIN 注入授权标头