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:

  1. Through NuGet package managerusing the command:

Install-Package FluentValidation

  1. Through dotnet CLIusing the command:

dotnet add package FluentValidation

  1. 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 Validateby 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:

  1. IsValid – contains a value of type boolwhich informs us about the success of validation.

  2. Errors – collection List (each element of the collection is a type ValidationFailure), 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 ToStringbut 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 ValidationResultyou 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 ValidationExceptionwhich contains a collection of validation errors in the property Errors. Method ValidateAndThrow this is a wrapper over the method Validatethat 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 CustomerValidatorall 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 nullthen 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 Addressso to avoid exception NullReferenceExceptionyou 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)

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *