Подтвердить что ты не робот

Исключения в обработчике особых исключений ASP.NET Web API никогда не достигают верхнего уровня, когда включен CORS

Я создал специальный обработчик исключений для веб-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;
    }
}

Я регистрирую его в своем приложении Global.asax Application_Start:

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

Все работало нормально, независимо от того, где я выбрал исключения - методы внутреннего контроллера или мои пользовательские атрибуты выше методов контроллера, и неважно, вызываю ли я его из запросов AJAX или непосредственно из браузера.

Но однажды мне понадобилась поддержка CORS для моих запросов AJAX. Я включил CORS во всем мире, как описано в статье Включение запросов перекрестного происхождения в веб-API ASP.NET

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

Сначала все казалось ОК, CORS работал, как ожидалось, сервер ответил на запрос OPTIONS.

Но проблемы начались, когда я хотел проверить свою аутентификацию. Внезапно исключение было проглочено, и я получил пустой ответ JSON {} вместо моего настраиваемого исключения JSON, которое я создаю в методе Handle() моего MyGlobalExceptionHandler.

Во время отладки я с удивлением обнаружил, что теперь для запросов AJAX метод ShouldHandle() вызывается только с IsTopLevel = false, поэтому исключение никогда не пузырится и никогда не достигает моего метода Handle(). Как только я отключу CORS, все работает отлично (за исключением междоменных запросов, конечно).

Почему IsTopLevel никогда не будет правдой, если я включу CORS? Как это исправить?

Еще один побочный эффект заключается в следующем. Если CORS отключен, то, если я исключу исключение из метода Handle(), он достигнет обработчика Application_Error в Global.asax. Но если я включу CORS и исключаю исключения из методов моего обработчика, эти исключения никогда не достигают Application_Error.

ОБНОВЛЕНО БОЛЬШЕ ДЕТАЛЕЙ:

Кажется, я нашел точно, когда это происходит.

Если я выбрал исключение в методе контроллера, когда CORS включен, то CORS вообще не ударит и не отправит заголовок Access-Control-Allow-Origin. Когда браузер не получает заголовок, он немедленно нарушает запрос, и это нарушение, похоже, влияет и на обработчик исключений - он никогда не достигает метода ShouldHandle() с IsTopLevel = true. Chrome и Mozilla действуют так же, даже когда я запускаю запрос AJAX из локального html файла в мой websiet на локальном хосте IIS Express.

Но IE 11 отличается от IE 11. Когда я открываю html файл там, он сначала запрашивает у меня разрешения на включение сценариев. После того, как я согласен, IE 11 игнорирует тот факт, что нет заголовков CORS, и он не прерывает запрос, поэтому мой обработчик исключений получает IsTopLevel = true и способен возвращать настраиваемый ответ об ошибке.

Я предполагаю, что это должно быть исправлено в ядре веб-API - даже если я делаю исключения, CORS все равно сможет пинать и отправлять свои заголовки, поэтому браузер принимает ответ. Я создал минимальное тестовое приложение, и я отправлю его команде ASP.NET на CodePlex. Ссылка на тестовый проект. (файл zip проекта будет отмечен, просто нажмите "Загрузить" и проигнорируйте все остальные файлы в этой папке)

4b9b3361

Ответ 1

Я нашел источник путаницы.

Похоже, 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 и return true всегда, потому что CatchBlock.IsTopLevel может никогда не иметь значения true.

Я попробовал оба apporaches, и они, похоже, работают нормально.