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

时间:2015-12-10 12:08:26

标签: c# asp.net asp.net-web-api owin

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

public override async Task Invoke(IOwinContext context)
{
    try
    {
        await Next.Invoke(context);
    }
    catch (Exception ex)
    {
        // Log error and return 500 response
    }
}

但是,在我可以访问之前,我想要捕获的一些异常被Web API管道捕获并转换为HttpErrorResponse。在这个过程中,我丢失了很多关于错误的细节,所以在调试时我无法获得有用的堆栈跟踪(调试器在抛出异常时甚至不会停止 - 我必须手动单步执行代码,看看它失败的地方......)。

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

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);

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

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

澄清,因为很多人似乎误解了我之后的事情:

  • Web API中的内置错误处理捕获一些(但不是全部)异常并将其重写为500个响应。
  • 我想抓住所有例外情况,做一些日志记录,然后使用我选择的信息发出500条回复(对于大多数情况而言) ,见下一个子弹)。
  • 还有一些异常表示业务逻辑错误,我想为此返回40x错误。
  • 我希望它位于(app)管道的顶部,即在请求生命周期中包装所有其他
  • 我希望使用OWIN来处理这个问题,以便将其移植到未来可能的自托管方案中(也就是说,这个应用程序将永远托管在IIS上,并不是一成不变的 - HTTP模块,Global.asax .cs等在这里不相关)。

5 个答案:

答案 0 :(得分:18)

更新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中断,但原始调用堆栈丢失;我必须在错误行处设置断点并再次调试,以便在抛出异常时拦截状态。)

答案 1 :(得分:1)

不确定这是否适合您,但我有类似的要求,即使未找到错误,也要将所有错误发送回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; }
}

答案 2 :(得分:1)

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

  1. 创建控制器激活器

    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)));
            }
        }
    }
    
  2. 创建ExceptionFilterAttribute

    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);
        }
    }
    
  3. ResponseModel是一个使用Formatters序列化为JSON并由所有控制器响应返回的类,因此客户端能够识别错误数据以及除HTTP状态代码之外的成功响应。

    config.Formatters.Clear(); // do not need any other
    config.Formatters.Add(new JsonMediaTypeFormatter());
    
  4. 接通电话

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

答案 3 :(得分:0)

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

如果您只是出于调试目的而尝试这样做,那么在那里设置断点应该允许您查看异常。如果你需要记录异常,在我看来,异常处理程序是最好的做法。

希望有所帮助。

答案 4 :(得分:0)

这可能会有所帮助:

https://stackoverflow.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());
    }
}