在 ASP.NET Web API 2 中禁用 *all* 异常处理(为我自己腾出空间)?

Posted

技术标签:

【中文标题】在 ASP.NET Web API 2 中禁用 *all* 异常处理(为我自己腾出空间)?【英文标题】:Disable *all* exception handling in ASP.NET Web API 2 (to make room for my own)? 【发布时间】:2016-03-16 01:36:36 【问题描述】:

我想在中间件组件中连接异常处理,如下所示:

public override async Task Invoke(IOwinContext context)

    try
    
        await Next.Invoke(context);
    
    catch (Exception ex)
    
        // Log error and return 500 response
    

但是,我想捕获的一些异常正在被 Web API 管道捕获并转换为HttpErrorResponses,然后我才能找到它们。在此过程中,我丢失了很多有关错误的详细信息,因此在调试等时无法获得有用的堆栈跟踪(调试器甚至在抛出异常时都不会停止 - 我必须手动单步执行代码并查看它失败的地方......)。

我尝试使用以下实现添加自定义异常处理程序:

public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)

    var owinContext = context.Request.GetOwinContext();
    owinContext.Set(Constants.ContextKeys.Exception, context.Exception);
    return Task.FromResult(0);

在我的启动配置中通过config.Services.Replace(typeof(IExceptionHandler), new MyExceptionHandler());注册,但是通过Next.Invoke(context)执行后查看

context.Get<Exception>(Constants.ContextKeys.Exception);

仍然没有给我我想要的所有细节,也没有在调试器的故障点处停止。

有没有办法可以完全关闭所有内置错误处理,以便我自己的中间件可以处理?

澄清,因为很多人似乎误解了我所追求的:

Web API 中的内置错误处理捕获一些(但不是全部)异常并将它们重写为 500 个响应。 我想捕获所有异常,做一些日志记录,然后然后发出 500 个响应我选择的信息(对于大多数,请参阅下一个项目符号)。 还有一些异常表示业务逻辑错误,我想为此返回 40x 错误。 我希望它位于(应用程序)管道的顶部,即在请求生命周期中包装 所有内容 我想使用 OWIN 处理此问题,以使其可移植到未来可能的自托管方案(即,此应用程序将始终托管在 IIS 上并不是一成不变的 - HTTP 模块、Global.asax.cs 等在这里不相关)。

【问题讨论】:

“抛出异常时调试器甚至不会停止”-您是否在 VS 中尝试过,Debug->Exception... 然后为所有异常选择检查“Thrown”。这将导致调试器在每次抛出异常时停止。 @lcryder:是的,但这仍然不能满足我的所有需求。例如,它也会在我确实在其他地方处理的异常上停止调试器,从而从不首先传播到中间件。 您到底想对这些错误做什么?它纯粹是为了记录目的吗?如果是这样,那么 HTTP 模块可能是合适的,而且实现起来非常简单。 @DavidG:不,它既记录了日志,又给出了适当的响应。 HTTP 模块以与中间件相同的方式解决问题(但使用不同的管道,即不严格相关)。 Web API 已经将异常重写为 500 响应仍然存在问题,在中间件(或 HTTP 模块)到达之前丢弃了大量信息。 【参考方案1】:

更新:I blogged about this。在研究博客文章时,我发现了一些改进的潜力;我已经更新了这个答案的相关部分。有关为什么我认为这比此处的所有其他建议或默认行为更好的更多详细信息,请阅读整篇文章:)


我现在采用了以下方法,它似乎工作正常,即使不是 100% 符合我的要求:

创建一个类PassthroughExceptionHandler

public class PassthroughExceptionHandler : IExceptionHandler

    public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
    
        // don't just throw the exception; that will ruin the stack trace
        var info = ExceptionDispatchInfo.Capture(context.Exception);
        info.Throw();
        return Task.CompletedTask;
    

让那个类替换 Web API 的IExceptionHandler 服务:

config.Services.Replace(typeof(IExceptionHandler), new PassthroughExceptionHandler());

创建一个做我想做的中间件类:

public class ExceptionHandlerMiddleware

    public override async Task Invoke(IOwinContext context)
    
        try
        
            await Next?.Invoke(context);
        
        catch (Exception ex)
        
            // handle and/or log
        
    

首先在堆栈中注册该中间件:

app.Use<ExceptionHandlerMiddleware>()
   .UseStageMarker(PipelineStage.Authenticate)
   // other middlewares omitted for brevity
   .UseStageMarker(PipelineStage.PreHandlerExecute)
   .UseWebApi(config);

我仍会将赏金奖励给提出建议的任何人(赏金已过期...)我仍在寻找更好的解决方案,例如,当 抛出未处理的异常。 (当我在处理程序中重新抛出异常时,这种方法会使 VS 中断,但原始调用堆栈丢失了;我必须在故障行设置断点并再次调试,以便能够在抛出异常时拦截状态。)

【讨论】:

哇。你玩得很开心!太奇妙了!可能值得一篇博文! 也许您可以使用github.com/JustLikeIcarus/aspnetwebstack/blob/master/src/… 中的方法 - 检索 ExceptionDispatchInfo 和 .Throw(),保留完整的调用堆栈。 注意await Next?.Invoke(context)会在Next为null时抛出异常。 @Caleb9:可能,但我一直无法创建发生这种情况的管道。即使只有app.Use&lt;ExceptionHandlerMiddleware&gt;(); 作为整个注册,Next != null 当我第一次请求时到达那里。 @TomasLycken 确实这是一个极端情况,但理论上有人可以在注册中间件后调用 app.Use(null)。但是查看AppBuilder.cs 在这种情况下它会抛出 ArgumentNullException,所以没问题:)。尽管如此,ReSharper 仍在抱怨它。否则很好的解决方案,我已经在我正在处理的代码中使用它。谢谢。【参考方案2】:

不确定这是否适合您,但我有类似的要求,即使未找到错误,也将所有错误作为 JSON 发送回。我创建了一个基本控制器并覆盖了 ExecuteAsync,允许我创建自己的响应。

public class ControllerBase : ApiController

    protected string ClassName = "ControllerBase::";

    public override System.Threading.Tasks.Task<HttpResponseMessage> ExecuteAsync(System.Web.Http.Controllers.HttpControllerContext controllerContext, System.Threading.CancellationToken cancellationToken)
    
        try
        
            System.Threading.Tasks.Task<HttpResponseMessage> TaskList = base.ExecuteAsync(controllerContext, cancellationToken);

            if (TaskList.Exception != null && TaskList.Exception.GetBaseException() != null)
            
                JSONErrorResponse AsyncError = new JSONErrorResponse();
                AsyncError.ExceptionMessage = TaskList.Exception.GetBaseException().Message;
                AsyncError.ErrorMessage = string.Format("Unknown error 0 ExecuteAsync 1", ClassName ,controllerContext.Request.RequestUri.AbsolutePath);
                AsyncError.HttpErrorCode = HttpStatusCode.BadRequest;

                HttpResponseMessage ErrorResponse = controllerContext.Request.CreateResponse(AsyncError.HttpErrorCode, AsyncError);

                return System.Threading.Tasks.Task.Run<HttpResponseMessage>(() => ErrorResponse);
            
            return TaskList;
        
        catch (Exception Error)
        
            JSONErrorResponse BadParameters = new JSONErrorResponse();
            BadParameters.ExceptionMessage = Error.Message;
            BadParameters.ErrorMessage = string.Format("Method [0], or URL [1] not found, verify your request", controllerContext.Request.Method.Method, controllerContext.Request.RequestUri.AbsolutePath);
            BadParameters.HttpErrorCode = HttpStatusCode.NotFound;
            HttpResponseMessage ErrorResponse = controllerContext.Request.CreateResponse(BadParameters.HttpErrorCode, BadParameters);

            return System.Threading.Tasks.Task.Run<HttpResponseMessage>(() => ErrorResponse);
        
    


public class JSONErrorResponse

    //Possible message from exception
    public string ExceptionMessage  get; set; 
    //Possible custom error message
    public string ErrorMessage  get; set; 
    //Http error code
    public HttpStatusCode HttpErrorCode  get; set; 

【讨论】:

这仍然不会让我捕捉到控制器被实例化之前发生的错误,甚至是在控制器动作的实际执行之外发生的错误。它也是可选的,因为我必须确保所有控制器实际上都继承了这个基类;忘记一次,错误处理就在窗外了...... 您是否尝试过实现 Global.asax.cs Application_Error 调用?我知道但没有使用它,所以我不知道是否一切都通过了那里。看看这个application error page 我们根本没有使用它;我们使用 OWIN 管道托管它(Global.asax.cs 在这种情况下不相关)。【参考方案3】:

您也可以尝试创建自己的控制器激活器,拥有自定义异常处理程序并尝试使用 ExceptionFilterAttribute。

    创建你的控制器激活器

    public class ExceptionHandlingControllerActivator : IHttpControllerActivator
    
        private readonly IHttpControllerActivator _concreteActivator;
    
        public ExceptionHandlingControllerActivator(IHttpControllerActivator concreteActivator)
        
            _concreteActivator = concreteActivator;
        
    
        public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
        
            try
            
                return _concreteActivator.Create(request, controllerDescriptor, controllerType);
            
            catch (Exception ex)
            
                // do stuff with the exception
                throw new HttpResponseException(request.CreateResponse(HttpStatusCode.InternalServerError, new ResponseModel(ex)));
            
        
    
    

    创建异常过滤属性

    public class ExceptionHandlingFilter : ExceptionFilterAttribute
    
        public override void OnException(HttpActionExecutedContext context)
        
            // do stuff with the exception
    
            var request = context.Request;
            ResponseModel respMod = null;
    
            // Example: if debug constant is not defined, mask exception, otherwise create normal object with message, inner exception and stacktrace
    
            #if !DEBUG
            respMod = new ResponseModel(context.Exception, context.Exception.Message, true);
            #else
            respMod = new ResponseModel(context.Exception);
            #endif
    
            context.Response = request.CreateResponse(HttpStatusCode.InternalServerError, respMod);
        
    
    

    ResponseModel 是我使用格式化程序序列化为 JSON 并由所有控制器响应返回的类,因此客户端能够识别错误数据以及除了 HTTP 状态代码之外的成功响应。

    config.Formatters.Clear(); // do not need any other
    config.Formatters.Add(new JsonMediaTypeFormatter());
    

    连线

    // ... [cut] ...            
    config.Filters.Add(new ExceptionHandlingFilter());
    // ... [cut] ...
    config.Services.Replace(typeof(IHttpControllerActivator),
        new ExceptionHandlingControllerActivator(config.Services.GetHttpControllerActivator())
    );
    // ... [cut] ...
    app.UseWebApi(config);
    

【讨论】:

【参考方案4】:

OWIN 不应该处理这样的异常,因为 web api 内置了自己的错误处理。OWIN 被设计为与应用程序分开。如果您在异常处理程序的 HandleAsync 方法上设置断点,您应该能够检查上下文变量并查看异常的详细信息。

如果您只是出于调试目的而尝试这样做,则在此处设置断点应该可以让您看到异常。如果您需要记录异常,我认为异常处理程序是执行此操作的最佳位置。

希望对您有所帮助。

【讨论】:

但是 Web API 中的异常处理机制并不能捕获所有类型的错误——例如,如果在构造控制器期间发生错误,Web API 错误处理程序将不会处理该错误,并且我将从框架中得到不同的错误行为,具体取决于堆栈中出现问题的位置。我想要一个解决方案,它可以让我捕获所有错误并以一致的方式处理它们。 其实我之前的评论有点不正确:Web API 错误处理程序捕获控制器构造函数抛出的异常。但是,它不会在管道早期处理中间件中的异常,因此我仍然需要在管道底部使用特定的异常处理机制。由于 Web API 将处理异常并重写响应,从不让异常传播到我的中间件,我仍然需要重复的逻辑。【参考方案5】:

这可能会有所帮助:

https://***.com/a/21382651/1419853

http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-21#global-error

本质上,有一些内置支持来捕获、处理和更改错误。

看起来像这样:

public class ExceptionLogger : System.Web.Http.ExceptionHandling.ExceptionLogger

    Logger _logger;

    public ExceptionLogger(Logger logger)
    
        _logger = logger;
    

    public override void Log(ExceptionLoggerContext context)
    
        _logger.Error(context.ExceptionContext.Exception.ToString());
    

【讨论】:

你会从这个问题中注意到,我已经做的(并且不太高兴)非常相似;我注入了一个新的IExceptionHandler 而不是IExceptionLogger,但除此之外我使用的是完全相同的机制。它们不足以解决我的问题。 IExceptionLogger 有更多机会“看到”异常,但缺乏参与处理它们的设施。

以上是关于在 ASP.NET Web API 2 中禁用 *all* 异常处理(为我自己腾出空间)?的主要内容,如果未能解决你的问题,请参考以下文章

Asp.Net Core API 禁用启动完成消息

如何在 ASP.NET 中禁用 RSS

Asp.Net Web API 2第十一课——在Web API中使用Dependency Resolver

Asp.Net Web API 2第十课——使用OWIN自承载Web API

Web API系列教程2.1 — ASP.NET Web API中的路由机制

Asp.Net Web API 2第三课——.NET客户端调用Web API