Обзор библиотеки FluentValidation. Часть 3. Сообщения об ошибках

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Переопределение сообщений.

Вы можете переопределять сообщение об ошибке по умолчанию у конкретного валидатора, с помощью метода расширения WithMessage:

// Модель клиента
public class Customer
{
  // Фамилия
  public string? Surname { get; set; }
}

// Валидатор для модели клиента
public class CustomerValidator : AbstractValidator<Customer>
{
  public CustomerValidator()
  {
    // Через вызов метода WithMessage переопределяем сообщение об ошибке у
    // валидатора NotNull и NotEqual
    RuleFor(customer => customer.Surname)
      .NotNull()
        .WithMessage("Переопределённое сообщение для валидатора NotNull")
      .NotEqual("Фамилия")
        .WithMessage("Переопределённое сообщение для валидатора NotEqual");
  }
}

Обратите внимание, что я выделил табуляцией вызов метода расширения WithMessage, чтобы подчеркнуть то, что он меняет сообщение предыдущего валидатора, не затрагивая другие.

Выполняем:

static void Main(string[] args)
{
  var customer = new Customer { Surname = null };
  var validator = new CustomerValidator();
  var result = validator.Validate(customer);
  Console.WriteLine(result.ToString(Environment.NewLine));
  // Выведет > "Переопределённое сообщение для валидатора NotNull"

  // Меняем данные объекта customer, для получения сообщения об ошибке
  // от валидатора NotEqual
  customer.Surname = "Фамилия";
  result = validator.Validate(customer);
  Console.WriteLine(result.ToString(Environment.NewLine));
  // Выведет > "Переопределённое сообщение для валидатора NotEqual"
}

У метода расширения WithMessage есть перегрузка на получение в параметрах валидируемого объекта (не валидируемого свойства, а именно объекта, который валидируется), для вставки его данных в своё кастомное сообщение:

// Модель клиента
public class Customer
{
  public string? Surname { get; set; }
  public decimal Discount { get; set; }
}

// Валидатор для модели клиента
public class CustomerValidator : AbstractValidator<Customer>
{
  public CustomerValidator()
  {
    // В методе WithMessage получаем доступ ко всем данным валидируемого объекта
    RuleFor(customer => customer.Surname)
      .NotNull()
        .WithMessage(customer => $"Ссылаемся на значения других свойств: Фамилия {customer.Surname}, Скидка {customer.Discount}");
  }
}

Всего у метода расширения WithMessage есть 3 перегрузки:

// Можно указать простое сообщение об ошибке
IRuleBuilderOptions<T, TProperty> WithMessage<T, TProperty>(this IRuleBuilderOptions<T, TProperty> rule, string errorMessage)
// Можно получить доступ ко всем данным валидируемого объекта
IRuleBuilderOptions<T, TProperty> WithMessage<T, TProperty>(this IRuleBuilderOptions<T, TProperty> rule, Func<T, string> messageProvider)
// Можно получить доступ ко всем данным валидируемого объекта, доступ к валидируемому свойству
IRuleBuilderOptions<T, TProperty> WithMessage<T, TProperty>(this IRuleBuilderOptions<T, TProperty> rule, Func<T, TProperty, string> messageProvider)

Заполнители (placeholders).

В каждом валидаторе (в примере выше это NotNull и NotEqual) присутствует свой набор плейсхолдеров (placeholder — заполнитель), с помощью которых можно опционально дополнять свои кастомные сообщения какими-то предопределёнными данными в рантайме (runtime - во время выполнения).

Все встроенные валидаторы (из коробки) содержат следующие заполнители:

{PropertyName} — название валидируемого свойства

{PropertyValue} — значение валидируемого свойства

{PropertyPath} — полный путь к свойству

Пример использования:

// Модель клиента
public class Customer
{
  // Адрес
  public Address? Address { get; set; }
}

// Модель адреса
public class Address
{
  // Фактический адрес
  public string? Actual { get; set; }
}

// Валидатор для модели клиента
public class CustomerValidator : AbstractValidator<Customer>
{
  public CustomerValidator()
  {
    RuleFor(customer => customer.Address.Actual)
      .Null()
        .WithMessage("Название свойства: {PropertyName}\n" +
        "Значение свойства: {PropertyValue}\n" +
        "Путь к свойству: {PropertyPath}");
  }
}

static void Main(string[] args)
{
  var customer = new Customer
  {
    Address = new()
    {
        Actual = "Фактический адрес"
    }
  };
  var validator = new CustomerValidator();
  var result = validator.Validate(customer);

  Console.WriteLine(result.ToString(Environment.NewLine));
  // Выведет >
  // Название свойства: Address Actual
  // Значение свойства: Фактический адрес
  // Путь к свойству: Address.Actual
}

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

Переопределение названия свойства в сообщении.

По умолчанию встроенные сообщения об ошибках содержат название свойства, которое валидируется:

RuleFor(customer => customer.Surname)
  .NotNull(); // Выведет "'Surname' должно быть заполнено."

Хоть вы и можете переопределить сообщение целиком через WithMessage, есть возможность переопределить только название свойства с помощью метода WithName:

RuleFor(customer => customer.Surname)
  .NotNull()
    .WithName("SomeNewName"); // Выведет "'SomeNewName' должно быть заполнено."

Важно! Изменяется только название свойства в сообщении ErrorMessage, но не у свойства PropertyName у элемента коллекции Errors в ValidationResult, там свойство PropertyName всё также будет содержать значение Surname:

Также есть перегрузка для метода WithName:

// Позволяет получить валидируемый объект и использовать его при генерации названия свойства
IRuleBuilderOptions<T, TProperty> WithName<T, TProperty>(this IRuleBuilderOptions<T, TProperty> rule, Func<T, string> nameProvider)

// Краткий пример использования перегрузки
RuleFor(customer => customer.Surname)
  .NotNull()
    .WithName(customer => $"{nameof(customer.Surname)}Foo");

Если вы хотите полностью переопределить название свойства как в ErrorMessage, так и в PropertyName у элемента коллекции Errors, нужно использовать метод OverridePropertyName, вместо метода WithName:

// Валидатор для модели клиента
public class CustomerValidator : AbstractValidator<Customer>
{
  public CustomerValidator()
  {
    // Полностью переопределяем название свойства в сообщении об ошибке
    RuleFor(customer => customer.Surname)
      .NotNull()
        .OverridePropertyName("SomeNewName");
  }
}
Как свойство ErrorMessage, так и свойство PropertyName содержат название свойства SomeNewName
Как свойство ErrorMessage, так и свойство PropertyName содержат название свойства SomeNewName

Как и у метода WithName, у метода OverridePropertyName присутствует такая же перегрузка:

// Позволяет получить валидируемый объект и использовать его при генерации названия свойства
IRuleBuilderOptions<T, TProperty> OverridePropertyName<T, TProperty>(this IRuleBuilderOptions<T, TProperty> rule, Expression<Func<T, object>> expr)

// Краткий пример использования перегрузки
RuleFor(customer => customer.Surname)
  .NotNull()
    .OverridePropertyName(customer => $"{nameof(customer.Surname)}Foo");

По умолчанию названия свойств извлекаются из MemberExpression, который был передан в RuleFor. Если вы хотите изменить это поведение, вы можете задать новое значение для свойства DisplayNameResolver в статическом классе ValidatorOptions.

// Модель клиента
public class Customer
{
  public string? Surname { get; set; }
}

// Валидатор для модели клиента
public class CustomerValidator : AbstractValidator<Customer>
{
  public CustomerValidator()
  {
    RuleFor(customer => customer.Surname)
      .NotNull();
  }
}

static void Main(string[] args)
{
  // Меняем поведение по определению названий свойств
  ValidatorOptions.Global.DisplayNameResolver = (type, member, expression) =>
  {
      if (member is not null)
          return member.Name + "Foo";

      return null;
  };

  // Дальше валидируем как обычно
  var customer = new Customer { Surname = null };
  var validator = new CustomerValidator();
  var result = validator.Validate(customer);

  Console.WriteLine(result.ToString(Environment.NewLine));
  // Выведет > 'SurnameFoo' должно быть заполнено.
}

Предыдущая часть

Источник: https://habr.com/ru/articles/798961/


Интересные статьи

Интересные статьи

Привет, друзья! В этой серии из 2 статей-туториалов мы с вами разработаем клиент-серверное (фуллстек — fullstack) приложение с помощью Next.js и TypeScript. Руководство по Next.js. Карманная ...
В этой части разберем устройство, виды и повадки современных телевизоров.Основные характеристики — всякие яркости, HDRы, контрасты и цветовые охваты, что они значат и зачем нужно 120Гц, мы разобрали в...
Часть 1. Часть 2. Всем привет! В этой части мы рассмотрим использование класса EventWriter и библиотеки ghcjs-dom. Читать дальше →
Алгоритм, который привёл к безудержному успеху нейронных сетей глубокого обучения, не работает в биологическом мозге, но исследователи находят работающие альтернативы. Из...
Docker Swarm, Kubernetes и Mesos являются наиболее популярными фреймворками для оркестровки контейнеров. В своем выступлении Арун Гупта сравнивает следующие аспекты работы Docker, Swa...