Validation personnalisée ValidationAttribute et Blazor

· 4 min de lecture

# Tout personnaliser

Comme vous l’avez probablement vu dans tous mes articles, j’essaie vraiment de garder tout aussi propre que possible, car j’ai déjà écrit des articles concernant les attributs personnalisés, la gestion des exceptions personnalisées, l’injection de collection de services, etc.

J’ai réalisé avec le temps que ce type de méthode de codage me donnait, à moi et à mon équipe, un moyen d’améliorer les heures supplémentaires, de trouver plus facilement les problèmes et de séparer le code autant que possible.

Ouais, après cette histoire sympa que je viens de vous raconter, j’ai travaillé sur Blazor ces derniers temps comme d’habitude et j’ai découvert qu’après des années et des années de développement, vous pouvez créer des attributs de validation personnalisés.

Ouais, c’est drôle, après toutes ces années…

Attributs de validation personnalisés

L’idée est venue du travail en fait, nous faisons toujours de la validation partout mais j’avais certains champs qui nécessitaient un peu le même processus de validation, alors j’ai pensé qu’il pourrait y avoir quelque chose là-bas… comme des attributs de validation personnalisés !

J’ai donc lancé la documentation Microsoft à ce sujet et découvert que oui, vous pouvez créer des attributs de validation personnalisés et les attribuer aux propriétés, comme ceci :

public class StringLengthRangeAttribute : ValidationAttribute
{
    public int Minimum { get; set; }
    public int Maximum { get; set; }

    public StringLengthRangeAttribute()
    {
        this.Minimum = 0;
        this.Maximum = int.MaxValue;
    }

    public override bool IsValid(object value)
    {
        string strValue = value as string;
        if (!string.IsNullOrEmpty(strValue))
        {
            int len = strValue.Length;
            return len >= this.Minimum && len <= this.Maximum;
        }
        return true;
    }
}

et utilisez-le dans une classe simple comme celle-ci :

[Required]
[StringLengthRange(Minimum = 10, ErrorMessage = "Must be >10 characters.")]

[StringLengthRange(Maximum = 20)]

[Required]
[StringLengthRange(Minimum = 10, Maximum = 20)]

Validateur personnalisé

J’ai donc ce validateur dont j’ai besoin pour une analyse de rentabilisation spécifique qui contiendra 20 premiers caractères qui comprendront 9 chiffres et un trait d’union, et se termineront par 2 caractères qui seront généralement le code du pays, donc quelque chose comme ceci : 123456789-123456789-ES

J’ai fini par arriver avec quelque chose comme ça, c’est vraiment simple mais ça marche :

using System;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;

public class SpecialStringValidatorAttribute : ValidationAttribute
{
    private const int TotalLength = 22;
    private const string Pattern = @"^(\d{10})-(\d{10})-([A-Za-z]{2})$";

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        string strValue = value as string;

        if (!string.IsNullOrEmpty(strValue))
        {
            if (strValue.Length != TotalLength)
            {
                return new ValidationResult($"The string must be {TotalLength} characters long.");
            }

            if (!Regex.IsMatch(strValue, Pattern))
            {
                return new ValidationResult("The string must follow the pattern: 1234567890-1234567890-AB");
            }

            return ValidationResult.Success;
        }

        return new ValidationResult("The string cannot be null or empty.");
    }
}

## Tests

J’ai aussi écrit un test pour eux, juste au cas où :

public class SpecialStringValidatorTests
{
    [Theory]
    [InlineData("1234567890-1234567890-AB", true)]
    [InlineData("1234567890-1234567890-XY", true)]
    [InlineData("1234567890-1234567890-A1", false)]
    [InlineData("1234567890-1234567890-A", false)]
    [InlineData("1234567890-123456789-AB", false)]
    [InlineData("1234567890-1234567890", false)]
    [InlineData("1234567890-1234567890-ABCDE", false)]
    public void SpecialStringValidatorTest(string input, bool expectedResult)
    {
        // Arrange
        var validator = new SpecialStringValidatorAttribute();

        // Act
        var result = validator.IsValid(input);

        // Assert
        Assert.Equal(expectedResult, result);
    }
}

Et une fois exécuté, j’ai eu ces résultats :

Microsoft (R) Test Execution Command Line Tool Version 16.9.1
Copyright (c) Microsoft Corporation.  All rights reserved.

Starting test execution, please wait...

A total of 1 test files matched the specified pattern.

Test run in progress...

Passed!  - SpecialStringValidatorTests.SpecialStringValidatorTest(input: "1234567890-1234567890-AB", expectedResult: True)
Passed!  - SpecialStringValidatorTests.SpecialStringValidatorTest(input: "1234567890-1234567890-XY", expectedResult: True)
Passed!  - SpecialStringValidatorTests.SpecialStringValidatorTest(input: "1234567890-1234567890-A1", expectedResult: False)
Passed!  - SpecialStringValidatorTests.SpecialStringValidatorTest(input: "1234567890-1234567890-A", expectedResult: False)
Passed!  - SpecialStringValidatorTests.SpecialStringValidatorTest(input: "1234567890-123456789-AB", expectedResult: False)
Passed!  - SpecialStringValidatorTests.SpecialStringValidatorTest(input: "1234567890-1234567890", expectedResult: False)
Passed!  - SpecialStringValidatorTests.SpecialStringValidatorTest(input: "1234567890-1234567890-ABCDE", expectedResult: False)

Test Run Successful.
Total tests: 7
     Passed: 7
 Total time: 1.7296 Seconds

Validation personnalisée et Blazor

Alors maintenant que je sais que le sien peut être utilisé, c’est clairement une bonne idée de l’implémenter dans Blazor, n’est-ce pas ?

Supposons que j’ai ce formulaire, qui utilisera le modèle Person que j’ai montré précédemment :

@using Models
@page "/"

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

<EditForm Model=@Person FormName="PersonForm">
  <DataAnnotationsValidator/>
  <ValidationSummary/>
  <div class="form-group">
    <label for="Name">Name</label>
    <InputText @bind-Value=Person.Name class="form-control" id="Name" />
    <ValidationMessage For="() => Person.Name"/>
  </div>
  <div class="form-group">
    <label for="MySpecialString">My special string</label>
    <InputText @bind-Value=Person.MySpecialString class="form-control" id="Name" />
    <ValidationMessage For="() => Person.MySpecialString"/>
  </div>
  <div class="form-group">
    <label for="Age">Age</label>
    <InputNumber @bind-Value=Person.Age class="form-control" id="Age" />
    <ValidationMessage For=@(() => Person.Age) />
  </div>
  <input type="submit" class="btn btn-primary" value="Save"/>
</EditForm>

@code {
  Person Person = new Person();
}

Dès que nous exécutons ceci, nous obtenons les erreurs suivantes :

Et si nous mettons juste ce que nous voulons, nous obtenons la chose suivante, tout à fait claire :

Pour être honnête pour moi, il est clair que nous devrions au moins déplacer la logique pour valider ces formulaires vers des attributs de validation personnalisés, cela nous donne la liberté de stocker le code de cette connexion en un seul endroit, et nous pouvons l’utiliser plus tard pour une API ou une autre application.

J’espère que cela vous a plu, si vous avez des questions ou si vous souhaitez nous contacter, n’hésitez pas et contactez-moi !