Уловить все необработанные исключения в ASP.NET Web Api

Как я могу поймать все необработанные исключения, которые происходят в ASP.NET Web Api, чтобы я мог их зарегистрировать?

До сих пор я пробовал:

  • Создайте и зарегистрируйте ExceptionHandlingAttribute
  • Внедрить метод Application_Error в Global.asax.cs
  • Подпишитесь на AppDomain.CurrentDomain.UnhandledException
  • Подпишитесь на TaskScheduler.UnobservedTaskException

ExceptionHandlingAttribute успешно обрабатывает исключения, которые генерируются в методах действий контроллера и фильтрах действий, но другие исключения не обрабатываются, например:

  • Исключения, возникающие, когда IQueryable, возвращаемый методом действия, не выполняется
  • Исключения, отправленные обработчиком сообщений (т.е. HttpConfiguration.MessageHandlers)
  • Исключения, создаваемые при создании экземпляра контроллера

В принципе, если исключение приведет к тому, что 500 Internal Server Error будет возвращено клиенту, я хочу, чтобы он был зарегистрирован. Реализация Application_Error хорошо выполняла эту работу в Web Forms и MVC - что я могу использовать в Web Api?

Ответ 1

Теперь это возможно с помощью WebAPI 2.1 (см. Что нового):

Создайте одну или несколько реализаций IExceptionLogger. Например:

public class TraceExceptionLogger : ExceptionLogger
{
    public override void Log(ExceptionLoggerContext context)
    {
        Trace.TraceError(context.ExceptionContext.Exception.ToString());
    }
}

Затем зарегистрируйтесь в своем приложении HttpConfiguration, внутри обратного вызова конфигурации, например:

config.Services.Add(typeof(IExceptionLogger), new TraceExceptionLogger());

или напрямую:

GlobalConfiguration.Configuration.Services.Add(typeof(IExceptionLogger), new TraceExceptionLogger());

Ответ 2

Чтобы ответить на мой собственный вопрос, это невозможно!

Обработка всех исключений, которые вызывают внутренние ошибки сервера, похоже на базовую возможность, которую должен иметь веб-API, поэтому я ввел запрос с Microsoft для Глобального обработчика ошибок для веб-API:

https://aspnetwebstack.codeplex.com/workitem/1001

Если вы согласны, перейдите по этой ссылке и проголосуйте за нее!

Тем временем, отличная статья Обработка исключений веб-API ASP.NET показывает несколько разных способов поймать несколько различных категорий ошибок. Это сложнее, чем должно быть, и оно не улавливает все межсерверные ошибки, но это лучший подход, доступный сегодня.

Обновление: Глобальная обработка ошибок теперь реализована и доступна в ночных сборках! Он будет выпущен в ASP.NET MVC v5.1. Вот как это будет работать: https://aspnetwebstack.codeplex.com/wikipage?title=Global%20Error%20Handling

Ответ 3

Ответ Yuval предназначен для настройки ответов на необработанные исключения, обнаруженные Web API, а не для ведения журнала, как указано на связанной странице. Подробнее см. Раздел "Когда использовать" на странице. Регистратор всегда вызывается, но обработчик вызывается только тогда, когда может быть отправлен ответ. Короче говоря, использовать регистратор для входа и обработчик, чтобы настроить реакцию.

Кстати, я использую сборку v5.2.3, а класс ExceptionHandler не имеет метода HandleCore. Я думаю, эквивалент - это Handle. Однако просто подклассификация ExceptionHandler (как в ответе Yuval) не работает. В моем случае я должен реализовать IExceptionHandler следующим образом.

internal class OopsExceptionHandler : IExceptionHandler
{
    private readonly IExceptionHandler _innerHandler;

    public OopsExceptionHandler (IExceptionHandler innerHandler)
    {
        if (innerHandler == null)
            throw new ArgumentNullException(nameof(innerHandler));

        _innerHandler = innerHandler;
    }

    public IExceptionHandler InnerHandler
    {
        get { return _innerHandler; }
    }

    public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
    {
        Handle(context);

        return Task.FromResult<object>(null);
    }

    public void Handle(ExceptionHandlerContext context)
    {
        // Create your own custom result here...
        // In dev, you might want to null out the result
        // to display the YSOD.
        // context.Result = null;
        context.Result = new InternalServerErrorResult(context.Request);
    }
}

Обратите внимание, что, в отличие от регистратора, вы регистрируете обработчик, заменяя обработчик по умолчанию, а не добавляя.

config.Services.Replace(typeof(IExceptionHandler),
    new OopsExceptionHandler(config.Services.GetExceptionHandler()));

Ответ 4

Вы также можете создать глобальный обработчик исключений, реализуя интерфейс IExceptionHandler (или наследуя базовый класс ExceptionHandler). Он будет последним, который будет вызван в цепочке выполнения, после всего зарегистрированного IExceptionLogger:

IExceptionHandler обрабатывает все необработанные исключения из всех контроллеры. Это последнее в списке. Если возникает исключение, Сначала будет вызываться IExceptionLogger, затем контроллер ExceptionFilters и если все еще необработанный, IExceptionHandler реализация.

public class OopsExceptionHandler : ExceptionHandler
{
    public override void HandleCore(ExceptionHandlerContext context)
    {
        context.Result = new TextPlainErrorResult
        {
            Request = context.ExceptionContext.Request,
            Content = "Oops! Sorry! Something went wrong."        
        };
    }

    private class TextPlainErrorResult : IHttpActionResult
    {
        public HttpRequestMessage Request { get; set; }

        public string Content { get; set; }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            HttpResponseMessage response = 
                             new HttpResponseMessage(HttpStatusCode.InternalServerError);
            response.Content = new StringContent(Content);
            response.RequestMessage = Request;
            return Task.FromResult(response);
        }
    }
}

Подробнее об этом здесь.

Ответ 5

Я думал, что мой новый метод global.asax.Application_Error не был постоянно вызван для необработанных исключений в нашем устаревшем коде.

Затем я обнаружил несколько блоков try-catch в середине стека вызовов, которые вызвали Response.Write в тексте Exception. Вот и все. Сбросил текст на экране, а затем уничтожил камень для исключения.