В отличие от большинства фильтров в ASP.NET Core, которые имеют обработчик событий "перед" и "после", фильтры исключений располагают только одним обработчиком: OnException
(или OnExceptionAsync
). Обработчик принимает один параметр, ExceptionContext
, который предоставляет доступ к ActionContext
, а также к сгенерированному исключению.
Кроме того, фильтры принимают участие во внедрении зависимостей, позволяя получить доступ в коде к любому элементу внутри контейнера. В рассматриваемом примере вам необходим экземпляр реализации IWebHostEnvironment
, внедренный в фильтр, который будет использоваться для выяснения среды времени выполнения. Если средой является Development
, тогда ответ должен также включать трассировку стека. Добавьте переменную уровня класса для хранения экземпляра реализации IWebHostEnvironment
и конструктор:
private readonly IWebHostEnvironment _hostEnvironment;
public CustomExceptionFilterAttribute(IWebHostEnvironment hostEnvironment)
{
_hostEnvironment = hostEnvironment;
}
Код в обработчике OnException
проверяет тип сгенерированного исключения и строит соответствующий ответ. В случае среды Development
в сообщение ответа включается трассировка стека. Затем создается динамический объект, который содержит значения для отправки вызывающему запросу, и возвращается в IActionResult
. Вот модифицированный код метода:
public override void OnException(ExceptionContext context)
{
var ex = context.Exception;
string stackTrace = _hostEnvironment.IsDevelopment
? context.Exception.StackTrace :
string.Empty;
string message = ex.Message;
string error;
IActionResult actionResult;
switch (ex)
{
case DbUpdateConcurrencyException ce:
// Возвращается код HTTP 400.
error = "Concurrency Issue.";
actionResult = new BadRequestObjectResult(
new {Error = error, Message = message, StackTrace = stackTrace});
break;
default:
error = "General Error.";
actionResult = new ObjectResult(
new {Error = error, Message = message, StackTrace = stackTrace})
{
StatusCode = 500
};
break;
}
//context.ExceptionHandled = true; // Если убрать здесь комментарий,
// то исключение поглощается
context.Result = actionResult;
}
Если вы хотите, чтобы фильтр исключений поглотил исключение и установил код состояния в 200 (скажем, для регистрации ошибки в журнале, не возвращая ее клиенту), тогда поместите следующую строку перед установкой Result
(в предыдущем примере кода просто уберите комментарий):
context.ExceptionHandled = true;
Добавление фильтров в конвейер обработки
Фильтры можно применять к методам действий, контроллерам или глобально к приложению. Код "перед" фильтров выполняется снаружи вовнутрь (глобальный, контроллер, метод действия), в то время как код "после" фильтров выполняется изнутри наружу (метод действия, контроллер, глобальный).
На уровне приложения фильтры добавляются в методе ConfigureServices
класса Startup
. Откройте файл класса Startup.cs
и поместите в начало файла следующий оператор using
:
using AutoLot.Api.Filters;
Модифицируйте метод AddControllers
, добавив специальный фильтр:
services
.AddControllers(config => config.Filters.Add(
new CustomExceptionFilterAttribute(_env)))
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = null;
options.JsonSerializerOptions.WriteIndented = true;
})
.ConfigureApiBehaviorOptions(options =>
{
...
});
Тестирование фильтра исключений
Чтобы протестировать фильтр исключений, откройте файл WeatherForecastController.cs
и обновите метод действия Get
показанным ниже кодом:
[HttpGet]