All streams
Search
Write a publication
Pull to refresh
0
0
Владислав Миккоев @vludlss

Бэкенд разработчик

Send message

Про порядок выполнения правил из документации:
Из раздела Dependent Rules:

By default, all rules in FluentValidation are separate and cannot influence one another. This is intentional and necessary for asynchronous validation to work. However, there may be some cases where you want to ensure that some rules are only executed after another has completed.

Тоесть тут про то, что оно может выполниться как в том же порядке в котором описаны, так и в любом другом, гарантий никаких нет. А если нужны гарантии, нужно использовать DependentRules метод.

Про порядок выполнения валидаторов из документации:
Из раздела Setting the Cascade mode:

public class PersonValidator : AbstractValidator<Person> {
  public PersonValidator() {
    RuleFor(x => x.Surname).NotNull().NotEqual("foo");
  }
}

This will first check whether the Surname property is not null and then will check if it’s not equal to the string “foo”. If the first validator (NotNull) fails, then by default, the call to NotEqual will still be invoked.

Валидаторы вызываются в том порядке в котором определены, не важно отработал предыдущий валидатор или нет, вызывается каждый.

Ничего не мешает сделать такую запись (MustAsync + DependentRules):

RuleFor(customer => customer.Address.Actual)
  .MustAsync(async (actual, token) =>
  {
    // Какая-то долгая асинхронная операция
  })
  .DependentRules(() =>
  {
    // Какой-то RuleFor на какое-то свойство
  });

Асинхронность это про неблокирование главного потока, поэтому это будет работать корректно.

Интересно было посмотреть, спасибо, не знал про такую.

Думаю что такая статья может выйти только после всех обзоров по библиотеке, как резюме. Сравнение FluentValidation с System.ComponentModel.DataAnnotations.

Если я вас правильно понял, то ответы

На первый вопрос:

  1. Объединение правил нескольких валидаторов, нацеленных на один и тот же тип модели: https://docs.fluentvalidation.net/en/latest/including-rules.html#

  2. Можно выбрать конкретное свойство, которое должно быть провалидировано из всех, что описаны в валидаторе: https://docs.fluentvalidation.net/en/latest/specific-properties.html

  3. Объединение правил в именованную группу, которую можно опционально вызывать при выполнении метода Validate: https://docs.fluentvalidation.net/en/latest/rulesets.html

На второй вопрос:
https://habr.com/ru/articles/799173/

https://docs.fluentvalidation.net/en/latest/dependentrules.html

https://docs.fluentvalidation.net/en/latest/cascade.html

Интересные вопросы для отдельной статьи, не в рамках этой серии. Я не думаю, что стоит отказываться продолжать эту серию, это может быть полезно разработчикам уровнем по меньше чем у вас.

Вы можете предложить как сделать следующие части лучше. Что-то добавить быть может?

В первой части я писал, что серия статей называется обзорами, т. к. это не урок и неполноценный перевод документации. Как минимум на русском языке, больше примеров, иногда может рассматриваться то, чего нет в документации.

Учёл в следующих частях.

Принял, выйдет следующей частью.

DependentRules() как один из способов, но есть другой, который больше подойдёт в вашем случае, использование метода Cascade(CascadeMode.Stop):

public class CustomerValidator : AbstractValidator<Customer>
{
  public CustomerValidator()
  {
    RuleFor(customer => customer.Forename)
      .Cascade(CascadeMode.Stop) // Вызов здесь
      .NotNull()
      .MustAsync(async (forename, token) =>
      {
        await Task.Delay(TimeSpan.FromSeconds(3), token);
        return forename!.Count() > 4;
      });
  }
}

Если NotNull зафейлится, MustAsync не будет выполнен (с последующими валидаторами в правиле аналогичное поведение).

Если надо много раз дублировать метод Cascade на каждый вызов метода RuleFor, то есть более сокращённая запись, присвоение RuleLevelCascadeMode = CascadeMode.Stop:

public class CustomerValidator : AbstractValidator<Customer>
{
  public CustomerValidator()
  {
    // Присвоение здесь, будет работать для всех правил в CustomerValidator
    RuleLevelCascadeMode = CascadeMode.Stop;

    RuleFor(customer => customer.Forename)
      .NotNull()
      .MustAsync(async (forename, token) =>
      {
        await Task.Delay(TimeSpan.FromSeconds(3), token);
        return forename!.Count() > 4;
      });
  }
}

Есть ещё что сказать, но это всё тема отдельной части статьи. Если в документации искать, то оно тут: https://docs.fluentvalidation.net/en/latest/cascade.html

В таком случае лучше искать ответ не в библиотеке, а попробовать переписать код. Но вообще, на крайний случай, можно попробовать так, если известно какие типы туда могут залететь:

public class Order { ... }
public class OrderValidator : AbstractValidator<Order> { ... }
public class Address { ... }
public class AddressValidator : AbstractValidator<Address> { ... }

// Модель клиента
public class Customer
{
  // Объекты разных типов
  public List<object>? Objects { get; set; }
}

// Валидатор для модели клиента
public class CustomerValidator : AbstractValidator<Customer>
{
  public CustomerValidator()
  {
    RuleFor(customer => customer.Objects.OfType<Address>().ToList())
      .ForEach(address =>
      {
        address.SetValidator(new AddressValidator());
      })
      .OverridePropertyName(nameof(Customer.Objects));

    RuleFor(customer => customer.Objects.OfType<Order>().ToList())
      .ForEach(order =>
      {
        order.SetValidator(new OrderValidator());
      })
      .OverridePropertyName(nameof(Customer.Objects));
  }
}

Кто-то возможно знает, что есть методы Transform, TransformForEach для преобразований, но в 12 версии они будут удалены, здесь в коде использован один из двух доступных способов (RuleFor + OverridePropertyName). https://github.com/FluentValidation/FluentValidation/issues/2072

Локализация должна будет выйти отдельной частью. Спасибо за подробный фидбек.

Мне кажется это плохой вариант, т. к. названия свойств указаны литералом, а не через nameof хотя бы например (после ренейма свойства будет расхождение в нейме), также тут нет поддержки локализации при том что условия валидаторов из коробки, где это реализовано уже.

Общие модели, маппинги, валидации можно выделить в NuGet пакет, дополнительно добавив туда методы расширения для удобной регистрации в DI всего сразу в отдельных проектах. Единственной проблемой от этого подхода будет то, что нужно будет обновлять пакет в каждом отдельном приложении при его изменении (не всегда правда, а только когда требуется это обновление для корректной работы приложения). Также надо правильно сортировать классы внутри проекта, чтобы было интуитивно понятно что с чем вяжется и как быстро это находить.

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

В библиотеке предусмотрены методы перегрузки для валидаторов из коробки, позволяющих обратиться к другим значениям модели, помимо валидируемого. Про это здесь не было упомянуто, т. к. как вы и сказали - это первая часть.

Валидаторы обычно хранятся в какой-то папке в каком-то одном месте, на один тип модели один валидатор. В библиотеке предусмотрен метод расширения для регистрации валидаторов по указанной сборке. Также предусмотрен функционал для работы с классами, которые используют наследование (переиспользование правил валидаций базового класса/интерфейса). Но про это не было упомянуто в этом обзоре, т. к. он стартовый.

Information

Rating
Does not participate
Location
Петрозаводск, Карелия, Россия
Date of birth
Registered
Activity

Specialization

Backend Developer
Middle
C#
.NET
ASP.Net
Entity Framework