Fluent validation

Publié par Oussama BOUAZZA
Catégorie : .Net
03/07/2025

La validation des données est un pilier fondamental de tout logiciel robuste. Elle agit comme un garde-fou, prévenant l’introduction d’informations erronées ou mal formatées qui pourraient compromettre l’intégrité et la stabilité du système, sécuriser les traitements et protéger l’application contre les données malveillantes ou erronées. Les méthodes traditionnelles de validation, comme les attributs de validation ou les vérifications manuelles avec des blocs if/else, deviennent rapidement difficiles à maintenir et à faire évoluer quand le code devient complexe.

 

 

Qu’est-ce que Fluent Validation ?

 

FluentValidation est une bibliothèque .NET open-source qui permet de définir des règles de validation de manière intuitive et réutilisable. Contrairement aux attributs de validation traditionnels comme [Range] ou [StringLength], FluentValidation offre une approche plus flexible et expressive en utilisant la syntaxe Fluent API. Elle sépare la logique de validation des modèles, ce qui rend le code plus maintenable et testable.

 

 

Créer vos premières règles de validation

 

Supposons la classe Book suivante :

 

public class Book
{
    public string Title { get; set; }
    public int Pages { get; set; }
    public string Language { get; set; }
}

 

Nous voulons vérifier la valeur de chaque propriété avec ces conditions :

  • Title ne doit pas être vide
  • Pages doit être supérieur à 0
  • Languages doit être égale à “frenchou “english

FluentValidation fournit la classe AbstractValidator<T> (où T est la classe à valider, ici c’est Book). Pour créer un validateur, il suffit de créer une classe qui hérite de AbstractValidator comme ceci :

 

public class BookValidator : AbstractValidator<Book>
{
    public BookValidator()
    {
        RuleFor(book => book.Title)
            .NotEmpty()
            .MinimumLength(5);
            
        RuleFor(book => book.Pages)
            .GreaterThan(0);
    }
}

 

Le constructeur de la classe BookValidator définit les règles de validation en utilisant la méthode RuleFor(). Cette méthode reçoit en argument une expression lambda qui retourne la propriété de la classe Book à vérifier. Ainsi, il suffit d’ajouter par la suite les règles que nous voulons appliquer à cette propriété (dans cet exemple, Title ne doit pas être vide et contenir au minimum 5 caractères).

Les méthodes NotEmpty() et MinimumLength() sont fournies par FluentValidation. Il existe une multitude de règles fournies par la librairie prêtes à être utilisées (GreaterThanOrEqualTo, InclusiveBetween, NotEqual, Matches, EmailAddress…).

Il est aussi possible de créer des règles personnalisées en utilisant la méthode Must(). Dans notre exemple, Must() est implémenté comme ceci pour la propriété Language:

 

public class BookValidator : AbstractValidator<Book>
{
    public BookValidator()
    {
        RuleFor(book => book.Title)
            .NotEmpty()
            .MinimumLength(5);

        RuleFor(book => book.Pages)
            .GreaterThan(0);
        
        RuleFor(book => book.Language)
            .NotEmpty()
            .Must(lang => lang is "french" or "english");
    }
}

 

 

Utiliser le validateur pendant l’exécution

 

Une fois le validateur défini, il suffit de créer une nouvelle instance de cette classe et utiliser la méthode Validate() comme ceci :

 

Book book = new()
{
    Title = "Harry Potter",
    Pages = 200,
    Language = "french"
};

BookValidator bookValidator = new(); 
ValidationResult validationResult = bookValidator.Validate(book); 
Console.WriteLine(validationResult.IsValid); //log "True"

 

Cette méthode retourne un objet de type ValidationResult. Celui-ci expose une propriété IsValid qui indique si l’objet book respecte les critères définis.

Si l’objet book ne respecte pas toutes les règles, ValidationResult définira IsValid sur false et fournira une liste d’erreurs détaillant les règles non respectées :

 

Book book = new()
{
    Title = "test",
    Pages = -3,
    Language = "spanish"
};

BookValidator bookValidator = new();
ValidationResult validationResult = bookValidator.Validate(book);
Console.WriteLine(validationResult.IsValid); //log "False"

 

Fluent results

 

À noter qu’il est possible de lever une exception si un objet n’est pas valide en utilisant la méthode ValidateAndThrow().

 

 

Réutiliser les validateurs dans des objets complexes

 

Supposons une classe Library contenant une liste d’objets Book définie précédemment :

 

public class Library
{
    public string Name { get; set; }
    public string Address { get; set; }
    public List<Book> Books { get; set; }
}

 

En plus de vérifier la validité des propriétés Name et Address, il faut aussi vérifier que chaque élément de la liste Books respecte les règles définies précédemment. FluentValidation met à disposition la méthode SetValidator() qui prend en argument un validateur héritant de la classe AbstractValidator<T> comme ceci :

 

public class LibraryValidator : AbstractValidator<Library>
{
    public LibraryValidator()
    {
        RuleFor(lib => lib.Name).NotEmpty();
        RuleFor(lib => lib.Address).NotEmpty();
         
        RuleForEach(lib => lib.Books).SetValidator(new BookValidator());
    }
}

 

NB : RuleForEach() permet d’appliquer les règles de validation sur tous les éléments d’une liste.

 

 

Ajouter des conditions de validation

 

Nous allons ajouter dans notre classe Book une propriété IsEbook de type booléen permettant de savoir si un livre est en version physique ou numérique :

 

public class Book
{
    public string Title { get; set; }
    public int Pages { get; set; }
    public string Language { get; set; }
    public bool IsEbook { get; set; }
}

 

Nous voulons maintenant ajouter une règle de taille maximale de 10 caractères pour le titre d’un livre uniquement si IsEbook est true. Pour cela, FluentValidation fournit la méthode When() qui prend en paramètre une expression lambda avec la condition à respecter :

 

public class BookValidator : AbstractValidator<Book>
{
    public BookValidator()
    {
        RuleFor(book => book.Title)
            .NotEmpty()
            .MaximumLength(10)
            .When(book => book.IsEbook == true);
    }
}

 

À noter que la condition s’applique à toutes les règles définies avant When(). Dans cet exemple, si IsEbook est false, FluentValidation ne vérifiera pas les conditions NotEmpty() et MaximumLength(10). Pour vérifier que le titre n’est pas vide pour tous les cas, il suffit de le placer après When() :

 

public class BookValidator : AbstractValidator<Book>
{
    public BookValidator()
    {
        RuleFor(book => book.Title)
            .MaximumLength(10)
            .When(book => book.IsEbook == true)
            .NotEmpty();
    }
}

 

 

Personnaliser les messages d’erreurs

 

Il est possible de personnaliser le message d’erreur pour chaque règle en utilisant la méthode WithMessage() :

 

public class BookValidator : AbstractValidator<Book>
{
    public BookValidator()
    {
        RuleFor(book => book.Title)
            .NotEmpty().WithMessage(" Book title must not be empty")
            .MinimumLength(5).WithMessage(" Book title must be at least 5 characters long");

        RuleFor(book => book.Pages)
            .GreaterThan(0)
            .WithMessage("Le nombre de pages doit être supérieur à 0");
        
        RuleFor(book => book.Language)
            .NotEmpty()
            .Must(lang => lang is "french" or "english")
            .WithMessage(book => $"'{book.Language}' is not a valid language. Only 'french' and 'english' are allowed.");
    }
}

 

Fluent results 2

 

Vous pouvez également donner un code d’erreur, que ValidationResult pourra ensuite utiliser pour gérer les cas d’erreur lors de l’exécution :

 

RuleFor(book => book.Language)
            .NotEmpty()
            .Must(lang => lang is "french" or "english")
            .WithErrorCode("InvalidLanguage");

 

Fluent results 3

 

FluentValidation fournit par défaut des codes d’erreur pour chaque règle définis comme :

  • NotNullValidator
  • NotEmptyValidator
  • EqualValidator
  • LengthValidator

 

 

Ajouter un validateur dans l’injection de dépendance

 

Comme vu précédemment, il faut créer une nouvelle instance de la classe du validateur pour pouvoir l’utiliser par la suite. FluentValidation permet aussi d’utiliser l’injection de dépendance en enregistrant le service dans Program.cs avec IValidator<T> (T étant le modèle à valider) :

 

builder.Services.AddTransient<IValidator<Book>, BookValidator>();
builder.Services.AddTransient<IValidator<Library>, LibraryValidator>();

 

public class MyClass
{
    private IValidator<Book> _bookValidator;
    
    public MyClass(IValidator<Book> bookValidator)
    {
        _bookValidator = bookValidator;
    }
    
    
    public bool ValidateBook(Book book)
    {
        return _bookValidator.Validate(book).IsValid;
    }
}

 

Il faut enregistrer chaque validateur dans les services de l’application. Une alternative est d’utiliser le package Nuget FluentValidation.DependencyInjectionExtensions qui permet d’enregistrer automatiquement tous les validateurs définis dans l’Assembly comme ceci :

 

builder.Services.AddValidatorsFromAssemblies([Assembly.GetExecutingAssembly()], ServiceLifetime.Transient);

 

Source : Documentation officielle FluentValidation