Обзор библиотеки FluentValidation. Часть 1. Первые шаги
Всем привет, меня зовут Влад, я начинаю серию статей по обзору библиотеки FluentValidation на русском языке. Источником информации является официальная дока. Не могу назвать это уроком или полноценным переводом, решил назвать это обзорами.
Для чего нужна?
FluentValidation — это .NET библиотека для описания правил валидации в виде текучего синтаксиса, и последующего их применения. Библиотека достаточно гибкая, можно строить поддерживаемые валидации любой сложности.
Последняя версия (FluentValidation 11) поддерживает следующие платформы .NET:
.NET Core 3.1
.NET 5
.NET 6
.NET 7
.NET 8
.NET Standard 2.0
Как установить в проект?
Установить библиотеку в проект можно несколькими способами:
Через NuGet package manager, с помощью команды:
Install-Package FluentValidation
Через dotnet CLI, с помощью команды:
dotnet add package FluentValidation
Через интерфейс Visual Studio IDE (тут возможно объяснений не требуется)
Основные понятия
Валидатор — обычный C# класс наследуемый от AbstractValidator
, где T
тип валидируемой модели (абсолютно любой тип, в том числе примитивные по типу int
, string
), к которому будет применён валидатор. Валидатором также обзывают методы расширения, которые осуществляют валидацию, валидаторы свойств по типу NotNull
(будет рассмотрено позже). Все они применяются внутри конструктора валидатора, который наследуется от AbstractValidator
. То есть «большой валидатор» содержит внутри себя «мини валидаторы».
Правила валидаций обычно описываются внутри конструктора валидатора, так задумал автор библиотеки (позже будут примеры).
Первые шаги
Создадим модель, которая будет содержать несколько полей разных примитивных типов:
// Класс клиента
public class Customer
{
public int Id { get; set; }
public string? Surname { get; set; }
public string? Forename { get; set; }
public decimal Discount { get; set; }
public string? Address { get; set; }
}
Теперь создадим валидатор для этой модели:
using FluentValidation;
// Валидатор для класса клиента
public class CustomerValidator : AbstractValidator
{
}
Чтобы указать правило проверки для определённого свойства модели, нужно вызвать метод RuleFor
, передав внутрь лямбду, которая будет указывать на свойство модели которое вы хотите провалидировать. Давайте зададим правило, которое даст нам гарантии после валидации о том, что свойство Surname
не равно null
:
using FluentValidation;
public class CustomerValidator : AbstractValidator
{
public CustomerValidator()
{
// Свойство Surname должно быть не равно null
RuleFor(customer => customer.Surname)
.NotNull();
}
}
Теперь чтобы запустить валидатор, нужно создать экземпляр валидатора, а затем вызвать у него метод Validate
, передав объект для проверки (экземпляр типа, который указан в T
у AbstractValidator
).
// Создаём валидируемый объект модели
Customer customer = new Customer();
// Создаём валидатор
CustomerValidator validator = new CustomerValidator();
// Валидируем объект клиента, получаем результат валидации
ValidationResult result = validator.Validate(customer);
Метод Validate
возвращает объект класса ValidationResult
(в переводе «Результат валидации»), который содержит 2 свойства:
IsValid
— содержит значение типаbool
, которое сообщает нам об успешности прохождения валидации.Errors
— коллекцияList
(каждый элемент коллекции это типValidationFailure
), которая содержит детали об ошибках в ходе валидации.
Чтобы вывести сообщения обо всех ошибках, нужно пройти коллекцию Errors
в цикле, например, через foreach
, и использовать доступные нам поля, которые можно вывести в любое необходимое место, в нашем случае в консоль.
Custoemr customer = new Customer();
CustomerValidator validator = new CustomerValidator();
ValidationResult result = validator.Validate(customer);
// Если валидация не прошла успешно
if(!result.IsValid)
{
// Выводим сообщения об ошибках валидации в консоль через цикл foreach
foreach(var failure in result.Errors)
{
Console.WriteLine($"Свойство {failure.PropertyName} не прошло валидацию. Ошибка: {failure.ErrorMessage}");
}
}
Можно также вывести все сообщения более удобным способом через метод ToString
, но в таком случае будет меньше возможностей для кастомизации.
Custoemr customer = new Customer();
CustomerValidator validator = new CustomerValidator();
ValidationResult result = validator.Validate(customer);
string bigMessage = result.ToString("\n"); // Где \n - это разделитель сообщений об ошибках
Console.WriteLine(bigMessage);
Вместо того чтобы проверять результат валидации через ValidationResult
, можно использовать альтернативный способ через выброс исключения при неудачной валидации, с помощью метода расширения ValidateAndThrow
.
Custoemr customer = new Customer();
CustomerValidator validator = new CustomerValidator();
validator.ValidateAndThrow(customer);
Он выбрасывает исключение ValidationException
, которое содержит коллекцию ошибок валидации в свойстве Errors
. Метод ValidateAndThrow
это обёртка над методом Validate
, то есть внутри выполняется следующий код:
validator.Validate(customer, options => options.ThrowOnFailures());
Перейдём обратно к нашему валидатору. Для одного свойства можно указывать несколько валидаторов в виде цепочки (текучий синтаксис):
public class CustomerValidator : AbstractValidator
{
public CustomerValidator()
{
// Указываем несколько валидаторов (NotNull, NotEqual) для одного свойства
RuleFor(customer => customer.Surname)
.NotNull()
.NotEqual("foo");
}
}
NotNull
и NotEqual
это 2 валидатора, которые задают правило о том, что свойство Surname
должно быть не равно null
и не должно содержать значение «foo».
Валидаторы могут переиспользоваться для сложных свойств модели (классов, структур и т. д.), например у нас есть следующие 2 класса моделей и 2 класса валидаторов:
// Модель клиента
public class Customer
{
public string? Name { get; set; }
public Address? Address { get; set; }
}
// Модель адреса клиента
public class Address
{
public string? Line1 { get; set; }
public string? Line2 { get; set; }
public string? Town { get; set; }
public string? Country { get; set; }
public string? Postcode { get; set; }
}
// Валидатор для модели адреса клиента
public class AddressValidator : AbstractValidator
{
public AddressValidator()
{
RuleFor(address => address.Postcode)
.NotNull();
}
}
// Валидатор для модели клиента
public class CustomerValidator : AbstractValidator
{
public CustomerValidator()
{
RuleFor(customer => customer.Name)
.NotNull();
RuleFor(customer => customer.Address)
.SetValidator(new AddressValidator()); // Указываем дочерний валидатор через метод SetValidator
}
}
В CustomerValidator
переиспользуется валидатор AddressValidator
для «сложного» свойства Address
. Установленный валидатор через метод SetValidator
будет называться дочерним.
Когда вы вызовете метод Validate
у CustomerValidator
, сработают все внутренние валидаторы (основной и дочерние через метод SetValidator
), и будет составлен один общий результат валидации. Если сложное свойство содержит null
, то дочерний валидатор, который к нему прикреплён, не будет выполнен.
Заместо использования дочернего валидатора можно задать правило непосредственно (inline):
RuleFor(customer => customer.Address.Postcode)
.NotNull();
В этом случае не будет автоматической проверки на null
у свойства Address
, поэтому чтобы избежать исключения NullReferenceException
, необходимо указать метод When
, который задаёт условие перед выполнением предыдущего валидатора/валидаторов. Подробнее про него будет позже.
RuleFor(customer => customer.Address.Postcode)
.NotNull()
.When(customer => customer.Address != null)