Валидация: DataAnnotations и FluentValidation

↑ 3.3 Web API: контроллеры, Minimal API, валидация · ← Предыдущая · → Следующая

Обязательно~3 мин чтенияУровень: middle

Зачем это на собесе

Валидацию входа проверяют почти на каждом собеседовании: где она, какой формат ответа и где не хватает атрибутов.

Объяснение

DataAnnotations

public record CreateOrderRequest(
    [property: Required, StringLength(100)] string Customer,
    [property: Range(1, 1000)] int Quantity,
    [property: EmailAddress] string Email);

С [ApiController] невалидная модель автоматически даёт 400 с ValidationProblemDetails. Атрибуты просты, но плохо выражают сложные и контекстные правила.

FluentValidation

public class CreateOrderValidator : AbstractValidator<CreateOrderRequest>
{
    public CreateOrderValidator(IProductCatalog catalog)
    {
        RuleFor(x => x.Customer).NotEmpty().MaximumLength(100);
        RuleFor(x => x.Quantity).InclusiveBetween(1, 1000);
        RuleFor(x => x.Email).EmailAddress();
        RuleFor(x => x.ProductId).MustAsync(catalog.ExistsAsync).WithMessage("Товар не найден");
    }
}
 
builder.Services.AddValidatorsFromAssemblyContaining<CreateOrderValidator>();
КритерийDataAnnotationsFluentValidation
Где правилаатрибуты в моделиотдельный класс
Условные и зависимые правилаограниченноWhen, Must, DependentRules
Асинхронные проверки, DIнетда
Локализацияресурсыресурсы + гибкость

В Minimal API

orders.MapPost("/", async (CreateOrderRequest req, IValidator<CreateOrderRequest> v, IOrderService s) =>
{
    var result = await v.ValidateAsync(req);
    if (!result.IsValid) return Results.ValidationProblem(result.ToDictionary());
    return Results.Created(...);
});

Можно вынести в endpoint filter (см. Фильтры: action, exception, resource, endpoint filters).

Нюансы и подводные камни

  • Валидация формы (формат, диапазоны) ≠ бизнес-инварианты: вторые проверяются в доменной логике.
  • Не доверяйте клиентской валидации.
  • Не возвращайте детали, раскрывающие внутренности (SQL, стек).
  • Проверки уникальности через валидатор не заменяют unique-индекс: возможны гонки.
  • Nullable reference types не заменяют Required для JSON: null может прийти.

Практика

  1. Реализуйте валидатор с зависимыми правилами и локализацией.
  2. Сделайте generic endpoint filter, вызывающий IValidator<T>.
  3. Убедитесь, что формат ошибок единый (ValidationProblemDetails).

Вопросы с ответами

Связанные темы