Валидация данных
Валидация входных данных: data annotations, ModelState и автоматические 400.
Суть: валидация проверяет, что присланные данные корректны (email — это email, имя не пустое, возраст в диапазоне) до того, как они попадут в бизнес-логику. В ASP.NET Core это делают атрибуты-аннотации, а
[ApiController]автоматически отклоняет невалидные запросы с кодом 400.
Никогда не доверяйте клиенту. Любой запрос может прийти с пустыми, слишком длинными или бессмысленными данными — случайно или со злым умыслом. Валидация — первая линия обороны бэкенда.
Аннотации на DTO
public class CreateUserDto
{
[Required]
[StringLength(50, MinimumLength = 2)]
public string Name { get; set; } = "";
[Required]
[EmailAddress]
public string Email { get; set; } = "";
[Range(18, 120)]
public int Age { get; set; }
}
С [ApiController] вам даже не нужно проверять вручную: если DTO невалиден, фреймворк сам вернёт 400 с описанием ошибок по полям, не заходя в метод.
Поток валидации
POST /api/users { "name": "", "age": 5 }
|
v
[Model binding] заполняет DTO
|
v
[Валидация по аннотациям]
|
ошибки? --да--> 400 + список ошибок (метод не вызван)
|
нет
v
[Ваш метод обрабатывает валидный DTO]
Как работает под капотом
После биндинга фреймворк прогоняет каждое свойство DTO через его атрибуты-валидаторы. Каждый атрибут (Required, Range) умеет проверить значение и при провале добавить сообщение в ModelState. Если ModelState.IsValid равно false и стоит [ApiController], срабатывает фильтр, который формирует ответ ValidationProblemDetails (400) и прерывает выполнение. Логику валидации можно расширять кастомными атрибутами или интерфейсом IValidatableObject.
# Аналог валидации DTO по правилам-аннотациям
import re
def validate(dto):
errors = {}
name = dto.get("name", "")
if not (2 <= len(name) <= 50):
errors["name"] = "длина 2..50"
email = dto.get("email", "")
if not re.match(r"^[^@]+@[^@]+\.[^@]+$", email):
errors["email"] = "невалидный email"
age = dto.get("age", 0)
if not (18 <= age <= 120):
errors["age"] = "возраст 18..120"
return errors
errs = validate({"name": "", "email": "bad", "age": 5})
if errs:
print("400 Bad Request:", errs) # метод не выполнится
else:
print("OK, обрабатываем")
Попробуй сам ▶ — функция собирает ошибки по полям и «возвращает 400», как делает [ApiController]. Подставьте валидные данные — увидите «OK».
Частые ошибки
- Проверять данные только на фронтенде. Фронт можно обойти — бэкенд обязан валидировать сам.
- Валидировать вручную при наличии
[ApiController]. Дублирование: фреймворк уже вернёт 400 за вас. - Бизнес-правила в аннотациях. «email уникален в БД» — это не аннотация, а проверка в сервисе/БД.
Best practices
- Простые правила (формат, длина, диапазон) — аннотациями на DTO.
- Сложные/доменные правила — в слое сервиса, с понятными ответами 400/409.
- Для богатой валидации рассмотрите FluentValidation — отдельные классы-правила, удобно тестировать.
Три уровня проверки данных
Полезно разделять валидацию по уровням. Синтаксическая (формат) — это data annotations: [Required], [EmailAddress], [StringLength], [Range], [RegularExpression]. Они проверяют форму данных и работают автоматически с [ApiController]. Семантическая (бизнес-правила одной модели) — реализуется через IValidatableObject или кастомные атрибуты: например «дата окончания позже даты начала». Доменная (правила, требующие БД или контекста) — живёт в сервисе: «email уникален», «на складе достаточно товара».
Смешивать уровни вредно. Доменную проверку нельзя засунуть в аннотацию — у атрибута нет доступа к БД и он сработает не вовремя. И наоборот, формат не стоит проверять руками в сервисе, дублируя то, что фреймворк делает сам. Чёткое распределение делает код понятным: по DTO видно формат, по сервису — бизнес-правила.
FluentValidation и сообщения об ошибках
Когда правил много или они сложные, аннотации становятся тесными. Тогда берут FluentValidation: для каждого DTO пишут отдельный класс-валидатор с цепочками правил (RuleFor(x => x.Email).NotEmpty().EmailAddress()). Плюсы — правила вынесены из модели, легко тестируются и читаются, поддерживают условную логику и асинхронные проверки. Это популярный выбор в средних и крупных проектах.
Отдельная тема — качество сообщений об ошибках. Клиенту (и человеку, и фронтенду) нужны понятные, привязанные к полю сообщения: не «invalid input», а «email: некорректный формат». Формат ProblemDetails с картой ошибок по полям, который ASP.NET Core возвращает автоматически, как раз решает это. Хорошая валидация — не только «отклонить плохое», но и «понятно объяснить, что именно не так».
Итог: валидация защищает бэкенд от мусорных данных, а [ApiController] автоматизирует возврат 400. Дальше — как документировать API, чтобы им было удобно пользоваться.