Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
217 views
in Technique[技术] by (71.8m points)

Exceptions in ASP.NET Web API custom exception handler never reach top level when CORS is enabled

I have created a custom Web API global exception handler like this:

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

I'm registering it in my Global.asax Application_Start:

GlobalConfiguration.Configuration.Services.Replace(typeof(IExceptionHandler),
    new MyGlobalExceptionHandler());

Everything worked fine, no matter where I threw exceptions - inside controller methods or in my custom attributes above controller methods, and no matter if I call it from AJAX requests or directly from browser.

But then one day I needed CORS support for my AJAX requests. I enabled CORS globally as described in the article Enabling Cross-Origin Requests in ASP.NET Web API

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

At first everything seemed OK, CORS worked as expected, the server responded to OPTIONS request.

But the problems started when I wanted to check my authentication. Suddenly the exception was swallowed and I got an empty JSON {} response instead of my custom JSON formatted exception which I create in the Handle() method of my MyGlobalExceptionHandler.

While debugging, I was surprised to find that now for AJAX requests ShouldHandle() method is called only with IsTopLevel = false, thus the exception never bubbles up and never reaches my Handle() method. As soon as I disable CORS, everything works fine again (except cross-domain requests, of course).

Why IsTopLevel is never true when I enable CORS? How should I fix this?

One more side effect is as follows. If CORS is disabled, then if I throw any exception inside Handle() method, it reaches the Application_Error handler in Global.asax. But if I enable CORS and throw exceptions in my handler methods, these exceptions never reach Application_Error.

UPDATED WITH MORE DETAILS:

It seems, I found exactly when this is happening.

If I throw an exception in a controller method when CORS is enabled, then CORS doesn't kick in at all and does not send the Access-Control-Allow-Origin header. When the browser doesn't receive the header, it immediately disrupts the request and this disruption seems to affect also the exception handler - it never reaches ShouldHandle() method with IsTopLevel = true. Chrome and Mozilla act like this even when I run AJAX request from a local html file to my websiet on IIS Express localhost.

But the situation is different on IE 11. When I open the html file there, it first asks me permissions to enable scripts. After I agree, then IE 11 ignores the fact that there are no CORS headers present and it doesn't disrupt the request, thus my exception handler receives IsTopLevel = true and is able to return a customised error response.

I guess, this should be fixed in the Web API core - even if I throw exceptions, CORS should still be able to kick in and send its headers, so the browser accepts the response. I have created a minimal test case application and I'll send it to the ASP.NET Team on CodePlex. Link to the test project. (the project zip file will be marked, just click Download and ignore all the other files in that folder)

question from:https://stackoverflow.com/questions/24189315/exceptions-in-asp-net-web-api-custom-exception-handler-never-reach-top-level-whe

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I have found the source of confusion.

It seems, WebAPI by default is using this exception handler:

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

and it has major differences from the suggested exception handling in this article:

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

see chapter "Appendix: Base Class Details", where the code of default exception base class is given.

In the example it checks for IsOutermostCatchBlock (which now seems to be moved to exceptionContext.CatchBlock.IsTopLevel) to see if it's time to handle the exception. When CORS is enabled, such approach won't work for the reasons I described above. ASP.NET team said, this behavior is by design and they won't change anything.

I hope someone experienced will write an up-to-date article with instructions for doing exception handling the right way with and without CORS.

Currently I see two ways to work around this in my code:

  • don't inherit my custom exception handler from System.Web.Http.ExceptionHandling.ExceptionHandler but implement IExceptionHandler directly

  • if inheriting from System.Web.Http.ExceptionHandling.ExceptionHandler, override ShouldHandle method and return true always because CatchBlock.IsTopLevel might never have true value.

I tried both apporaches and they seem to work fine.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...