Обзор библиотеки FluentValidation. Часть 3. Сообщения об ошибках
Переопределение сообщений.
Вы можете переопределять сообщение об ошибке по умолчанию у конкретного валидатора, с помощью метода расширения WithMessage
:
// Модель клиента
public class Customer
{
// Фамилия
public string? Surname { get; set; }
}
// Валидатор для модели клиента
public class CustomerValidator : AbstractValidator
{
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
{
public CustomerValidator()
{
// В методе WithMessage получаем доступ ко всем данным валидируемого объекта
RuleFor(customer => customer.Surname)
.NotNull()
.WithMessage(customer => $"Ссылаемся на значения других свойств: Фамилия {customer.Surname}, Скидка {customer.Discount}");
}
}
Всего у метода расширения WithMessage
есть 3 перегрузки:
// Можно указать простое сообщение об ошибке
IRuleBuilderOptions WithMessage(this IRuleBuilderOptions rule, string errorMessage)
// Можно получить доступ ко всем данным валидируемого объекта
IRuleBuilderOptions WithMessage(this IRuleBuilderOptions rule, Func messageProvider)
// Можно получить доступ ко всем данным валидируемого объекта, доступ к валидируемому свойству
IRuleBuilderOptions WithMessage(this IRuleBuilderOptions rule, Func 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
{
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 WithName(this IRuleBuilderOptions rule, Func nameProvider)
// Краткий пример использования перегрузки
RuleFor(customer => customer.Surname)
.NotNull()
.WithName(customer => $"{nameof(customer.Surname)}Foo");
Если вы хотите полностью переопределить название свойства как в ErrorMessage
, так и в PropertyName
у элемента коллекции Errors
, нужно использовать метод OverridePropertyName
, вместо метода WithName
:
// Валидатор для модели клиента
public class CustomerValidator : AbstractValidator
{
public CustomerValidator()
{
// Полностью переопределяем название свойства в сообщении об ошибке
RuleFor(customer => customer.Surname)
.NotNull()
.OverridePropertyName("SomeNewName");
}
}
Как свойство ErrorMessage, так и свойство PropertyName содержат название свойства SomeNewName
Как и у метода WithName
, у метода OverridePropertyName
присутствует такая же перегрузка:
// Позволяет получить валидируемый объект и использовать его при генерации названия свойства
IRuleBuilderOptions OverridePropertyName(this IRuleBuilderOptions rule, Expression> 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
{
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' должно быть заполнено.
}
Предыдущая часть