Overview of the FluentValidation library. Part 1. First steps
Hello everyone, my name is VladI'm starting a series of library review articles FluentValidation in Russian. The source of information is the official document. I can’t call this a lesson or a full translation, I decided to call it reviews.
What is it for?
FluentValidation – This .NET a library for describing validation rules in a fluid syntax and then applying them. The library is quite flexible; you can build supported validations of any complexity.
Latest version (Fluent Validation 11) supports the following platforms .NET:
.NET Core 3.1
.NET 5
.NET 6
.NET 7
.NET 8
.NET Standard 2.0
How to install in a project?
There are several ways to install the library into a project:
Through NuGet package managerusing the command:
Install-Package FluentValidation
Through dotnet CLIusing the command:
dotnet add package FluentValidation
Via interface Visual Studio IDE (maybe no explanation needed here)
Basic Concepts
Validator – a regular C# class inherited from AbstractValidator<T>
Where T
type of validated model (absolutely any type, including primitive ones int
, string
) to which the validator will be applied. Validator is also the name given to extension methods that perform validation, property validators by type NotNull
(will be discussed later). All of them are used inside the validator constructor, which inherits from AbstractValidator<T>
. That is, the “big validator” contains “mini validators” within it.
Validation rules are usually described inside the validator constructor, as the author of the library intended (there will be examples later).
First steps
Let's create a model that will contain several fields of different primitive types:
// Класс клиента
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; }
}
Now let's create a validator for this model:
using FluentValidation;
// Валидатор для класса клиента
public class CustomerValidator : AbstractValidator<Customer>
{
}
To specify a validation rule for a specific model property, you need to call the method RuleFor
, passing inside a lambda that will point to the model property that you want to validate. Let's set a rule that will give us guarantees after validation that the property Surname
not equal null
:
using FluentValidation;
public class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
// Свойство Surname должно быть не равно null
RuleFor(customer => customer.Surname)
.NotNull();
}
}
Now to run the validator, you need to create an instance of the validator and then call its method Validate
by passing an object to check (an instance of the type that is specified in T
at AbstractValidator<T>
).
// Создаём валидируемый объект модели
Customer customer = new Customer();
// Создаём валидатор
CustomerValidator validator = new CustomerValidator();
// Валидируем объект клиента, получаем результат валидации
ValidationResult result = validator.Validate(customer);
Method Validate
returns a class object ValidationResult
(translated as “Validation Result”), which contains 2 properties:
IsValid
– contains a value of typebool
which informs us about the success of validation.Errors
– collectionList
(each element of the collection is a typeValidationFailure
), which contains details about errors during validation.
To display messages about all errors, you need to go through the collection Errors
in a loop, for example, through foreach
, and use the fields available to us, which can be output to any necessary place, in our case to the console.
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}");
}
}
You can also display all messages in a more convenient way using the method ToString
but in this case there will be less possibilities for customization.
Custoemr customer = new Customer();
CustomerValidator validator = new CustomerValidator();
ValidationResult result = validator.Validate(customer);
string bigMessage = result.ToString("\n"); // Где \n - это разделитель сообщений об ошибках
Console.WriteLine(bigMessage);
Instead of checking the validation result via ValidationResult
you can use an alternative method by throwing an exception when validation fails, using an extension method ValidateAndThrow
.
Custoemr customer = new Customer();
CustomerValidator validator = new CustomerValidator();
validator.ValidateAndThrow(customer);
It throws an exception ValidationException
which contains a collection of validation errors in the property Errors
. Method ValidateAndThrow
this is a wrapper over the method Validate
that is, the following code is executed inside:
validator.Validate(customer, options => options.ThrowOnFailures());
Let's go back to our validator. For one property, you can specify several validators in a chain (fluid syntax):
public class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
// Указываем несколько валидаторов (NotNull, NotEqual) для одного свойства
RuleFor(customer => customer.Surname)
.NotNull()
.NotEqual("foo");
}
}
NotNull
And NotEqual
these are 2 validators that set the rule that the property Surname
must not be equal null
and must not contain the value “foo”.
Validators can be reused for complex model properties (classes, structures, etc.), for example we have the following 2 model classes and 2 validator classes:
// Модель клиента
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<Address>
{
public AddressValidator()
{
RuleFor(address => address.Postcode)
.NotNull();
}
}
// Валидатор для модели клиента
public class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
RuleFor(customer => customer.Name)
.NotNull();
RuleFor(customer => customer.Address)
.SetValidator(new AddressValidator()); // Указываем дочерний валидатор через метод SetValidator
}
}
IN CustomerValidator
validator is reused AddressValidator
for a “complex” property Address
. Installed validator via method SetValidator
will be called child.
When you call the method Validate
at CustomerValidator
all internal validators will work (main and child ones through the method SetValidator
), and one overall validation result will be compiled. If a complex property contains null
then the child validator that is attached to it will not be executed.
Instead of using a child validator, you can set the rule directly (inline):
RuleFor(customer => customer.Address.Postcode)
.NotNull();
In this case there will be no automatic check for null
at the property Address
so to avoid exception NullReferenceException
you must specify a method When
, which specifies a condition before executing the previous validator/validators. There will be more details about him later.
RuleFor(customer => customer.Address.Postcode)
.NotNull()
.When(customer => customer.Address != null)