Содержание
Фильтры в ASP.NET Core MVC позволяют выполнять код до или после определенных этапов обработки запроса. Обычно, фильтры в ASP.NET Core MVC реализуются как атрибутов для классов и методов. Для чего нужны фильтры в ASP.NET Core MVC? Их использование в проектах самое широкое — от авторизации пользователя в системе до кэширования ответов и предотвращения дублирования кода. В этой части мы рассмотрим вопросы связанные с использованием различных фильтров в ASP.NET Core MVC
Как работают фильтры
Фильтры выполняются в конвейере вызова действий ASP.NET Core, который иногда называют конвейером фильтров (filter pipeline). Конвейер фильтра выполняется после того, как ASP.NET Core выбирает действие для выполнения, то есть, положение конвейера фильтров во всей цепочке обработки запроса можно представить следующим образом:
Прежде чем заработают какие-либо фильтры, запрос проходит через конвейер обработки запроса, включая компоненты middleware маршрутизации и уже после выбора контроллера и действия в нем запускается конвейер фильтров. При этом, в ASP.NET Core выделяются пять групп фильтров, которые выполняются в определенном порядке:
- Фильтры авторизации — определяют, авторизован ли пользователь для выполнения текущего действия. Если пользователь не авторизован, то фильтр этой группы завершает обработку запроса.
- Фильтры ресурсов — выполняются после фильтров авторизации. Его метод
OnResourceExecuting()
выполняется до всех остальных фильтров и до привязки модели, а его методOnResourceExecuted()
выполняется после всех остальных фильтров. Эта группа фильтров используется, в том числе, для выполнения задач кэширования. - Фильтры действий — применяется только к действиям (методам) контроллера, запускается после фильтра ресурсов как до, так и после выполнения метода контроллера. Эти фильтры можно использовать, например, для предотвращения дублирования кода в контроллере.
- Фильтры исключений — определяют действия в отношении необработанных исключений
- Фильтры результатов действий — фильтр применяется к результатам методов контроллера, выполняется как до, так и после получения результата. Этот тип фильтров можно использовать, например, для того, чтобы добавить в ответ свои заголовки.
Таким образом, общая схема работы ASP.NET Core MVC от момента поступления запроса до отправки ответа пользователю выглядит следующим образом:
Определение фильтров в ASP.NET Core MVC
Фильтры поддерживают два варианта реализации — синхронную и асинхронную. В зависимости от того, какого типа фильтр мы хотим определить, выбирается тот или иной интерфейс для реализации:
Тип фильтра | Синхронная реализация | Асинхронная реализация |
Фильтр авторизации | IAuthorizationFilter |
IAsyncAuthorizationFilter |
Фильтр ресурсов | IResourceFilter |
IAsyncResourceFilter |
Фильтр действий | IActionFilter |
IAsyncActionFilter |
Фильтр исключений | IExceptionFilter |
IAsyncExceptionFilter |
Фильтр результатов | IResultFilter |
IAsyncResultFilter |
IAlwaysRunResultFilter |
IAsyncAlwaysRunResultFilter |
Также, классы фильтров нередко наследуют класс Attribute
, благодаря которому мы можем применять фильтры как обычные атрибуты. Чтобы продемонстрировать работу фильтра, далее мы создадим фильтр действий.
Разработка простейшего фильтра действий
Создадим новое приложение ASP.NET Core MVC и добавим в папку Models следующую модель:
public class User { [Required] public string Name { get; set; } [Required] public string Email { get; set; } [Required] public string Password { get; set; } [Phone] public string Phone { get; set; } }
К этой модели мы применили атрибуты валидации и, в дальнейшем, будем осуществлять валидацию модели на стороне сервера. Также изменим код представления Views/Home/Index.cshtml на следующий:
@model User @{ ViewData["Title"] = "Home Page"; } @using(Html.BeginForm(FormMethod.Post)) { @Html.EditorForModel(Model)<br/> @Html.TextBox("button", "Отправить", new { type="submit" }) }
и, наконец, допишем методы контроллера HomeController
:
public class HomeController : Controller { private readonly ILogger<HomeController> _logger; public HomeController(ILogger<HomeController> logger) { _logger = logger; } [HttpGet] public IActionResult Index() { return View(); } [HttpPost] public IActionResult Index(User user) { if (ModelState.IsValid == false) return BadRequest(ModelState); //тут действия по добавлению пользователя в БД return View(); } }
В рабочем приложении мы увидим следующую форму добавления нового пользователя:
Если пользователь вводит некорректные данные, то мы увидим список ошибок, например:
Проверка валидности модели, как в методе Index()
:
if (ModelState.IsValid == false) return BadRequest(ModelState);
может встречаться в контроллере несколько раз. Например, такая проверка может потребоваться, если мы, в дальнейшем, реализуем метод обновления сведений о пользователе. Более того, сам процесс проверки модели может занимать не 2 строки — мы можем проводить дополнительный анализ свойства ModelState
и т.д. Чтобы не дублировать такой код по всему контроллеру — вынесем проверку модели в отдельный фильтр действий.
Реализация фильтра действий в виде атрибута
Создадим в проекте папку Filters
и добавим в неё файл UserCheckFilter.cs
в котором разместим одноименный класс следующего содержания:
public class UserCheckFilter : Attribute, IActionFilter { public void OnActionExecuted(ActionExecutedContext context) { //throw new NotImplementedException(); } public void OnActionExecuting(ActionExecutingContext context) { if (context.ModelState.IsValid == false) context.Result = new BadRequestObjectResult(context.ModelState); } }
Чтобы наш класс в дальнейшем можно было использовать как обычный атрибут, он наследует класс Attribute
. Также, так как мы пишем фильтр действий, то наш класс должен реализовывать интерфейс IActionFilter
или его асинхронную версию. В классе мы реализуем методы интерфейса, а именно, метод OnActionExecuting()
. Этот метод выполняется перед выполнением действия. В параметрах метода передается объект класса ActionExecutingContext
, содержащий информацию о действии и, в том числе, о состоянии модели, о данных маршрута, контекст запроса и т.д. Внутри метода мы проверяем состояние модели:
if (context.ModelState.IsValid == false)
и, если в модели обнаружены ошибки валидации, то записываем в результат объект BadRequestObjectResult
. То есть, если происходит ошибка валидации, то метод контроллера далее выполняться не будет и приложение сразу выдаст нам код 400 и все обнаруженные ошибки валидации модели. Применим наш фильтр.
Применение фильтра действий к методу контроллера
Так как мы наследовали наш класс фильтра от класса Attribute
, то теперь мы можем его применить в контроллере следующим образом:
[HttpPost] [UserCheckFilter] public IActionResult Index(User user) { //эта проверка нам больше не нужна //if (ModelState.IsValid == false) // return BadRequest(ModelState); _logger.LogInformation("Зашли в метод Index"); return View(); }
для наглядности, если начнется выполнение метода, то в лог будет выведено сообщение «Зашли в метод Index». Проверим результат. Отправляя пустую форму, мы должны получить код 400:
Как можно видеть, сообщения в логе не появилось, но, зато в браузере появились сообщения об обнаруженных ошибках в модели. В случае, если форма будет заполнена корректно, консоль будет выглядеть следующим образом:
Итого
Фильтры позволяют выполнять определенные действия до или после какого-либо этапа обработки запроса. В зависимости от назначения, фильтры подразделяются на пять групп и выполнятся в определенной последовательности. Для создания фильтра необходимо реализовать один из интерфейсов, описывающий синхронный или асинхронный фильтр.