Меню

Web api обработка ошибок

В преддверии старта курса «C# ASP.NET Core разработчик» подготовили традиционный перевод полезного материала.

Также рекомендуем посмотреть вебинар на тему

«Отличия структурных шаблонов проектирования на примерах». На этом открытом уроке участники вместе с преподавателем-экспертом познакомятся с тремя структурными шаблонами проектирования: Заместитель, Адаптер и Декоратор.


Введение 

Сегодня в этой статье мы обсудим концепцию обработки исключений в приложениях ASP.NET Core. Обработка исключений (exception handling) — одна из наиболее важных импортируемых функций или частей любого типа приложений, которой всегда следует уделять внимание и правильно реализовывать. Исключения — это в основном средства ориентированные на обработку рантайм ошибок, которые возникают во время выполнения приложения. Если этот тип ошибок не обрабатывать должным образом, то приложение будет остановлено в результате их появления.

В ASP.NET Core концепция обработки исключений подверглась некоторым изменениям, и теперь она, если можно так сказать, находится в гораздо лучшей форме для внедрения обработки исключений. Для любых API-проектов реализация обработки исключений для каждого действия будет отнимать довольно много времени и дополнительных усилий. Но мы можем реализовать глобальный обработчик исключений (Global Exception handler), который будет перехватывать все типы необработанных исключений. Преимущество реализации глобального обработчика исключений состоит в том, что нам нужно определить его всего лишь в одном месте. Через этот обработчик будет обрабатываться любое исключение, возникающее в нашем приложении, даже если мы объявляем новые методы или контроллеры. Итак, в этой статье мы обсудим, как реализовать глобальную обработку исключений в ASP.NET Core Web API.

Создание проекта ASP.NET Core Web API в Visual Studio 2019

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

  • Откройте Microsoft Visual Studio и нажмите «Create a New Project» (Создать новый проект).

  • В диалоговом окне «Create New Project» выберите «ASP.NET Core Web Application for C#» (Веб-приложение ASP.NET Core на C#) и нажмите кнопку «Next» (Далее).

  • В окне «Configure your new project» (Настроить новый проект) укажите имя проекта и нажмите кнопку «Create» (Создать).

  • В диалоговом окне «Create a New ASP.NET Core Web Application» (Создание нового веб-приложения ASP.NET Core) выберите «API» и нажмите кнопку «Create».

  • Убедитесь, что флажки «Enable Docker Support» (Включить поддержку Docker) и «Configure for HTTPS» (Настроить под HTTPS) сняты. Мы не будем использовать эти функции.

  • Убедитесь, что выбрано «No Authentication» (Без аутентификации), поскольку мы также не будем использовать аутентификацию.

  • Нажмите ОК.

Используем UseExceptionHandler middleware в ASP.NET Core.

Чтобы реализовать глобальный обработчик исключений, мы можем воспользоваться преимуществами встроенного Middleware ASP.NET Core. Middleware представляет из себя программный компонент, внедренный в конвейер обработки запросов, который каким-либо образом обрабатывает запросы и ответы. Мы можем использовать встроенное middleware ASP.NET Core UseExceptionHandler в качестве глобального обработчика исключений. Конвейер обработки запросов ASP.NET Core включает в себя цепочку middleware-компонентов. Эти компоненты, в свою очередь, содержат серию делегатов запросов, которые вызываются один за другим. В то время как входящие запросы проходят через каждый из middleware-компонентов в конвейере, каждый из этих компонентов может либо обработать запрос, либо передать запрос следующему компоненту в конвейере.

С помощью этого middleware мы можем получить всю детализированную информацию об объекте исключения, такую ​​как стектрейс, вложенное исключение, сообщение и т. д., а также вернуть эту информацию через API в качестве вывода. Нам нужно поместить middleware обработки исключений в configure() файла startup.cs. Если мы используем какое-либо приложение на основе MVC, мы можем использовать middleware обработки исключений, как это показано ниже. Этот фрагмент кода демонстрирует, как мы можем настроить middleware UseExceptionHandler для перенаправления пользователя на страницу с ошибкой при возникновении любого типа исключения.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  
{  
    app.UseExceptionHandler("/Home/Error");  
    app.UseMvc();  
} 

Теперь нам нужно проверить сообщение об исключении. Для этого откройте файл WeatherForecastController.cs и добавьте следующий экшн-метод, чтобы пробросить исключение:

[Route("GetExceptionInfo")]  
[HttpGet]  
public IEnumerable<string> GetExceptionInfo()  
{  
     string[] arrRetValues = null;  
     if (arrRetValues.Length > 0)  
     { }  
     return arrRetValues;  
} 

Если мы хотим получить подробную информацию об объектах исключения, например, стектрейс, сообщение и т. д., мы можем использовать приведенный ниже код в качестве middleware исключения —

app.UseExceptionHandler(  
                options =>  
                {  
                    options.Run(  
                        async context =>  
                        {  
                            context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;  
                            context.Response.ContentType = "text/html";  
                            var exceptionObject = context.Features.Get<IExceptionHandlerFeature>();  
                            if (null != exceptionObject)  
                            {  
                                var errorMessage = $"<b>Exception Error: {exceptionObject.Error.Message} </b> {exceptionObject.Error.StackTrace}";  
                                await context.Response.WriteAsync(errorMessage).ConfigureAwait(false);  
                            }  
                        });  
                }  
            );  

Для проверки вывода просто запустите эндпоинт API в любом браузере:

Определение пользовательского Middleware для обработки исключений в API ASP.NET Core

Кроме того, мы можем написать собственное middleware для обработки любых типов исключений. В этом разделе мы продемонстрируем, как создать типичный пользовательский класс middleware. Пользовательское middleware также обеспечивает гораздо большую гибкость для обработки исключений. Мы можем добавить стекатрейс, имя типа исключения, код ошибки или что-нибудь еще, что мы захотим включить как часть сообщения об ошибке. В приведенном ниже фрагменте кода показан типичный пользовательский класс middleware:

using Microsoft.AspNetCore.Http;    
using Newtonsoft.Json;    
using System;    
using System.Collections.Generic;    
using System.Linq;    
using System.Net;    
using System.Threading.Tasks;    
    
namespace API.DemoSample.Exceptions    
{    
    public class ExceptionHandlerMiddleware    
    {    
        private readonly RequestDelegate _next;    
    
        public ExceptionHandlerMiddleware(RequestDelegate next)    
        {    
            _next = next;    
        }    
    
        public async Task Invoke(HttpContext context)    
        {    
            try    
            {    
                await _next.Invoke(context);    
            }    
            catch (Exception ex)    
            {    
                    
            }    
        }    
    }    
} 

В приведенном выше классе делегат запроса передается любому middleware. Middleware либо обрабатывает его, либо передает его следующему middleware в цепочке. Если запрос не успешен, будет выброшено исключение, а затем будет выполнен метод HandleExceptionMessageAsync в блоке catch. Итак, давайте обновим код метода Invoke, как показано ниже:

public async Task Invoke(HttpContext context)  
{  
    try  
    {  
        await _next.Invoke(context);  
    }  
    catch (Exception ex)  
    {  
        await HandleExceptionMessageAsync(context, ex).ConfigureAwait(false);  
    }  
}  

 Теперь нам нужно реализовать метод HandleExceptionMessageAsync, как показано ниже:

private static Task HandleExceptionMessageAsync(HttpContext context, Exception exception)  
{  
    context.Response.ContentType = "application/json";  
    int statusCode = (int)HttpStatusCode.InternalServerError;  
    var result = JsonConvert.SerializeObject(new  
    {  
        StatusCode = statusCode,  
        ErrorMessage = exception.Message  
    });  
    context.Response.ContentType = "application/json";  
    context.Response.StatusCode = statusCode;  
    return context.Response.WriteAsync(result);  
} 

Теперь, на следующем шаге, нам нужно создать статический класс с именем ExceptionHandlerMiddlewareExtensions и добавить приведенный ниже код в этот класс,

using Microsoft.AspNetCore.Builder;  
using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Threading.Tasks;  
  
namespace API.DemoSample.Exceptions  
{  
    public static class ExceptionHandlerMiddlewareExtensions  
    {  
        public static void UseExceptionHandlerMiddleware(this IApplicationBuilder app)  
        {  
            app.UseMiddleware<ExceptionHandlerMiddleware>();  
        }  
    }  
}  

На последнем этапе, нам нужно включить наше пользовательское middleware в методе Configure класса startup, как показано ниже:

app.UseExceptionHandlerMiddleware();  

Заключение

Обработка исключений — это по сути сквозная функциональность для любого типа приложений. В этой статье мы обсудили процесс реализации концепции глобальной обработки исключений. Мы можем воспользоваться преимуществами глобальной обработки исключений в любом приложении ASP.NET Core, чтобы гарантировать, что каждое исключение будет перехвачено и вернет правильные сведения, связанные с этим исключением. С глобальной обработкой исключений нам достаточно в одном месте написать код, связанный с обработкой исключений, для всего нашего приложения. Любые предложения, отзывы или запросы, связанные с этой статьей, приветствуются.


Узнать подробнее о курсе «C# ASP.NET Core разработчик».

Посмотреть вебинар на тему «Отличия структурных шаблонов проектирования на примерах».

| Воскресенье, 3 марта, 2013

Метки: ASP.NET Web API Комментарии: 0

ASP.NET Web API упрощает разработку HTTP-сервисов, а также предоставляет много способов возврата полных и информативных сообщений об ошибках для различных ситуаций. Рассмотрим эти возможности.

Для начала, посмотрим как выглядит обычное сообщение об ошибке для Web API:

{ 
"Message": "No HTTP resource was found that matches the request URI 'http://localhost/Foo'.", 
"MessageDetail": "No type was found that matches the controller named 'Foo'." 
}

То есть ошибка, это просто коллекция пар ключей и значений, которая сообщает нам, что пошло не так. Эта коллекция отсылается клиенту через HTTP-запросы. В примере выше содержимое представлено в формате JSON.

Но если указать в запросе в заголовке Accept «text/html», то ответ будет в xml виде:

<Error> 
  <Message>No HTTP resource was found that matches the request URI 'http://localhost/Foo'.</Message> 
  <MessageDetail>No type was found that matches the controller named 'Foo'.</MessageDetail> 
</Error>

Этот простой формат предоставляет информацию клиентам об ошибках, что дает возможность делать запись в журнал, либо отсылать отчеты на email.

HttpError

Примеры приведенные выше — это объекты HttpError, сериализованные с помощью Json.NET и DataContactSerializer.

Тип HttpError определяет содержательный и расширяемый способ передачи информации об ошибках между различными платформами. Фактически, это просто класс Dictionary, который предоставляет еще несколько вспомогательных конструкторов для создания сообщений об ошибках, исключениях и некорректных состояниях модели.

public HttpError();
public HttpError(string message);
public HttpError(Exception exception, bool includeErrorDetail);
public HttpError(ModelStateDictionary modelState, bool includeErrorDetail);

Так выглядят исключения в формате JSON:

{ 
"Message": "An error has occurred.", 
"ExceptionMessage": "Index was outside the bounds of the array.", 
"ExceptionType": "System.IndexOutOfRangeException", 
"StackTrace": "   at WebApiTest.TestController.Post(Uri uri) in c:\Temp\WebApiTest\WebApiTest\TestController.cs:line 18rn   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClassf.<GetExecutor>b__9(Object instance, Object[] methodParameters)rn   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object instance, Object[] arguments)rn   at System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func`1 func, CancellationToken cancellationToken)" 
}

Так некорректная модель:

{ 
  "Message": "The request is invalid.", 
  "ModelState": 
   { 
       "s": [ "Required property 's' not found in JSON. Path '', line 1, position 2."  ] 
   }
} 

Можно создавать и собственные виды ошибок в классе HttpError, например так:

public HttpResponseMessage Get()
{
   HttpError myCustomError = new HttpError("My custom error message") { { "CustomErrorCode", 37 } };
   return Request.CreateErrorResponse(HttpStatusCode.BadRequest, myCustomError);
}

Результат будет таким:

{ 
  "Message": "My custom error message", 
  "CustomErrorCode": 37 
}

В этом примере метод возвращает ошибку с помощью Request.CreateErrorMessage. Пользовательская ошибка обернута в класс HttpResponseMessage. Это рекомендованный способ создания ответов c сообщениями об ошибках. Ниже приведены расширяемые методы для возврата такой информации:

public static HttpResponseMessage CreateErrorResponse(this HttpRequestMessage request, HttpStatusCode statusCode, Exception exception);
public static HttpResponseMessage CreateErrorResponse(this HttpRequestMessage request, HttpStatusCode statusCode, HttpError error);
public static HttpResponseMessage CreateErrorResponse(this HttpRequestMessage request, HttpStatusCode statusCode, ModelStateDictionary modelState);
public static HttpResponseMessage CreateErrorResponse(this HttpRequestMessage request, HttpStatusCode statusCode, string message);
public static HttpResponseMessage CreateErrorResponse(this HttpRequestMessage request, HttpStatusCode statusCode, string message, Exception exception);

Но можно и просто генерировать любое исключение в методе действия. Web API автоматически перехватывает исключение, конвертирует в ответ сервера с кодом 500 (Internal Server Error) и возвращает сообщение в точно таком же формате, как и с методом CreateErrorResponse.

HttpResponseException

Но как быть, если нужно вернуть ошибку в методе, который возвращает объекты не являющиеся классами HttpResponseMessage? Тут на помощь приходит класс HttpResponseException. Данный тип исключения определен специально для Web API и служит двум целям:

  1. Позволяет возвращать специальные ответы HTTP из методов, которые не возвращают HttpResponseMethod.
  2. Упрощает код в методах Web API, выполняя вывод ответа сервера с информацией об ошибке немедленно.

Технически, HttpResponseException может быть использован для возврата любых http-ответов, но особенно полезен данный класс для ошибок. Это позволяет нам писать методы следующим образом:

public Person Get(int id)
{
   if (!_contacts.ContainsKey(id))
   {
      throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, String.Format("Contact {0} not found.", id)));
   }
   return _contacts[id];
}

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

Подробное описание ошибок

В начале статьи в примере упоминался параметр «includeErrorDetails» в конструкторе класса HttpError. Это для случаев, когда клиенту нужно предоставить отладочную информацию, для разрешения проблем на сервере. По умолчанию подробная информация не высылается удаленным клиентам, но предоставляется клиентам на локальном компьютере. В самом первом примере статьи, локальный клиент получит:

{ 
"Message": "No HTTP resource was found that matches the request URI 'http://localhost/Foo'.", 
"MessageDetail": "No type was found that matches the controller named 'Foo'." 
}

А до удаленного клиента дойдет только:

{ 
"Message": "No HTTP resource was found that matches the request URI 'http://localhost/Foo'." 
}

MessageDetails содержит специфичную информацию, которую удаленные клиенты в большинстве случаев видеть не должны. То есть ошибки содержащие отладочную информацию (exception message, exception type, stack trace) не предоставляют подробное описание по умолчанию, но ошибки состояния моделей (кроме исключений) подробно отсылаются и удаленным клиентам.

Явно задавать возврат подробной информации можно в объекте HttpConfiguration:

config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.LocalOnly;
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Never;

Как было упомянуто выше, значение по умолчанию LocalOnly.

Обработка ошибок для некорректных моделей

Еще один распространенный способ использования обработки ошибок Web API – это немедленный возврат ошибки в случае некорректной модели. Для этого лучше всего реализовать следующий фильтр метода действия:

public class ValidationFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            actionContext.Response = 
                 actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest,   
                 actionContext.ModelState);
         }
     }
}

Можно прописать этот фильтр для каждого метода а можно зарегистрировать его глобально сразу для всех методов:

config.Filters.Add(new ValidationFilterAttribute());

Это позволяет не писать проверку модели в каждом методе, а сразу возвращать ошибку клиенту в случае некорректной модели.

Никто еще не оставил здесь комментарий.

Содержание

  1. Обработка ошибок API в веб-приложении, используя Axios
  2. Отлов ошибок Axios
  3. Проверка error.response
  4. Проверка error.request
  5. Все остальные типы ошибок
  6. Как вам их исправить?
  7. Ухудшение пользовательского опыта
  8. Обрывистые сети
  9. Обработка ошибок в веб-API ASP.NET Core
  10. Страница со сведениями об исключении для разработчика
  11. Обработчик исключений
  12. Изменение ответа с помощью исключений
  13. Ответ в случае ошибки при сбое проверки
  14. Ответ при ошибке клиента
  15. Ответ со сведениями о проблеме по умолчанию
  16. Служба сведений о проблеме
  17. Настройка сведений о проблеме с помощью CustomizeProblemDetails
  18. Реализуйте ProblemDetailsFactory
  19. Используйте ApiBehaviorOptions.ClientErrorMapping .
  20. Дополнительные ресурсы
  21. Страница со сведениями об исключении для разработчика
  22. Обработчик исключений
  23. Изменение ответа с помощью исключений
  24. Ответ в случае ошибки при сбое проверки
  25. Ответ при ошибке клиента
  26. Реализуйте ProblemDetailsFactory
  27. Используйте ApiBehaviorOptions.ClientErrorMapping .
  28. Пользовательское ПО промежуточного слоя для обработки исключений
  29. Создание полезных данных ProblemDetails для исключений
  30. Дополнительные ресурсы
  31. Страница со сведениями об исключении для разработчика
  32. Обработчик исключений
  33. Изменение ответа с помощью исключений
  34. Ответ в случае ошибки при сбое проверки
  35. Ответ при ошибке клиента
  36. Реализуйте ProblemDetailsFactory
  37. Использование ApiBehaviorOptions.ClientErrorMapping
  38. Пользовательское ПО промежуточного слоя для обработки исключений

Обработка ошибок API в веб-приложении, используя Axios

Когда вы делаете вызов к бэкенд API с axios, вы должны рассмотреть, что делать с блоком .catch() вашего промиса. Теперь вам может показаться, что ваш API высокодоступен и он будет работать 24/7, что рабочий процесс пользователя довольно ясен, ваш JavaScript вменяем и у вас есть модульные тесты. Поэтому, когда вы смотрите на блок catch при выполнении запросов с помощью axios , вы можете подумать: “Ну… Я просто использую console.log . Все будет в порядке.”

Но есть еще так много вещей, которые находятся вне вашего контроля, которые могут вызвать ошибки при выполнении запросов API — и вы, вероятно, даже не знаете, что они происходят!

Эта статья посвящена в основном ошибкам, которые вы видите в браузере. На бэкенде тоже все может выглядеть довольно забавно. Просто взгляните на три вещи, которые вы можете увидеть в своих бэкенд журналах.

Ниже приведены три типа ошибок, которые могут появиться, и как их обрабатывать при использовании axios.

Отлов ошибок Axios

Ниже приведен фрагмент кода, который я начал включать в несколько проектов JS:

Каждое условие предназначено для фиксации различного типа ошибки.

Проверка error.response

Если ваш объект error содержит поле response , это означает, что сервер ответил с ошибкой 4xx/5xx. Обычно это та ошибка, с которой мы лучше всего знакомы и с которой легче всего справиться.

Применяйте следующее: “Показать страницу 404 Not Found / сообщение об ошибке, если ваш API возвращает 404.” Покажите другое сообщение об ошибке, если ваш бэкенд возвращает 5xx или вообще ничего не возвращает. Вы может показаться, что ваш хорошо сконструированный бэкенд не будет генерировать ошибки, но это всего лишь вопрос времени, а не “если”.

Проверка error.request

Второй класс ошибок — это когда у вас нет ответа, но есть поле request , прикрепленное к ошибке. Когда же это происходит? Это происходит, когда браузер смог сделать запрос, но по какой-то причине не получил ответа. Это может произойти, если:

• Вы находитесь в обрывочной сети (например, в метро или используете беспроводную сеть здания).

• Ваш бэкенд зависает на каждом запросе и не возвращает ответ вовремя.

• Вы делаете междоменные запросы, но вы не авторизованы, чтобы их делать.

• Вы делаете междоменные запросы, и вы авторизованы, но бэкенд API возвращает ошибку.

Одна из наиболее распространенных версий этой ошибки имела бесполезное сообщение “Ошибка сети”. У нас есть API для фронтенда и бэкенда, размещенные в разных доменах, поэтому каждый вызов к бэкенд API — это междоменный запрос.

Из-за ограничений безопасности на JS в браузере, если вы делаете запрос API, и он не работает из-за плохих сетей, единственная ошибка, которую вы увидите — это “Ошибка сети”, которая невероятно бесполезна. Она может означать что угодно: от “Ваше устройство не имеет подключения к Интернету” до “Ваши OPTIONS вернули 5xx” (если вы делаете запросы CORS). Причина ошибки сети хорошо описана в этом ответе на StackOverflow.

Все остальные типы ошибок

Если ваш объект error не содержит поля response или request , это означает, что это не ошибка axios и, скорее всего, в вашем приложении что-то еще не так. Сообщение об ошибке + трассировка стека должны помочь вам понять, откуда оно исходит.

Как вам их исправить?

Ухудшение пользовательского опыта

Все это зависит от вашего приложения. Для проектов, над которыми я работаю, для каждой функции, использующей эти конечные точки, мы ухудшаем пользовательский опыт.

Например, если запрос не выполняется и страница бесполезна без этих данных, то у нас будет большая страница ошибок, которая появится и предложит пользователям выход — иногда это всего лишь кнопка “Обновить страницу”.

Другой пример: если запрос на изображение профиля в потоке социальных сетей не выполняется, мы можем показать изображение-плейсхолдер и отключить изменения изображения профиля вместе с всплывающим уведомлением, объясняющим, почему кнопка “Обновить изображение профиля” отключена. Однако показывать предупреждение с надписью “422 необработанных объекта” бесполезно для пользователя.

Обрывистые сети

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

Для решения такого рода периодических проблем с сетью, мы добавили axios-retry, что решило большое количество ошибок, которые мы наблюдали в продакшне. Это было добавлено в нашу настройку axios:

Мы увидели, что 10% наших пользователей (которые находятся в плохих школьных сетях) периодически наблюдали ошибки сети, но число снизилось до

Источник

Обработка ошибок в веб-API ASP.NET Core

В этой статье описывается обработка ошибок и настройка обработки ошибок с помощью ASP.NET Core веб-API.

Страница со сведениями об исключении для разработчика

На странице исключений для разработчиков отображаются подробные трассировки стека ошибок сервера. Он использует DeveloperExceptionPageMiddleware для записи синхронных и асинхронных исключений из конвейера HTTP и для создания ответов об ошибках. Например, рассмотрим следующее действие контроллера, которое создает исключение:

Когда страница исключений разработчика обнаруживает необработанное исключение, она создает ответ в виде обычного текста по умолчанию, аналогичный следующему примеру:

Если клиент запрашивает ответ в формате HTML, страница исключений разработчика создает ответ, аналогичный следующему примеру:

Чтобы запросить ответ в формате HTML, задайте для заголовка Accept HTTP-запроса значение text/html .

Не включайте страницу исключений для разработчиков, если приложение не запущено в среде разработки. Не публикуйте подробные сведения об исключениях, когда приложение выполняется в рабочей среде. Дополнительные сведения о настройке сред см. в статье Использование нескольких сред в ASP.NET Core.

Обработчик исключений

В средах, не относящихся к разработке, используйте ПО промежуточного слоя для обработки исключений , чтобы создать полезные данные об ошибке:

В Program.cs вызовите UseExceptionHandler , чтобы добавить ПО промежуточного слоя для обработки исключений:

Настройте действие контроллера для ответа на маршрут /error :

Предыдущее действие HandleError отправляет клиенту полезные данные, соответствующие RFC 7807.

Не следует помечать метод действия обработки ошибок атрибутами метода HTTP, например HttpGet . Из-за использования явных команд некоторые запросы могут не передаваться в метод действия.

Для веб-API, использующих Swagger или OpenAPI, пометьте действие обработчика ошибок атрибутом [ApiExplorerSettings] и задайте для его IgnoreApi свойства значение true . Эта конфигурация атрибута исключает действие обработчика ошибок из спецификации OpenAPI приложения:

Разрешите анонимный доступ к методу, если пользователи без проверки подлинности должны увидеть ошибку.

ПО промежуточного слоя для обработки исключений также можно использовать в среде разработки для создания согласованного формата полезных данных во всех средах:

В Program.cs зарегистрируйте экземпляры ПО промежуточного слоя обработки исключений для конкретной среды:

В приведенном выше коде ПО промежуточного слоя регистрируется с помощью внедрения зависимостей.

  • Маршрут /error-development в среде разработки.
  • Маршрут в средах /error , не относящихся к разработке.

Добавьте действия контроллера как для маршрутов Разработки, так и для маршрутов, не относящихся к разработке:

Изменение ответа с помощью исключений

Содержимое ответа можно изменить за пределами контроллера с помощью настраиваемого исключения и фильтра действий:

Создайте известный тип исключения с именем HttpResponseException :

Создайте фильтр действий с именем HttpResponseExceptionFilter :

Предыдущий фильтр указывает Order максимальное целочисленное значение минус 10. Это Order позволяет выполнять другие фильтры в конце конвейера.

В Program.cs добавьте фильтр действий в коллекцию фильтров:

Ответ в случае ошибки при сбое проверки

В контроллерах веб-API MVC отвечает с помощью типа ответа ValidationProblemDetails при сбое проверки модели. MVC использует результаты InvalidModelStateResponseFactory для создания ответа в случае ошибки при сбое проверки. В следующем примере фабрика по умолчанию заменяется реализацией, которая также поддерживает форматирование ответов в формате XML в Program.cs :

Ответ при ошибке клиента

Результат ошибки определяется как результат с кодом состояния HTTP 400 или выше. Для контроллеров веб-API MVC преобразует результат ошибки для создания ProblemDetails.

Автоматическое создание ProblemDetails для кодов состояния ошибок включено по умолчанию, но ответы на ошибки можно настроить одним из следующих способов:

Ответ со сведениями о проблеме по умолчанию

Следующий Program.cs файл был создан шаблонами веб-приложений для контроллеров API:

Рассмотрим следующий контроллер, который возвращает BadRequest , если входные данные недопустимы:

Ответ со сведениями о проблеме создается с помощью предыдущего кода, если применяется любое из следующих условий:

  • Конечная /api/values2/divide точка вызывается с нулевым знаменателем.
  • Конечная /api/values2/squareroot точка вызывается с radicand меньше нуля.

Текст ответа на сведения о проблеме по умолчанию имеет следующие type значения , title и status :

Служба сведений о проблеме

Следующий код настраивает приложение для создания ответа со сведениями о проблеме для всех ответов об ошибках HTTP-клиента и сервера, у которых еще нет содержимого текста:

Рассмотрим контроллер API из предыдущего раздела, который возвращает, BadRequest если входные данные недопустимы:

Ответ со сведениями о проблеме создается с помощью предыдущего кода, если применяется любое из следующих условий:

  • Указан недопустимый вход.
  • Универсальный код ресурса (URI) не имеет совпадающей конечной точки.
  • Возникает необработанное исключение.

Отключить автоматическое создание ProblemDetails для кодов состояния ошибок можно, задав свойству SuppressMapClientErrors значение true :

Используя приведенный выше код, когда контроллер API возвращает BadRequest , возвращается состояние ответа HTTP 400 без текста ответа. SuppressMapClientErrors предотвращает ProblemDetails создание ответа даже при вызове WriteAsync конечной точки контроллера API. WriteAsync объясняется далее в этой статье.

В следующем разделе показано, как настроить текст ответа со сведениями о проблеме с помощью CustomizeProblemDetails, чтобы получить более полезный ответ. Дополнительные параметры настройки см. в разделе Настройка сведений о проблеме.

Настройка сведений о проблеме с помощью CustomizeProblemDetails

Обновленный контроллер API:

Следующий код содержит MathErrorFeature и MathErrorType , которые используются в предыдущем примере:

Ответ со сведениями о проблеме создается с помощью предыдущего кода, если применяется любое из следующих условий:

  • Конечная /divide точка вызывается с нулевым знаменателем.
  • Конечная /squareroot точка вызывается с radicand меньше нуля.
  • Универсальный код ресурса (URI) не имеет совпадающей конечной точки.

Текст ответа сведений о проблеме содержит следующее, если любая из squareroot конечных точек вызывается с radicand меньше нуля:

Реализуйте ProblemDetailsFactory

Чтобы настроить ответ с подробными сведениями о проблемах, зарегистрируйте пользовательскую реализацию ProblemDetailsFactory в Program.cs :

Используйте ApiBehaviorOptions.ClientErrorMapping .

Используйте свойство ClientErrorMapping, чтобы настроить содержимое ответа ProblemDetails . Например, следующий код в Link обновляет свойство Program.cs для ответов 404:

Дополнительные ресурсы

В этой статье описывается обработка ошибок и настройка обработки ошибок с помощью ASP.NET Core веб-API.

Страница со сведениями об исключении для разработчика

На странице исключений для разработчиков отображаются подробные трассировки стека ошибок сервера. Он использует DeveloperExceptionPageMiddleware для записи синхронных и асинхронных исключений из конвейера HTTP и для создания ответов об ошибках. Например, рассмотрим следующее действие контроллера, которое создает исключение:

Когда страница исключений разработчика обнаруживает необработанное исключение, она создает ответ в виде обычного текста по умолчанию, аналогичный следующему примеру:

Если клиент запрашивает ответ в формате HTML, страница исключений разработчика создает ответ, аналогичный следующему примеру:

Чтобы запросить ответ в формате HTML, задайте для заголовка Accept HTTP-запроса значение text/html .

Не включайте страницу исключений для разработчиков, если приложение не запущено в среде разработки. Не публикуйте подробные сведения об исключениях, когда приложение выполняется в рабочей среде. Дополнительные сведения о настройке сред см. в статье Использование нескольких сред в ASP.NET Core.

Обработчик исключений

В средах, не относящихся к разработке, используйте ПО промежуточного слоя для обработки исключений , чтобы создать полезные данные об ошибке:

В Program.cs вызовите UseExceptionHandler , чтобы добавить ПО промежуточного слоя для обработки исключений:

Настройте действие контроллера для ответа на маршрут /error :

Предыдущее действие HandleError отправляет клиенту полезные данные, соответствующие RFC 7807.

Не следует помечать метод действия обработки ошибок атрибутами метода HTTP, например HttpGet . Из-за использования явных команд некоторые запросы могут не передаваться в метод действия.

Для веб-API, использующих Swagger или OpenAPI, пометьте действие обработчика ошибок атрибутом [ApiExplorerSettings] и задайте для его IgnoreApi свойства значение true . Эта конфигурация атрибута исключает действие обработчика ошибок из спецификации OpenAPI приложения:

Разрешите анонимный доступ к методу, если пользователи без проверки подлинности должны увидеть ошибку.

ПО промежуточного слоя для обработки исключений также можно использовать в среде разработки для создания согласованного формата полезных данных во всех средах:

В Program.cs зарегистрируйте экземпляры ПО промежуточного слоя обработки исключений для конкретной среды:

В приведенном выше коде ПО промежуточного слоя регистрируется с помощью внедрения зависимостей.

  • Маршрут /error-development в среде разработки.
  • Маршрут в средах /error , не относящихся к разработке.

Добавьте действия контроллера как для маршрутов Разработки, так и для маршрутов, не относящихся к разработке:

Изменение ответа с помощью исключений

Содержимое ответа можно изменить за пределами контроллера с помощью настраиваемого исключения и фильтра действий:

Создайте известный тип исключения с именем HttpResponseException :

Создайте фильтр действий с именем HttpResponseExceptionFilter :

Предыдущий фильтр указывает Order максимальное целочисленное значение минус 10. Это Order позволяет выполнять другие фильтры в конце конвейера.

В Program.cs добавьте фильтр действий в коллекцию фильтров:

Ответ в случае ошибки при сбое проверки

В контроллерах веб-API MVC отвечает с помощью типа ответа ValidationProblemDetails при сбое проверки модели. MVC использует результаты InvalidModelStateResponseFactory для создания ответа в случае ошибки при сбое проверки. В следующем примере фабрика по умолчанию заменяется реализацией, которая также поддерживает форматирование ответов в формате XML в Program.cs :

Ответ при ошибке клиента

Результат ошибки определяется как результат с кодом состояния HTTP 400 или выше. Для контроллеров веб-API MVC преобразует результат ошибки для создания ProblemDetails.

Ответ при ошибке можно настроить одним из следующих способов:

Реализуйте ProblemDetailsFactory

Чтобы настроить ответ с подробными сведениями о проблемах, зарегистрируйте пользовательскую реализацию ProblemDetailsFactory в Program.cs :

Используйте ApiBehaviorOptions.ClientErrorMapping .

Используйте свойство ClientErrorMapping, чтобы настроить содержимое ответа ProblemDetails . Например, следующий код в Link обновляет свойство Program.cs для ответов 404:

Пользовательское ПО промежуточного слоя для обработки исключений

Значения по умолчанию в ПО промежуточного слоя для обработки исключений хорошо работают для большинства приложений. Для приложений, требующих специализированной обработки исключений, рассмотрите возможность настройки ПО промежуточного слоя для обработки исключений.

Создание полезных данных ProblemDetails для исключений

ASP.NET Core не создает стандартизированные полезные данные ошибок при возникновении необработанного исключения. В сценариях, в которых желательно возвращать клиенту стандартизованный ответ ProblemDetails , ПО промежуточного слоя ProblemDetails можно использовать для сопоставления исключений и 404 ответов с полезными данными ProblemDetails . ПО промежуточного слоя для обработки исключений ProblemDetails также можно использовать для возврата полезных данных для необработанных исключений.

Дополнительные ресурсы

В этой статье описывается обработка и настройка обработки ошибок с помощью веб-API ASP.NET Core.

Страница со сведениями об исключении для разработчика

Страница со сведениями об исключении для разработчика — это полезное средство, с помощью которого можно получить подробные трассировки стека для ошибок сервера. Он использует DeveloperExceptionPageMiddleware для записи синхронных и асинхронных исключений из конвейера HTTP и для создания ответов об ошибках. Для иллюстрации рассмотрим следующее действие контроллера:

Выполните следующую команду curl , чтобы проверить предыдущее действие:

Страница исключений разработчика отображает ответ в виде обычного текста, если клиент не запрашивает выходные данные в формате HTML. Отображаются следующие результаты:

Чтобы вместо этого отображался отформатированный HTML-запрос, задайте для заголовка HTTP-запроса Accept тип носителя text/html . Пример:

Рассмотрим следующий фрагмент HTTP-ответа:

Отформатированный HTML-ответ полезен при тестировании с помощью таких инструментов, как Postman. На следующем снимке экрана показаны ответы в виде обычного текста и в формате HTML в Postman:

Включать страницу исключений для разработчика следует только тогда, когда приложение выполняется в среде разработки. Не публикуйте подробные сведения об исключениях, когда приложение выполняется в рабочей среде. Дополнительные сведения о настройке сред см. в статье Использование нескольких сред в ASP.NET Core.

Не следует помечать метод действия обработки ошибок атрибутами метода HTTP, например HttpGet . Из-за использования явных команд некоторые запросы могут не передаваться в метод действия. Разрешите анонимный доступ к методу, если пользователи без проверки подлинности должны увидеть ошибку.

Обработчик исключений

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

В Startup.Configure вызовите UseExceptionHandler, чтобы использовать ПО промежуточного слоя:

Настройте действие контроллера для ответа на маршрут /error :

Предыдущее действие Error отправляет клиенту полезные данные, соответствующие RFC 7807.

ПО промежуточного слоя для обработки исключений также может предоставлять более подробные данные, согласованные с содержимым, в локальной среде разработки. Чтобы создать согласованный формат полезных данных в среде разработки и рабочей среде, сделайте следующее:

В Startup.Configure зарегистрируйте экземпляры ПО промежуточного слоя обработки исключений для конкретной среды:

В приведенном выше коде ПО промежуточного слоя регистрируется с помощью внедрения зависимостей.

  • Маршрут /error-local-development в среде разработки.
  • Маршрут /error в средах, не имеющих отношения к разработке.

Примените маршрутизацию атрибутов к действиям контроллера:

Приведенный выше код вызывает ControllerBase.Problem для создания ProblemDetails ответа.

Изменение ответа с помощью исключений

Содержимое ответа можно изменить за пределами контроллера. В веб-API ASP.NET 4.x один из способов это сделать — использовать тип HttpResponseException. ASP.NET Core не содержит эквивалентный тип. Поддержку HttpResponseException можно добавить, сделав следующее:

Создайте известный тип исключения с именем HttpResponseException :

Создайте фильтр действий с именем HttpResponseExceptionFilter :

Предыдущий фильтр задает Order значение максимального целого числа минус 10. Это Order позволяет выполнять другие фильтры в конце конвейера.

В Startup.ConfigureServices добавьте фильтр действий в коллекцию фильтров:

Ответ в случае ошибки при сбое проверки

В контроллерах веб-API MVC отвечает с помощью типа ответа ValidationProblemDetails при сбое проверки модели. MVC использует результаты InvalidModelStateResponseFactory для создания ответа в случае ошибки при сбое проверки. В следующем примере в Startup.ConfigureServices для изменения типа ответа по умолчанию на SerializableError используется фабрика:

Ответ при ошибке клиента

Результат ошибки определяется как результат с кодом состояния HTTP 400 или выше. В контроллерах веб-API платформа MVC преобразует результат ошибки в результат с ProblemDetails.

Ответ при ошибке можно настроить одним из следующих способов:

Реализуйте ProblemDetailsFactory

Чтобы настроить ответ с подробными сведениями о проблемах, зарегистрируйте пользовательскую реализацию ProblemDetailsFactory в Startup.ConfigureServices :

Использование ApiBehaviorOptions.ClientErrorMapping

Используйте свойство ClientErrorMapping, чтобы настроить содержимое ответа ProblemDetails . Например, следующий код в type обновляет свойство Startup.ConfigureServices для ответов 404:

Пользовательское ПО промежуточного слоя для обработки исключений

Значения по умолчанию в ПО промежуточного слоя для обработки исключений хорошо работают для большинства приложений. Для приложений, требующих специализированной обработки исключений, рассмотрите возможность настройки ПО промежуточного слоя для обработки исключений.

Источник

Quick and Easy Exception Handling

Simply add this middleware before ASP.NET routing into your middleware registrations.

app.UseExceptionHandler(c => c.Run(async context =>
{
    var exception = context.Features
        .Get<IExceptionHandlerPathFeature>()
        .Error;
    var response = new { error = exception.Message };
    await context.Response.WriteAsJsonAsync(response);
}));
app.UseMvc(); // or .UseRouting() or .UseEndpoints()

Done!


Enable Dependency Injection for logging and other purposes

Step 1. In your startup, register your exception handling route:

// It should be one of your very first registrations
app.UseExceptionHandler("/error"); // Add this
app.UseEndpoints(endpoints => endpoints.MapControllers());

Step 2. Create controller that will handle all exceptions and produce error response:

[AllowAnonymous]
[ApiExplorerSettings(IgnoreApi = true)]
public class ErrorsController : ControllerBase
{
    [Route("error")]
    public MyErrorResponse Error()
    {
        var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
        var exception = context.Error; // Your exception
        var code = 500; // Internal Server Error by default

        if      (exception is MyNotFoundException) code = 404; // Not Found
        else if (exception is MyUnauthException)   code = 401; // Unauthorized
        else if (exception is MyException)         code = 400; // Bad Request

        Response.StatusCode = code; // You can use HttpStatusCode enum instead

        return new MyErrorResponse(exception); // Your error model
    }
}

A few important notes and observations:

  • You can inject your dependencies into the Controller’s constructor.
  • [ApiExplorerSettings(IgnoreApi = true)] is needed. Otherwise, it may break your Swashbuckle swagger
  • Again, app.UseExceptionHandler("/error"); has to be one of the very top registrations in your Startup Configure(...) method. It’s probably safe to place it at the top of the method.
  • The path in app.UseExceptionHandler("/error") and in controller [Route("error")] should be the same, to allow the controller handle exceptions redirected from exception handler middleware.

Here is the link to official Microsoft documentation.


Response model ideas.

Implement your own response model and exceptions.
This example is just a good starting point. Every service would need to handle exceptions in its own way. With the described approach you have full flexibility and control over handling exceptions and returning the right response from your service.

An example of error response model (just to give you some ideas):

public class MyErrorResponse
{
    public string Type { get; set; }
    public string Message { get; set; }
    public string StackTrace { get; set; }

    public MyErrorResponse(Exception ex)
    {
        Type = ex.GetType().Name;
        Message = ex.Message;
        StackTrace = ex.ToString();
    }
}

For simpler services, you might want to implement http status code exception that would look like this:

public class HttpStatusException : Exception
{
    public HttpStatusCode Status { get; private set; }

    public HttpStatusException(HttpStatusCode status, string msg) : base(msg)
    {
        Status = status;
    }
}

This can be thrown from anywhere this way:

throw new HttpStatusCodeException(HttpStatusCode.NotFound, "User not found");

Then your handling code could be simplified to just this:

if (exception is HttpStatusException httpException)
{
    code = (int) httpException.Status;
}

HttpContext.Features.Get<IExceptionHandlerFeature>() WAT?

ASP.NET Core developers embraced the concept of middlewares where different aspects of functionality such as Auth, MVC, Swagger etc. are separated and executed sequentially in the request processing pipeline. Each middleware has access to request context and can write into the response if needed. Taking exception handling out of MVC makes sense if it’s important to handle errors from non-MVC middlewares the same way as MVC exceptions, which I find is very common in real world apps. So because built-in exception handling middleware is not a part of MVC, MVC itself knows nothing about it and vice versa, exception handling middleware doesn’t really know where the exception is coming from, besides of course it knows that it happened somewhere down the pipe of request execution. But both may needed to be «connected» with one another. So when exception is not caught anywhere, exception handling middleware catches it and re-runs the pipeline for a route, registered in it. This is how you can «pass» exception handling back to MVC with consistent content negotiation or some other middleware if you wish. The exception itself is extracted from the common middleware context. Looks funny but gets the job done :).

Handling errors in an ASP.NET Core Web API

This post looks at the best ways to handle exceptions, validation and other invalid requests such as 404s in ASP.NET Core Web API projects and how these approaches differ from MVC error handling.

Why do we need a different approach from MVC?

In .Net Core, MVC and Web API have been combined so you now have the same controllers for both MVC actions and API actions. However, despite the similarities, when it comes to error handling, you almost certainly want to use a different approach for API errors.

MVC actions are typically executed as a result of a user action in the browser so returning an error page to the browser is the correct approach. With an API, this is not generally the case.

API calls are most often called by back-end code or javascript code and in both cases, you never want to simply display the response from the API. Instead we check the status code and parse the response to determine if our action was successful, displaying data to the user as necessary. An error page is not helpful in these situations. It bloats the response with HTML and makes client code difficult because JSON (or XML) is expected, not HTML.

While we want to return information in a different format for Web API actions, the techniques for handling errors are not so different from MVC. Much of the time, it is practically the same flow but instead of returning a View, we return JSON. Let’s look at a few examples.

The minimal approach

With MVC actions, failure to display a friendly error page is unacceptable in a professional application. With an API, while not ideal, empty response bodies are far more permissible for many invalid request types. Simply returning a 404 status code (with no response body) for an API route that does not exist may provide the client with enough information to fix their code.

With zero configuration, this is what ASP.NET Core gives us out of the box.

Depending on your requirements, this may be acceptable for many common status codes but it will rarely be sufficient for validation failures. If a client passes you invalid data, returning a 400 Bad Request is not going to be helpful enough for the client to diagnose the problem. At a minimum, we need to let them know which fields are incorrect and ideally, we would return an informative message for each failure.

With ASP.NET Web API, this is trivial. Assuming that we are using model binding, we get validation for free by using data annotations and/or IValidatableObject. Returning the validation information to the client as JSON is one easy line of code.

Here is our model:

public class GetProductRequest : IValidatableObject
{
    [Required]
    public string ProductId { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (...)
        {
            yield return new ValidationResult("ProductId is invalid", new[] { "ProductId" });
        }
    }
}

And our controller action:

[HttpGet("product")]
public IActionResult GetProduct(GetProductRequest request)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    ...
}

A missing ProductId results in a 400 status code plus a JSON response body similar to the following:

{
    "ProductId":["The ProductId field is required."]
}

This provides an absolute minimum for a client to consume our service but it is not difficult to improve upon this baseline and create a much better client experience. In the next few sections we will look at how simple it is to take our service to the next level.

Returning additional information for specific errors

If we decide that a status code only approach is too bare-bones, it is easy to provide additional information. This is highly recommended. There are many situations where a status code by itself is not enough to determine the cause of failure. If we take a 404 status code as an example, in isolation, this could mean:

  • We are making the request to the wrong site entirely (perhaps the ‘www’ site rather than the ‘api’ subdomain)
  • The domain is correct but the URL does not match a route
  • The URL correctly maps to a route but the resource does not exist

If we could provide information to distinguish between these cases, it could be very useful for a client. Here is our first attempt at dealing with the last of these:

[HttpGet("product")]
public async Task<IActionResult> GetProduct(GetProductRequest request)
{
    ...

    var model = await _db.Get(...);

    if (model == null)
    {
        return NotFound("Product not found");
    }

    return Ok(model);
}

We are now returning a more useful message but it is far from perfect. The main problem is that by using a string in the NotFound method, the framework will return this string as a plain text response rather than JSON.

As a client, a service returning a different content type for certain errors is much harder to deal with than a consistent JSON service.

This issue can quickly be rectified by changing the code to what is shown below but in the next section, we will talk about a better alternative.

return NotFound(new { message = "Product not found" });

Customising the response structure for consistency

Constructing anonymous objects on the fly is not the approach to take if you want a consistent client experience. Ideally our API should return the same response structure in all cases, even when the request was unsuccessful.

Let’s define a base ApiResponse class:

public class ApiResponse
{
    public int StatusCode { get; }

    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public string Message { get; }

    public ApiResponse(int statusCode, string message = null)
    {
        StatusCode = statusCode;
        Message = message ?? GetDefaultMessageForStatusCode(statusCode);
    }

    private static string GetDefaultMessageForStatusCode(int statusCode)
    {
        switch (statusCode)
        {
            ...
            case 404:
                return "Resource not found";
            case 500:
                return "An unhandled error occurred";
            default:
                return null;
        }
    }
}

We’ll also need a derived ApiOkResponse class that allows us to return data:

public class ApiOkResponse : ApiResponse
{
    public object Result { get; }

    public ApiOkResponse(object result)
        :base(200)
    {
        Result = result;
    }
}

Finally, let’s declare an ApiBadRequestResponse class to handle validation errors (if we want our responses to be consistent, we will need to replace the built-in functionality used above).

public class ApiBadRequestResponse : ApiResponse
{
    public IEnumerable<string> Errors { get; }

    public ApiBadRequestResponse(ModelStateDictionary modelState)
        : base(400)
    {
        if (modelState.IsValid)
        {
            throw new ArgumentException("ModelState must be invalid", nameof(modelState));
        }

        Errors = modelState.SelectMany(x => x.Value.Errors)
            .Select(x => x.ErrorMessage).ToArray();
    }
}

These classes are very simple but can be customised to your own requirements.

If we change our action to use these ApiResponse based classes, it becomes:

[HttpGet("product")]
public async Task<IActionResult> GetProduct(GetProductRequest request)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(new ApiBadRequestResponse(ModelState));
    }

    var model = await _db.Get(...);

    if (model == null)
    {
        return NotFound(new ApiResponse(404, $"Product not found with id {request.ProductId}"));
    }

    return Ok(new ApiOkResponse(model));
}

The code is slightly more complicated now but all three types of response from our action (success, bad request and not found) now use the same general structure.

Centralising Validation Logic

Given that validation is something that you do in practically every action, it makes to refactor this generic code into an action filter. This reduces the size of our actions, removes duplicated code and improves consistency.

public class ApiValidationFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(new ApiBadRequestResponse(context.ModelState));
        }

        base.OnActionExecuting(context);
    }
}

Handling global errors

Responding to bad input in our controller actions is the best way to provide specific error information to our client. Sometimes however, we need to respond to more generic issues. Examples of this include:

  • A 401 Unauthorized code returned from security middleware.

  • A request URL that does not map to a controller action resulting in a 404.

  • Global exceptions. Unless you can do something about a specific exception, you should not clutter your actions with try catch blocks.

As with MVC, the easiest way to deal with global errors is by using StatusCodePagesWithReExecute and UseExceptionHandler.

We talked about StatusCodePagesWithReExecute last time but to reiterate, when a non-success status code is returned from inner middleware (such as an API action), the middleware allows you to execute another action to deal with the status code and return a custom response.

UseExceptionHandler works in a similar way, catching and logging unhandled exceptions and allowing you to execute another action to handle the error. In this example, we configure both pieces of middleware to point to the same action.

We add the middleware in startup.cs:

app.UseStatusCodePagesWithReExecute("/error/{0}");
app.UseExceptionHandler("/error/500");
...
//register other middleware that might return a non-success status code

Then we add our error handling action:

[Route("error/{code}")]
public IActionResult Error(int code)
{
    return new ObjectResult(new ApiResponse(code));
}

With this in place, all exceptions and non-success status codes (without a response body) will be handled by our error action where we return our standard ApiResponse.

Custom Middleware

For the ultimate in control, you can replace or complement built-in middleware with your own custom middleware. The example below handles any bodiless response and returns our simple ApiResponse object as JSON. If this is used in conjunction with code in our actions to return ApiResponse objects, we can ensure that both success and failure responses share the same common structure and all requests result in both a status code and a consistent JSON body:

public class ErrorWrappingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ErrorWrappingMiddleware> _logger;
    
    public ErrorWrappingMiddleware(RequestDelegate next, ILogger<ErrorWrappingMiddleware> logger)
    {
        _next = next;
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next.Invoke(context);
        }
        catch(Exception ex)
        {
            _logger.LogError(EventIds.GlobalException, ex, ex.Message);

            context.Response.StatusCode = 500;
        }            

        if (!context.Response.HasStarted)
        {
            context.Response.ContentType = "application/json";

            var response = new ApiResponse(context.Response.StatusCode);

            var json = JsonConvert.SerializeObject(response);

            await context.Response.WriteAsync(json);
        }            
    }
}

Conclusion

Handling errors in ASP.NET Core APIs is similar but different from MVC error code. At the action level, we want to return custom objects (serialised as JSON) rather than custom views.

For generic errors, we can still use the StatusCodePagesWithReExecute middleware but need to modify our code to return an ObjectResult instead of a ViewResult.

For full control, it is not difficult to write your own middleware to handle errors exactly as required.

Useful or Interesting?

If you liked the article, I would really appreciate it if you could share it with your Twitter followers.

Share
on Twitter

Comments

0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии

А вот еще интересные материалы:

  • Яшка сломя голову остановился исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного где ошибка
  • Wearable lanterns ошибка профиль чтение запись поврежден что это
  • We found a problem office 2019 ошибка как исправить