Пользовательская обработка исключений в .NET API
Исключения – это плохо, мы знаем, верно? Но что, если нам придется с ними справиться?
Что происходит, когда у нас есть исключение, например в API, оно отображает сообщение стека, содержащее много информации, которую мы, возможно, захотим удалить из ответа, который получают наши пользователи.
Для демонстрации я создал API dotnet и добавил метод, который выдает исключение:
[HttpGet]
[Route("GetWithoutExceptionHandler")]
public Task GetWithoutExceptionHandler()
{
throw new Exception("This is a custom exception!");
}
Если мы создадим исключение, это будет выглядеть так:
[[[ТОК_1]]]
Это выглядит нормально, это то, что вы получаете из исключения, но для меня это отображение большого количества информации. Если у вас есть библиотеки и прочее, оно может показать полезную информацию о вашем клиенте, вашем проекте или других вещах тому, кто может пытаться что-то увидеть.
Вот как это выглядит на Swagger:

Создание собственного исключения
Этого шага можно было бы избежать, поскольку мы знаем, какое исключение будет выдано, в данном случае Exception, но для меня лучше иметь собственные исключения, поскольку у вас больше контроля над тем, что вы выбрасываете.
В данном случае я только что создал объект CustomException, который наследуется от Exception:
[[[ТОК_6]]]
После того, как мы создали собственное исключение, давайте обновим наш метод, чтобы он выдавал CustomException вместо Exception:
[[[ТОК_9]]]
На данный момент это ничего не изменит, но трассировка стека покажет, что созданный объект является CustomException вместо Exception, взгляните на начало трассировки стека:
[[[ТОК_12]]]
Создание атрибута ExceptionFilterAttribute
Microsoft предоставила нам способ обработки исключений после их возникновения. Дополнительную информацию можно найти здесь.
Но на данный момент документация, которую они нам предоставляют, такова:
Абстрактный фильтр, который запускается асинхронно после того, как действие вызвало исключение. Подклассы должны переопределять OnException(ExceptionContext) или OnExceptionAsync(ExceptionContext), но не оба одновременно.
Итак, давайте создадим один, мы собираемся создать CustomExceptionFilterAttribute в котором мы собираемся переопределить OnException:
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using CustomExceptionHandleDemo.Exceptions;
namespace CustomExceptionHandleDemo.ExceptionFilterAttributes
{
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
/// <summary>
/// OnException
/// </summary>
/// <param cref="ExceptionContext" name="context">Parameter for context</param>
public override void OnException(ExceptionContext context)
{
if (context.Exception is CustomException)
{
context.HttpContext.Response.StatusCode = 500;
context.Result = new ObjectResult(context.Exception.Message);
}
}
}
}
Как видите, мы рассматриваем ExceptionContext, когда исключение является типом CustomException, мы что-то делаем.
Это что-то обновляет ответ и код состояния того, что мы собираемся вернуть.
Чтобы обновить код состояния, мы должны обновить context.HttpContext.Response.StatusCode и чтобы вернуть результат, мы должны обновить context.Result, передав ему объект, унаследованный от ActionResult.
Это фильтр, поэтому нам нужно что-то добавить, добавив [CustomExceptionFilter].
Использование фильтра
Теперь давайте воспроизведем имеющийся у нас метод и добавим этот фильтр, чтобы он действовал. Конечная точка нашего API будет выглядеть следующим образом:
using CustomExceptionHandleDemo.ExceptionFilterAttributes;
using CustomExceptionHandleDemo.Exceptions;
using Microsoft.AspNetCore.Mvc;
namespace CustomExceptionHandleDemo.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
public WeatherForecastController()
{
}
[HttpGet]
[Route("GetWithoutExceptionHandler")]
public Task GetWithoutExceptionHandler()
{
throw new CustomException("This is a custom exception!");
}
[HttpGet]
[Route("GetWithExceptionHandler")]
[CustomExceptionFilter]
public Task GetWithExceptionHandler()
{
throw new CustomException("This is a custom exception!");
}
}
}
Как видите, у нас есть новый метод под названием GetWithExceptionHandler, который имеет ту же логику, что и GetWithoutExceptionHandler, но в данном случае мы добавили фильтр [CustomExceptionFilter] к методу.
Результат следующий: после запуска метода я отобразю изображение, поскольку оно больше не показывает трассировку стека:
Таким образом, мы создали собственное исключение, фильтр для переопределения того, что происходит, когда мы выбрасываем исключение и используем его в методе.
Это можно использовать для множества вещей, таких как ведение журнала и знание того, что, когда и где происходит ошибка.