Что такое 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()