FluentValidation для C# (.NET): полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое FluentValidation и зачем она нужна?

FluentValidation — это популярная библиотека для .NET, предназначенная для построения строгих и читаемых правил валидации бизнес-объектов. В отличие от встроенных атрибутов валидации (Data Annotations), FluentValidation предлагает подход, основанный на разделении логики валидации и модели данных. Это делает код чище, тестируемее и гибче.

Библиотека использует "текучий интерфейс" (Fluent Interface), что позволяет строить цепочки правил валидации с помощью точечной нотации. FluentValidation поддерживает сложные сценарии: каскадные валидации, условные правила, пользовательские валидаторы, работу со списками и вложенными объектами.

Зачем нужна FluentValidation?

  • Разделение ответственности: Правила валидации не засоряют модель данных.
  • Читаемость: Цепочки правил легко читать и поддерживать.
  • Гибкость: Легко создавать сложные, условные и составные правила.
  • Тестируемость: Валидаторы можно тестировать как обычные классы.
  • Интеграция: Отлично интегрируется с ASP.NET Core, MVC, Web API и другими фреймворками.

Установка FluentValidation

Установка библиотеки производится через менеджер пакетов NuGet. Выполните одну из следующих команд в консоли диспетчера пакетов или в терминале .NET CLI:

// Установка через Package Manager Console
Install-Package FluentValidation

// Установка через .NET CLI
dotnet add package FluentValidation

Для интеграции с ASP.NET Core дополнительно потребуется пакет:

dotnet add package FluentValidation.AspNetCore

Быстрый старт: пример кода

Предположим, у нас есть модель пользователя:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public int Age { get; set; }
    public string Password { get; set; }
    public string ConfirmPassword { get; set; }
}

Создадим валидатор, унаследовавшись от AbstractValidator<User>:

using FluentValidation;

public class UserValidator : AbstractValidator<User>
{
    public UserValidator()
    {
        RuleFor(user => user.Name)
            .NotEmpty().WithMessage("Имя обязательно для заполнения")
            .Length(2, 50).WithMessage("Имя должно быть от 2 до 50 символов");

        RuleFor(user => user.Email)
            .NotEmpty().WithMessage("Email обязателен")
            .EmailAddress().WithMessage("Некорректный формат email");

        RuleFor(user => user.Age)
            .InclusiveBetween(18, 120).WithMessage("Возраст должен быть от 18 до 120 лет");

        RuleFor(user => user.Password)
            .NotEmpty().WithMessage("Пароль обязателен")
            .MinimumLength(6).WithMessage("Пароль должен содержать минимум 6 символов");

        RuleFor(user => user.ConfirmPassword)
            .Equal(user => user.Password).WithMessage("Пароли не совпадают");
    }
}

Использование валидатора:

var validator = new UserValidator();
var user = new User
{
    Name = "A",
    Email = "invalid-email",
    Age = 15,
    Password = "123",
    ConfirmPassword = "456"
};

var result = validator.Validate(user);

if (!result.IsValid)
{
    foreach (var error in result.Errors)
    {
        Console.WriteLine($"Свойство: {error.PropertyName}, Ошибка: {error.ErrorMessage}");
    }
}

Основные методы FluentValidation

Ниже представлены ключевые методы библиотеки с подробным описанием и примерами.

1. RuleFor()

Что делает: Определяет правило валидации для конкретного свойства объекта. Это основной метод, с которого начинается построение цепочки правил.

Параметры: Выражение, указывающее на свойство (например, user => user.Name).

Пример:

RuleFor(user => user.Name).NotEmpty();

2. NotEmpty() / Empty()

Что делает: Проверяет, что значение свойства не пустое (не null, не пустая строка, не коллекция без элементов) или, наоборот, пустое.

Параметры: Нет.

Пример:

RuleFor(user => user.Name).NotEmpty().WithMessage("Имя не может быть пустым");
RuleFor(user => user.MiddleName).Empty().When(user => user.Age < 18).WithMessage("Отчество должно быть пустым для несовершеннолетних");

3. Length() / MinimumLength() / MaximumLength()

Что делает: Проверяет длину строки. Length(min, max) задает диапазон, MinimumLength(min) — минимальную длину, MaximumLength(max) — максимальную.

Параметры: Length(int min, int max), MinimumLength(int min), MaximumLength(int max).

Пример:

RuleFor(user => user.Name).Length(2, 50);
RuleFor(user => user.Password).MinimumLength(8);
RuleFor(user => user.Bio).MaximumLength(500);

4. InclusiveBetween() / ExclusiveBetween()

Что делает: Проверяет, что числовое значение находится в заданном диапазоне. InclusiveBetween включает границы, ExclusiveBetween — исключает.

Параметры: InclusiveBetween(IComparable from, IComparable to).

Пример:

RuleFor(user => user.Age).InclusiveBetween(18, 120);
RuleFor(user => user.Score).ExclusiveBetween(0, 100); // Значение должно быть от 0 до 100, не включая 0 и 100

5. EmailAddress()

Что делает: Проверяет, что строка является корректным email-адресом. Использует регулярное выражение для базовой валидации.

Параметры: Нет.

Пример:

RuleFor(user => user.Email).EmailAddress().WithMessage("Введите корректный email");

6. Equal() / NotEqual()

Что делает: Проверяет, что значение свойства равно (или не равно) указанному значению или другому свойству.

Параметры: Equal(T value) или Equal(Expression<Func<T, object>> expression) для сравнения с другим свойством.

Пример:

RuleFor(user => user.ConfirmPassword).Equal(user => user.Password);
RuleFor(user => user.Status).NotEqual("deleted");

7. Matches()

Что делает: Проверяет, что строка соответствует заданному регулярному выражению.

Параметры: Matches(string regex).

Пример:

RuleFor(user => user.PhoneNumber)
    .Matches(@"^\+?[1-9]\d{1,14}$").WithMessage("Некорректный номер телефона");

8. Must()

Что делает: Позволяет задать пользовательское условие валидации с помощью лямбда-выражения или функции. Это один из самых гибких методов.

Параметры: Must(Func<T, TProperty, bool> predicate) или Must(Func<TProperty, bool> predicate).

Пример:

RuleFor(user => user.Name)
    .Must(name => !string.IsNullOrEmpty(name) && char.IsUpper(name[0]))
    .WithMessage("Имя должно начинаться с заглавной буквы");

// Более сложный пример с доступом ко всему объекту
RuleFor(user => user.Age)
    .Must((user, age) => age >= 18 || user.HasParentalConsent)
    .WithMessage("Несовершеннолетние должны иметь согласие родителей");

9. When() / Unless()

Что делает: Условная валидация. When применяет правило, если условие истинно. Unless — если условие ложно.

Параметры: When(Func<T, bool> predicate, Action<IRuleBuilderOptions<T, TProperty>> action).

Пример:

RuleFor(user => user.Password)
    .NotEmpty()
    .When(user => user.IsRegistrationMode); // Пароль обязателен только при регистрации

RuleFor(user => user.DiscountCode)
    .NotEmpty()
    .Unless(user => user.TotalAmount < 100); // Код скидки не обязателен, если сумма меньше 100

10. Custom()

Что делает: Позволяет написать полностью кастомную логику валидации с доступом к контексту валидации. Вы можете добавлять ошибки вручную.

Параметры: Custom(Action<T, ValidationContext<T>> action).

Пример:

RuleFor(user => user).Custom((user, context) =>
{
    if (user.Age < 18 && string.IsNullOrEmpty(user.ParentEmail))
    {
        context.AddFailure("ParentEmail", "Для несовершеннолетних необходимо указать email родителя");
    }
});

11. SetValidator()

Что делает: Позволяет вложить один валидатор в другой для валидации сложных (вложенных) объектов.

Параметры: SetValidator(IValidator<TProperty> validator).

Пример:

public class AddressValidator : AbstractValidator<Address>
{
    public AddressValidator()
    {
        RuleFor(a => a.City).NotEmpty();
        RuleFor(a => a.ZipCode).Matches(@"^\d{5}$");
    }
}

public class UserValidator : AbstractValidator<User>
{
    public UserValidator()
    {
        RuleFor(user => user.Address).SetValidator(new AddressValidator());
    }
}

12. ForEach()

Что делает: Применяет правила валидации к каждому элементу коллекции (списка, массива).

Параметры: ForEach(Action<IRuleBuilderInitialCollection<T, TElement>> action).

Пример:

RuleFor(user => user.PhoneNumbers)
    .ForEach(phone =>
    {
        phone.NotEmpty().WithMessage("Номер телефона не может быть пустым");
        phone.Matches(@"^\d{10}$").WithMessage("Номер должен содержать 10 цифр");
    });

Реальный практический пример использования

Рассмотрим пример валидации заказа в интернет-магазине. У нас есть модели Order, OrderItem и Customer.

public class Customer
{
    public string FullName { get; set; }
    public string Email { get; set; }
    public string Phone { get; set; }
}

public class OrderItem
{
    public string ProductId { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
}

public class Order
{
    public Customer Customer { get; set; }
    public List<OrderItem> Items { get; set; }
    public string ShippingAddress { get; set; }
    public DateTime? DeliveryDate { get; set; }
    public string CouponCode { get; set; }
    public bool IsGift { get; set; }
}

Создадим валидаторы:

public class CustomerValidator : AbstractValidator<Customer>
{
    public CustomerValidator()                    

Рекомендаций