FlatValidator. Эффективная проверка входных данных в фильтрах Minimal API
Начну с риторического вопроса - что может быть увлекательнее процесса изучения новой технологии, когда понимание происходит "на лету", а клеточки мозга воспринимают новые знания как нечто знакомое, но слегка подзабытое?
Ответ - да, в общем-то, много чего! 🤷 Хотя, технология, несложная в своём освоении, определённо вызывает позитив.
В этой статье хотелось бы рассмотреть возможность подключения пакета FlatValidator в проект с Minimal API. Предлагаю, не углубляться сильно в детали самого пакета, их можно найти на страницах проекта, сконцентрируемся в первую очередь на интеграции.
Шаг в прошлое. Minimal API впервые был заявлен в выпуске .NET 6.0, это гибкая программная техника, предназначенная для обслуживания HTTP-роутинга. Появление Minimal API подвесило в воздух некоторое ощущение, что время контроллеров неумолимо истекает. И как всегда, новые времена создают новые вызовы.
Если вы обо всём этом ничего не слышали, не беда, дальнейшее обсуждение внесёт определённую ясность.
Фильтры конечных точек известны главным образом именно в рамках техники Minimal API, хотя сейчас они доступны в том числе и для MVC, и для Razor Pages. На мой взгляд, фильтры - это нечто среднее между самим обработчиком запроса к конечной точке и middleware. Термин 'middleware' труднопереводим на русский язык, скажем так - это программный модуль, который встраивается в цепочку обработки HTTP-запроса. Если таких модулей несколько, они выполняются последовательно, один за другим. К чему я это рассказываю? Раньше для валидации данных часто использовали middleware. Теперь появился более удобный инструмент - `IEndpointFilter`.
Программный код `IEndpointFilter` выглядит примерно так:
Что здесь происходит? Фильтр пропускает обработку запроса к конечной точке (вызывая `next()`) и на выходе заменяет в response все фрагменты `vodka` на `pineapple juice`.
Давайте подключим этот фильтр к нашему приложению с Minimal API.
Очевидно, что результатом работы нашего фильтра будет замена на выходе фразы `I like to drink vodka!` на `I like to drink pineapple juice!`. Такой вот нежданчик для любителя `vodka` © "А что делать? Пьянству бой."
Итак, теперь, когда с фильтрами для Minimal API немного разобрались, давайте перейдём к более реальному примеру и подключим пакет FlatValidator, он поможет проверять наши данные профессионально.
Через NuGet инсталлируем основной пакет:
В документации к пакету написано, что использование валидатора предусмотрено в двух режимах, ориентировочно названных inline и derived.
В inline-режиме правила проверки задаются прямо в точке валидации. Это может быть удобно, поскольку оставляет возможность видеть логику проверки "по месту".
С другой стороны, в солидных проектах такой подход может не согласовываться с концептуальными требованиями, где бизнес-логика, например, отделена и находится в строго отведённых местах. В этом случае, остаётся возможность пронаследовать класс `FlatValidator` и поместить его в любое подходящее место.
Позволю себе взять несколько изменённый пример из документации:
Функции `ValidIf` и `ErrorIf` позволяют задавать правила валидации. Их может быть сколько угодно много в одном валидаторе. `TypedResults.ValidationProblem` - это часть .NET 6+, упрощающая возврат ошибок в HTTP-response.
Само приложение в концепции inline-режима мы могли бы написать так:
Если inline-стиль вам не подходит, используйте вариант с наследованием.
Эта запись выглядит опрятнее. Ясно, что классы `Todo` и `TodoValidator` должны бы лежать каждый в своём файле.
Что ж, до реализации фильтра валидации, заявленного в заголовке статьи, нам остался один шаг. Просто логику вызова самого валидатора перенесём непосредственно внутрь фильтра.
Заметили? Тело конечной точки `MapPost("/todos")` вообще избавилось от каких-либо проверок, логика теперь в фильтре. Но не забываем про `.AddEndpointFilter<ValidationFilter<Todo>>`. А, поскольку фильтр у нас generic, он подойдёт для модели любого типа, достаточно реализовать сам класс валидатора и зарегистрировать его в `IServiceCollection`.
Впрочем, с этим тоже проблем нет. Если вы хотите автоматизировать регистрацию всех ваших валидаторов, используйте вспомогательный пакет FlatValidator.DependencyInjection.
Вообще же, если вы решите поближе познакомиться с возможностями пакета FlatValidator, рекомендую в первую очередь заглянуть на страничку проекта, там найдётся документация и лежат вполне годные примеры.