Overview of the FluentValidation library. Part 2. Collections
Validation rules for collection elements can be described in three ways:
Method
RuleForEach
Combination of methods
RuleForEach
+ChildRules
Combination of methods
RuleFor
+ForEach
(the author of the library recommends using this method)
When applying the method RuleFor
on a collection, subsequent validators specified in the chain will validate the collection, and not its elements, this is important.
Let's consider the first method, method RuleForEach
:
// Модель клиента
public class Customer
{
// Адреса
public List<string>? Addresses { get; set; }
}
// Валидатор для модели клиента
public class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
// Описываем правило, которое говорит о том, что каждый элемент
// коллекции Addresses должен быть не равен null
RuleForEach(customer => customer.Addresses)
.NotNull();
}
}
The code above will call the property validator NotNull
for each element of the collection.
You can also combine the method RuleForEach
with method SetValidator
when the collection contains complex objects (classes, structures, etc.):
// Модель заказа
public class Order
{
// Общая стоимость
public decimal TotalCost { get; set; }
}
// Валидатор для модели заказа
public class OrderValidator : AbstractValidator<Order>
{
public OrderValidator()
{
// Метод GreaterThan говорит о том, что значение свойства TotalCost
// должно быть больше 0
RuleFor(order => order.TotalCost).GreaterThan(0);
}
}
// Модель клиента
public class Customer
{
// Заказы
public List<Order>? Orders { get; set; }
}
// Модель валидатора для модели клиента
public class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
// Переиспользуем валидатор OrderValidator на каждый элемент
// коллекции Orders через вызов метода SetValidator
RuleForEach(customer => customer.Orders)
.SetValidator(new OrderValidator());
}
}
Optionally, you can filter collection elements using the method Where
, which will be subject to validation. Method Where
must be directly called after the method RuleForEach
:
// Модель заказа
public class Order
{
// Теперь поле имеет тип decimal?
public decimal? TotalCost { get; set; }
// Другие свойства...
}
public class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
// Фильтрация элементов коллекции через метод Where для последующей их валидации.
// Валидируются только те элементы, у которых значение свойства TotalCost
// не равно null
RuleForEach(customer => customer.Orders)
.Where(order => order.TotalCost is not null)
.SetValidator(new OrderValidator());
}
}
Let's consider the second method, a combination of methods RuleForEach
+ ChildRules
:
As an alternative to combination RuleForEach
+ SetValidator
you can use a combination RuleForEach
+ ChildRules
. ChildRules
look like SetValidator
but differs in that validations are described not in a separate validator, but directly in the same validator (inline validator):
// Валидатор для модели клиента
public class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
RuleForEach(customer => customer.Orders)
.ChildRules(validator =>
{
// Метод GreaterThan говорит о том, что значение свойства TotalCost
// должно быть больше 0
validator.RuleFor(order => order.TotalCost).GreaterThan(0);
});
}
}
Last third method, combination RuleFor
+ ForEach
:
// Валидатор для модели клиента
public class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
// Коллекция Orders должна быть не равна null
RuleFor(customer => customer.Orders)
.NotNull()
.ForEach(rule =>
{
// Значение свойства TotalCost должно быть
// больше 0 (у каждого элемента коллекции Orders)
rule.Must(order => order.TotalCost > 0);
});
}
}
Method Must
this is a universal validator that allows you to specify any custom predicate (which was not included out of the box), in this case it requires that the property value TotalCost
there were more 0
for successful validation.
The author recommends using this method out of the three because it is cleaner, easier to read, and more flexible. It is possible to specify validations simultaneously for both the collection and its elements.