Як я реєструю ВСІ винятки у глобальному масштабі для програми C # MVC4 WebAPI?


175

Фон

Я розробляю API-сервісний шар для клієнта, і мене просять вловлювати та фіксувати всі помилки в усьому світі.

Отже, хоча щось на зразок невідомої кінцевої точки (або дії) легко обробляється за допомогою ELMAH або додаючи щось подібне до Global.asax:

protected void Application_Error()
{
     Exception unhandledException = Server.GetLastError();
     //do more stuff
}

. . .неопрацьовані помилки, не пов'язані з маршрутизацією, не входять у систему. Наприклад:

public class ReportController : ApiController
{
    public int test()
    {
        var foo = Convert.ToInt32("a");//Will throw error but isn't logged!!
        return foo;
    }
}

Я також спробував встановити [HandleError]атрибут глобально, зареєструвавши цей фільтр:

filters.Add(new HandleErrorAttribute());

Але це також не реєструє всіх помилок.

Проблема / питання

Як я перехоплюю помилки, подібні до тієї, що генерується викликом /testвище, щоб я міг їх реєструвати? Здається, що ця відповідь повинна бути очевидною, але я перепробував усе, що можу придумати досі.

В ідеалі я хочу додати деякі речі до реєстрації помилок, такі як IP-адреса запитуючого користувача, дата, час тощо. Я також хочу мати можливість автоматично надіслати електронною поштою службі підтримки, коли виникає помилка. Все це я можу зробити, якщо тільки можу перехопити ці помилки, коли вони трапляються!

ВИРІШИЛИ!

Завдяки Дарину Димитров, відповідь якого я прийняв, я це зрозумів. WebAPI не обробляє помилки так само, як звичайний контролер MVC.

Ось що працювало:

1) Додайте спеціальний фільтр до простору імен:

public class ExceptionHandlingAttribute : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext context)
    {
        if (context.Exception is BusinessException)
        {
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
            {
                Content = new StringContent(context.Exception.Message),
                ReasonPhrase = "Exception"
            });

        }

        //Log Critical errors
        Debug.WriteLine(context.Exception);

        throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
        {
            Content = new StringContent("An error occurred, please try again or contact the administrator."),
            ReasonPhrase = "Critical Exception"
        });
    }
}

2) Тепер зареєструйте фільтр глобально у класі WebApiConfig :

public static class WebApiConfig
{
     public static void Register(HttpConfiguration config)
     {
         config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{action}/{id}", new { id = RouteParameter.Optional });
         config.Filters.Add(new ExceptionHandlingAttribute());
     }
}

АБО ви можете пропустити реєстрацію і просто прикрасити один контролер [ExceptionHandling]атрибутом.


У мене така ж проблема. Неопрацьовані винятки потрапляють в атрибут фільтра винятку штрафу, але коли я викидаю новий виняток, він не потрапляє в атрибут фільтра виключень, будь-яка ідея щодо цього?
daveBM

1
Невідомі виклики контролера api, такі як помилки myhost / api / undefinedapicontroller, як і раніше, не вловлюються. Код фільтра Application_error та Exception не виконується. Як їх також зловити?
Андрус

1
До WebAPI v2.1 додано глобальну обробку помилок. Дивіться моя відповідь тут: stackoverflow.com/questions/17449400 / ...
DarrellNorton

1
Це не призведе до помилок в деяких обставинах, наприклад, "ресурс не знайдено" або помилок у конструкторі контролера. Зверніться сюди: aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/Elmah/…
Джордан Морріс

Привіт, @Matt. Відповідь ви написали як частину запитання, але це не найкраща практика в питаннях. Тут відповіді повинні бути окремими від питання. Чи можете ви написати, як окрему відповідь (ви можете використовувати синю кнопку "Відповісти на власне запитання" внизу).
sashoalm

Відповіді:


56

Якщо ваш веб-API розміщено в додатку ASP.NET, Application_Errorподія буде викликана за всіма необробленими винятками у вашому коді, включаючи те, що було показано в тестовій дії, яку ви показали. Тому все, що вам потрібно зробити, це обробити цей виняток всередині події Application_Error. У наведеному зразковому коді ви обробляєте лише виняток типу, HttpExceptionякий, очевидно, не стосується Convert.ToInt32("a")коду. Тому переконайтеся, що ви там входите та обробляєте всі винятки:

protected void Application_Error()
{
    Exception unhandledException = Server.GetLastError();
    HttpException httpException = unhandledException as HttpException;
    if (httpException == null)
    {
        Exception innerException = unhandledException.InnerException;
        httpException = innerException as HttpException;
    }

    if (httpException != null)
    {
        int httpCode = httpException.GetHttpCode();
        switch (httpCode)
        {
            case (int)HttpStatusCode.Unauthorized:
                Response.Redirect("/Http/Error401");
                break;

            // TODO: don't forget that here you have many other status codes to test 
            // and handle in addition to 401.
        }
        else
        {
            // It was not an HttpException. This will be executed for your test action.
            // Here you should log and handle this case. Use the unhandledException instance here
        }
    }
}

Обробка винятків у веб-API може здійснюватися на різних рівнях. Ось detailed articleпояснення різних можливостей:

  • спеціальний атрибут фільтра виключень, який можна зареєструвати як глобальний фільтр виключень

    [AttributeUsage(AttributeTargets.All)]
    public class ExceptionHandlingAttribute : ExceptionFilterAttribute
    {
        public override void OnException(HttpActionExecutedContext context)
        {
            if (context.Exception is BusinessException)
            {
                throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
                {
                    Content = new StringContent(context.Exception.Message),
                    ReasonPhrase = "Exception"
                });
            }
    
            //Log Critical errors
            Debug.WriteLine(context.Exception);
    
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
            {
                Content = new StringContent("An error occurred, please try again or contact the administrator."),
                ReasonPhrase = "Critical Exception"
            });
        }
    }
  • користувальницький виклик дій

    public class MyApiControllerActionInvoker : ApiControllerActionInvoker
    {
        public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
        {
            var result = base.InvokeActionAsync(actionContext, cancellationToken);
    
            if (result.Exception != null && result.Exception.GetBaseException() != null)
            {
                var baseException = result.Exception.GetBaseException();
    
                if (baseException is BusinessException)
                {
                    return Task.Run<HttpResponseMessage>(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)
                    {
                        Content = new StringContent(baseException.Message),
                        ReasonPhrase = "Error"
    
                    });
                }
                else
                {
                    //Log critical error
                    Debug.WriteLine(baseException);
    
                    return Task.Run<HttpResponseMessage>(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)
                    {
                        Content = new StringContent(baseException.Message),
                        ReasonPhrase = "Critical Error"
                    });
                }
            }
    
            return result;
        }
    }

Я хотів би, щоб це було так просто, але помилка все ще не потрапляє. Я оновив питання, щоб уникнути плутанини. Дякую.
Метт Кашатт

@MatthewPatrickCashatt, якщо цей виняток не потрапляє у Application_Errorвипадку, це означає, що якийсь інший код споживає його раніше. Наприклад, у вас можуть бути деякі користувальницькі HandleErrorAttributes, спеціальні модулі, ... Є газильйони інших місць, де винятки можуть бути схоплені та оброблені. Але найкращим місцем для цього є подія Application_Error, тому що саме там закінчуються всі необроблені винятки.
Дарин Димитров

Ще раз дякую, але незалежно від того, /testприклад не потрапляє. Я поставив точку розриву на першому рядку ( Exception unhandledException = . . .), але не можу потрапити на цю точку розриву у /testсценарії. Якщо я поміщую фіктивну URL-адресу, то точка перелому потрапляє.
Метт Кашатт

1
@MatthewPatrickCashatt, ти абсолютно правий. Application_ErrorПодія не є правильним місцем для обробки винятків для Web API , оскільки він не буде спрацьовувати у всіх випадках. Я знайшов дуже детальну статтю, яка пояснює різні можливості для досягнення цього: weblogs.asp.net/fredriknormen/archive/2012/06/11/…
Димитров

1
@Darin Dimitrov Невідомі виклики контролера api, як-от помилки myhost / api / undefinedapi , досі не вловлюються. Код фільтра Application_error та Exception не виконується. Як їх також зловити?
Андрус

79

Як додаток до попередніх відповідей.

Вчора ASP.NET Web API 2.1 був офіційно випущений .
Це пропонує ще одну можливість поводитися з винятками в усьому світі.
Деталі наведені у зразку .

Якщо коротко, ви додаєте глобальні реєстратори винятків та / або глобальний обробник винятків (лише один).
Ви додаєте їх до конфігурації:

public static void Register(HttpConfiguration config)
{
  config.MapHttpAttributeRoutes();

  // There can be multiple exception loggers.
  // (By default, no exception loggers are registered.)
  config.Services.Add(typeof(IExceptionLogger), new ElmahExceptionLogger());

  // There must be exactly one exception handler.
  // (There is a default one that may be replaced.)
  config.Services.Replace(typeof(IExceptionHandler), new GenericTextExceptionHandler());
}

І їх реалізація:

public class ElmahExceptionLogger : ExceptionLogger
{
  public override void Log(ExceptionLoggerContext context)
  {
    ...
  }
}

public class GenericTextExceptionHandler : ExceptionHandler
{
  public override void Handle(ExceptionHandlerContext context)
  {
    context.Result = new InternalServerErrorTextPlainResult(
      "An unhandled exception occurred; check the log for more information.",
      Encoding.UTF8,
      context.Request);
  }
}

2
Це спрацювало чудово. Я реєструюсь і обробляю одночасно (тому що я отримую logID і передаю його назад, щоб користувач міг додавати коментарі), тому я встановлюю Результат для нового ResponseMessageResult. Це клопоче мене вже деякий час, дякую.
Бретт

8

Чому переробляти тощо? Це працює, і воно призведе до статусу повернення послуги 500 тощо

public class LogExceptionFilter : ExceptionFilterAttribute
{
    private static readonly ILog log = LogManager.GetLogger(typeof (LogExceptionFilter));

    public override void OnException(HttpActionExecutedContext actionExecutedContext)
    {
        log.Error("Unhandeled Exception", actionExecutedContext.Exception);
        base.OnException(actionExecutedContext);
    }
}

2

Ви думали про те, щоб зробити щось на кшталт фільтра дій з помилками ручки, як

[HandleError]
public class BaseController : Controller {...}

ви також можете створити власну версію, за [HandleError]допомогою якої ви можете записувати інформацію про помилки та всі інші деталі для входу


Дякую, але у мене це вже встановлено глобально. Це задає те саме питання, що і вище, не всі помилки реєструються.
Метт Кашатт

1

Загорніть всю справу в спробу / ловити і запишіть не оброблений виняток, а потім передайте її далі. Якщо тільки немає кращого вбудованого способу це зробити.

Ось довідка Вилучення всіх (оброблених чи необроблених) Винятків

(редагувати: о API)


На всякий випадок, йому також доведеться повторно скинути виняток.
DigCamara

@DigCamara Вибачте, це я мав на увазі, передаючи це далі. кинути; повинні впоратися з цим. Спочатку я сказав "вирішити, чи потрібно виходити чи перезавантажувати", потім зрозумів, що сказав, що це API. У такому випадку краще дозволити додатку вирішувати, що він хоче зробити, передаючи його далі.
Тим

1
Це погана відповідь, оскільки це призведе до навантаження дублюючого коду в кожній дії.
Янскій
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.