Обзор библиотеки FluentValidation. Часть 2. Коллекции

91f1af263bd02073d9b2f772a615a9dd.png

Правила валидаций для элементов коллекций можно описать тремя способами:

  1. Метод RuleForEach

  2. Комбинация методов RuleForEach + ChildRules

  3. Комбинация методов 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 + ChildRulesChildRules похож на 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, для успешного прохождения валидации.

Автор рекомендует использовать этот способ из трёх, поскольку он чище, легче читается, гибче. Есть возможность указать валидации одновременно как для коллекции, так и для её элементов.

© Habrahabr.ru