Обзор библиотеки FluentValidation. Часть 2. Коллекции
Правила валидаций для элементов коллекций можно описать тремя способами:
Метод
RuleForEach
Комбинация методов
RuleForEach
+ChildRules
Комбинация методов
RuleFor
+ForEach
(этот способ рекомендует использовать автор библиотеки)
При применении метода RuleFor
на коллекцию, последующие валидаторы, указанные в цепочке, будут валидировать именно коллекцию, а не её элементы, это важно.
Рассмотрим первый способ, метод RuleForEach
:
// Модель клиента
public class Customer
{
// Адреса
public List? Addresses { get; set; }
}
// Валидатор для модели клиента
public class CustomerValidator : AbstractValidator
{
public CustomerValidator()
{
// Описываем правило, которое говорит о том, что каждый элемент
// коллекции Addresses должен быть не равен null
RuleForEach(customer => customer.Addresses)
.NotNull();
}
}
Код выше будет вызывать валидатор свойства NotNull
для каждого элемента коллекции.
Вы также можете комбинировать метод RuleForEach
с методом SetValidator
, когда коллекция содержит сложные объекты (классы, структуры и т. д.):
// Модель заказа
public class Order
{
// Общая стоимость
public decimal TotalCost { get; set; }
}
// Валидатор для модели заказа
public class OrderValidator : AbstractValidator
{
public OrderValidator()
{
// Метод GreaterThan говорит о том, что значение свойства TotalCost
// должно быть больше 0
RuleFor(order => order.TotalCost).GreaterThan(0);
}
}
// Модель клиента
public class Customer
{
// Заказы
public List? Orders { get; set; }
}
// Модель валидатора для модели клиента
public class CustomerValidator : AbstractValidator
{
public CustomerValidator()
{
// Переиспользуем валидатор OrderValidator на каждый элемент
// коллекции Orders через вызов метода SetValidator
RuleForEach(customer => customer.Orders)
.SetValidator(new OrderValidator());
}
}
Опционально можно фильтровать элементы коллекции через метод Where
, которые будут подвергнуты валидации. Метод Where
должен непосредственно вызываться после метода RuleForEach
:
// Модель заказа
public class Order
{
// Теперь поле имеет тип decimal?
public decimal? TotalCost { get; set; }
// Другие свойства...
}
public class CustomerValidator : AbstractValidator
{
public CustomerValidator()
{
// Фильтрация элементов коллекции через метод Where для последующей их валидации.
// Валидируются только те элементы, у которых значение свойства TotalCost
// не равно null
RuleForEach(customer => customer.Orders)
.Where(order => order.TotalCost is not null)
.SetValidator(new OrderValidator());
}
}
Рассмотрим второй способ, комбинация методов RuleForEach
+ ChildRules
:
Как альтернативу комбинации RuleForEach
+ SetValidator
можно использовать комбинацию RuleForEach
+ ChildRules
. ChildRules
похож на SetValidator
, но отличается тем, что валидации описываются не в отдельном валидаторе, а прямо в этом же валидаторе (inline validator):
// Валидатор для модели клиента
public class CustomerValidator : AbstractValidator
{
public CustomerValidator()
{
RuleForEach(customer => customer.Orders)
.ChildRules(validator =>
{
// Метод GreaterThan говорит о том, что значение свойства TotalCost
// должно быть больше 0
validator.RuleFor(order => order.TotalCost).GreaterThan(0);
});
}
}
Последний третий способ, комбинация RuleFor
+ ForEach
:
// Валидатор для модели клиента
public class CustomerValidator : AbstractValidator
{
public CustomerValidator()
{
// Коллекция Orders должна быть не равна null
RuleFor(customer => customer.Orders)
.NotNull()
.ForEach(rule =>
{
// Значение свойства TotalCost должно быть
// больше 0 (у каждого элемента коллекции Orders)
rule.Must(order => order.TotalCost > 0);
});
}
}
Метод Must
это универсальный валидатор, который позволяет указать любой кастомный предикат (которого не было из коробки), в данном случае он требует, чтобы значение свойства TotalCost
было больше 0
, для успешного прохождения валидации.
Автор рекомендует использовать этот способ из трёх, поскольку он чище, легче читается, гибче. Есть возможность указать валидации одновременно как для коллекции, так и для её элементов.