启用CORS时,ASP.NET Web API自定义异常处理程序中的异常永远不会达到顶级

Jus*_*tin 35 exception-handling cors asp.net-web-api

我创建了一个自定义Web API全局异常处理程序,如下所示:

public class MyGlobalExceptionHandler : ExceptionHandler
{
    public override void Handle(ExceptionHandlerContext context)
    {
        // here I handle them all, no matter sync or not
    }

    public override Task HandleAsync(ExceptionHandlerContext context, 
        CancellationToken cancellationToken)
    {
        // not needed, but I left it to debug and find out why it never reaches Handle() method
        return base.HandleAsync(context, cancellationToken);
    }

    public override bool ShouldHandle(ExceptionHandlerContext context)
    {
        // not needed, but I left it to debug and find out why it never reaches Handle() method
        return context.CatchBlock.IsTopLevel;
    }
}
Run Code Online (Sandbox Code Playgroud)

我在我的Global.asax Application_Start中注册它:

GlobalConfiguration.Configuration.Services.Replace(typeof(IExceptionHandler),
    new MyGlobalExceptionHandler());
Run Code Online (Sandbox Code Playgroud)

一切都运行正常,无论我在哪里抛出异常 - 在控制器方法内部或在控制器方法之上的自定义属性中,无论我是从AJAX请求还是直接从浏览器调用它.

但是有一天我需要CORS支持我的AJAX请求.我按照ASP.NET Web API中的启用跨源请求一文中的说明全局启用了CORS

var cors = new EnableCorsAttribute("*", "*", "*"); // * is just for debugging purpose
config.EnableCors(cors);
Run Code Online (Sandbox Code Playgroud)

起初一切似乎都没问题,CORS按预期工作,服务器响应OPTIONS请求.

但是当我想检查我的身份验证时问题就出现了.突然异常被吞下,我得到了一个空的JSON {}响应,而不是我在MyGlobalExceptionHandler的Handle()方法中创建的自定义JSON格式异常.

在调试时,我很惊讶地发现现在对于AJAX请求,只有IsTopLevel = false才会调用ShouldHandle()方法,因此异常永远不会冒泡并且永远不会到达我的Handle()方法.一旦我禁用CORS,一切都可以正常工作(当然,除了跨域请求).

为什么启用CORS时IsTopLevel永远不会成立?我该怎么解决这个问题?

另一个副作用如下.如果禁用CORS,那么如果我在Handle()方法中抛出任何异常,它将到达Global.asax中的Application_Error处理程序.但是如果我在我的处理程序方法中启用CORS并抛出异常,则这些异常永远不会达到Application_Error.

更新详情:

看来,我确实发现了这种情况.

如果在启用CORS时在控制器方法中抛出异常,则CORS根本不会启动,也不会发送Access-Control-Allow-Origin标头.当浏览器没有收到标题时,它会立即中断请求,这种中断似乎也会影响异常处理程序 - 它永远不会到达IsTopLevel = true的ShouldHandle()方法.即使我在本地html文件中运行AJAX请求到IIS Express localhost上的我的websiet,Chrome和Mozilla就是这样的.

但IE 11的情况有所不同.当我在那里打开html文件时,它首先会要求我启用脚本的权限.在我同意之后,IE 11忽略了这样一个事实,即没有CORS头存在并且它不会中断请求,因此我的异常处理程序接收IsTopLevel = true并且能够返回自定义的错误响应.

我想,这应该在Web API核心中修复 - 即使我抛出异常,CORS应该仍然可以启动并发送其标头,因此浏览器接受响应.我创建了一个最小的测试用例应用程序,我将它发送到CodePlex上的ASP.NET团队. 链接到测试项目.(项目zip文件将被标记,只需单击"下载"并忽略该文件夹中的所有其他文件)

Jus*_*tin 64

我发现了混乱的根源.

看来,WebAPI默认使用此异常处理程序:

https://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Http/ExceptionHandling/DefaultExceptionHandler.cs

它与本文中建议的异常处理有很大的不同:

http://www.asp.net/web-api/overview/web-api-routing-and-actions/web-api-global-error-handling

请参阅"附录:基类详细信息"一章,其中给出了默认异常基类的代码.

在示例中,它检查IsOutermostCatchBlock(现在似乎移动到exceptionContext.CatchBlock.IsTopLevel)以查看是否有时间处理异常.启用CORS时,由于上述原因,此方法不起作用.ASP.NET团队表示,这种行为是设计的,他们不会改变任何东西.

我希望有经验的人会写一篇最新文章,其中包含使用和不使用CORS以正确方式进行异常处理的说明.

目前我在我的代码中看到了两种解决方法:

  • 不要继承我的自定义异常处理程序,System.Web.Http.ExceptionHandling.ExceptionHandler而是IExceptionHandler直接实现

  • 如果继承System.Web.Http.ExceptionHandling.ExceptionHandler,则覆盖ShouldHandle方法并true始终返回因为CatchBlock.IsTopLevel可能永远不会有true值.

我尝试了两个apporaches,他们似乎工作正常.

  • @JustAMartin我想提出第三个选择.在我的情况下,我在多个项目上使用异常处理程序,其中一个项目没有启用CORS.`public override bool ShouldHandle(ExceptionHandlerContext context){if(context.RequestContext.Configuration.Properties.ContainsKey("MS_CorsEnabledKey")){return(bool)context.RequestContext.Configuration.Properties ["MS_CorsEnabledKey"]; } return base.ShouldHandle(context); }` (2认同)