Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 516 minIntermédiaire

Valider une saisie en C# : TryParse, Regex et ErrorProvider

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • convertir une saisie sans planter avec TryParse ;
  • contrôler un format avec une expression régulière ;
  • signaler l'erreur à côté du champ avec un ErrorProvider ;
  • répartir les contrôles entre l'interface, la couche métier et la base.

4 exercices corrigés

Valider une saisie, c'est vérifier qu'une valeur tapée par l'utilisateur est complète, au bon format et cohérente avant de l'utiliser ou de l'enregistrer. En WinForms, on s'appuie sur trois outils : les méthodes TryParse pour convertir sans planter, les expressions régulières pour contrôler un format, et le composant ErrorProvider pour signaler l'erreur à côté du champ concerné.

Pourquoi valider à plusieurs endroits

Une même règle peut être vérifiée à trois niveaux, qui n'ont pas le même but.

NiveauExemple pour le numéro de sécurité socialeBut
InterfaceMaxLength 13, filtrage des lettres, ErrorProviderLe confort : l'utilisateur voit son erreur tout de suite, à côté du champ
Couche métierLe set de NumeroSecu refuse ce qui n'a pas 13 chiffres ; le service refuse un numéro déjà prisLa garantie : aucune fenêtre, aucun import ne peut contourner la règle
Base de donnéesCHAR(13), contrainte UNIQUELe dernier rempart : même un script SQL lancé à la main ne peut pas l'enfreindre

La validation de l'interface ne suffit jamais : un autre formulaire, une API ou un import peuvent créer des patients sans passer par elle. Inversement, une validation seulement dans la base produit des messages incompréhensibles (« Duplicate entry '2900575123456' for key 'numeroSecu' »). On valide donc à chaque niveau, chacun pour sa raison.

Les trois questions de toute validation

Pour chaque champ, pose-toi trois questions dans l'ordre :

  1. Présence : le champ est-il obligatoire ? string.IsNullOrWhiteSpace(texte) refuse le vide et les espaces seuls.
  2. Format : la valeur a-t-elle la bonne forme ? Un nombre, une date, 13 chiffres, une adresse électronique.
  3. Cohérence : la valeur a-t-elle un sens, seule ou avec les autres ? Une naissance dans le futur, une durée de 0 jour, une date de fin avant la date de début.

Convertir sans planter : TryParse

Text est toujours une chaîne. Pour obtenir un nombre, int.Parse lève une FormatException si l'utilisateur tape « abc », et une OverflowException s'il tape un nombre trop grand. int.TryParse ne lève jamais d'exception : il renvoie true ou false et place le résultat dans une variable out.

csharp
if (!int.TryParse(txtDuree.Text, out int duree))
{
    errorProvider.SetError(txtDuree, "Saisis un nombre entier de jours.");
    return;
}
// ici, duree est un int valide
Saisieint.Parseint.TryParse
"42"42true, 42
" 42 "42 (espaces acceptés)true, 42
"abc"FormatExceptionfalse
"2147483648"OverflowException (plus grand qu'un int)false
""FormatExceptionfalse

Attention à la culture pour les nombres décimaux. En France, le séparateur décimal est la virgule ; decimal.TryParse("12.50", …) échoue avec la culture française, et decimal.TryParse("12,50", …) échoue avec une culture anglaise. Par défaut, TryParse utilise la culture de Windows (en général française sur les postes de tes utilisateurs). Pour ne pas dépendre du poste, précise-la :

csharp
using System.Globalization;

var fr = new CultureInfo("fr-FR");
bool ok = decimal.TryParse(txtPrix.Text, NumberStyles.Number, fr, out decimal prix);
// "12,50" → true, 12,50    "1 250,50" → true, 1250,50    "12.50" → false

DateTime.TryParse existe aussi, mais 01/02/2026 signifie 1er février en France et 2 janvier aux États-Unis : pour une date, le DateTimePicker du chapitre 2 évite toute ambiguïté.

Contrôler un format : les expressions régulières

Une expression régulière (ou regex) décrit la forme d'un texte. Regex.IsMatch(texte, motif) dit si le texte correspond.

csharp
using System.Text.RegularExpressions;

bool valide = Regex.IsMatch(txtSecu.Text, @"^\d{13}$");
  • ^ et $ : le début et la fin du texte. Sans eux, « abc1234567890123xyz » serait accepté, puisqu'il contient 13 chiffres.
  • \d : un chiffre ; {13} : exactement 13 fois.
  • Le @ devant la chaîne (chaîne verbatim) évite de doubler les barres obliques inverses.
DonnéeMotifAccepteRefuse
N° de sécurité sociale (sans clé)^\d{13}$29005751234562 90 05 75…, 29005751234
N° RPPS d'un médecin^\d{11}$100012345671000123456
Code postal^\d{5}$750117501, 75O11
Téléphone français^0[1-9](\d{2}){4}$0612345678612345678, 06 12 34 56 78
Code médicament du référentiel^[A-Z]{4}\d{3,4}$PARA1000, AMOX500para1000, PARA10000

Signaler l'erreur : ErrorProvider

Une MessageBox par champ invalide est pénible : l'utilisateur la ferme, puis doit chercher le champ fautif. Le composant ErrorProvider affiche une petite icône rouge à côté du champ ; le message apparaît au survol de la souris.

Glisse un ErrorProvider depuis la Boîte à outils : il se range sous le formulaire, dans la barre des composants non visuels. Renomme-le errorProvider. Ensuite, deux appels suffisent :

csharp
errorProvider.SetError(txtNom, "Le nom est obligatoire.");   // affiche l'icône et le message
errorProvider.SetError(txtNom, "");                          // efface l'erreur de ce champ
errorProvider.Clear();                                       // efface toutes les erreurs

Valider au bon moment : Validating et ValidateChildren

Chaque contrôle déclenche l'événement Validating quand il va perdre le focus. Mettre e.Cancel à true signale que sa valeur est invalide.

csharp
private void txtNom_Validating(object? sender, CancelEventArgs e)
{
    if (string.IsNullOrWhiteSpace(txtNom.Text))
    {
        errorProvider.SetError(txtNom, "Le nom est obligatoire.");
        e.Cancel = true;
    }
    else
    {
        errorProvider.SetError(txtNom, "");
    }
}

private void txtSecu_Validating(object? sender, CancelEventArgs e)
{
    string secu = txtSecu.Text.Trim();
    if (secu.Length > 0 && !Regex.IsMatch(secu, @"^\d{13}$"))
    {
        errorProvider.SetError(txtSecu, "13 chiffres attendus, sans espace (ex. 2900575123456).");
        e.Cancel = true;
    }
    else
    {
        errorProvider.SetError(txtSecu, "");
    }
}

Par défaut, e.Cancel = true retient le focus dans le champ : l'utilisateur ne peut plus aller ailleurs tant qu'il n'a pas corrigé, ce qui est vite exaspérant. Trois réglages rendent l'ensemble agréable :

csharp
public FrmPatientEdition(Patient? patient = null)
{
    InitializeComponent();
    AutoValidate = AutoValidate.EnableAllowFocusChange;   // l'erreur s'affiche, mais on peut changer de champ
    btnAnnuler.CausesValidation = false;                  // cliquer sur Annuler ne valide rien
    // …
}

private void btnEnregistrer_Click(object? sender, EventArgs e)
{
    if (!ValidateChildren()) return;   // déclenche Validating sur tous les champs
    // …
}
  • AutoValidate.EnableAllowFocusChange garde la validation à la sortie du champ, sans bloquer le focus.
  • CausesValidation = false sur Annuler : sans ce réglage, cliquer sur Annuler depuis un champ vide déclenche sa validation et affiche une erreur absurde.
  • ValidateChildren() valide tous les champs, même ceux que l'utilisateur n'a jamais visités, et renvoie false si l'un d'eux a mis e.Cancel à true. C'est indispensable : un champ obligatoire jamais visité n'a jamais déclenché son Validating.

L'événement Validated, qui suit Validating quand la valeur est correcte, sert rarement ; c'est dans Validating qu'on place les contrôles.

La règle métier, deuxième barrière

Même si l'interface a tout vérifié, la classe Patient contrôle ses propres données (chapitre 1) : son set lève une ArgumentException quand le nom est vide ou que le numéro n'a pas 13 chiffres. Le formulaire l'intercepte avec un try … catch :

csharp
private void btnEnregistrer_Click(object? sender, EventArgs e)
{
    if (!ValidateChildren()) return;

    try
    {
        Resultat = new Patient
        {
            NumPatient = _numPatient,
            Nom = txtNom.Text,
            Prenom = string.IsNullOrWhiteSpace(txtPrenom.Text) ? null : txtPrenom.Text.Trim(),
            DateNaissance = dtpNaissance.Checked ? dtpNaissance.Value.Date : null,
            NumeroSecu = string.IsNullOrWhiteSpace(txtSecu.Text) ? null : txtSecu.Text.Trim(),
        };
    }
    catch (ArgumentException ex)
    {
        MessageBox.Show(ex.Message, "Saisie incorrecte", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        return;
    }

    DialogResult = DialogResult.OK;
}

Les règles qui demandent la base de données (« ce numéro est-il déjà pris par un autre patient ? ») ne peuvent pas se vérifier dans la fiche : c'est le service de la couche métier qui s'en charge au moment d'enregistrer, et qui lève une RegleMetierException que la fenêtre principale affiche (chapitre 8).

Les exceptions : intercepter ce qu'on sait traiter

Une exception est un objet qui signale une erreur et interrompt le code jusqu'au premier catch capable de la traiter. Si personne ne la traite, l'application affiche une boîte d'erreur et peut se fermer.

csharp
try
{
    await _service.EnregistrerAsync(patient);
}
catch (RegleMetierException ex)          // une règle refusée : on l'explique
{
    MessageBox.Show(ex.Message, "Enregistrement refusé", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
catch (Exception ex)                     // tout le reste : panne réseau, base arrêtée…
{
    MessageBox.Show($"L'enregistrement a échoué.\n{ex.Message}", "Erreur",
        MessageBoxButtons.OK, MessageBoxIcon.Error);
}

Trois règles pour des catch utiles :

  • intercepte d'abord les exceptions précises, puis les plus générales : le premier catch qui correspond gagne ;
  • n'écris jamais de catch { } vide : l'erreur disparaît, et le bug avec elle ;
  • ne te sers pas des exceptions pour la validation ordinaire de l'interface : TryParse et un if sont plus simples et plus rapides qu'un try { int.Parse(…) }.

Exercices

Exercice 1 sur 4 Le numéro RPPS Réussi À revoir

Dans la fiche d'un médecin, le champ txtRpps est facultatif, mais s'il est rempli, il doit contenir exactement 11 chiffres. Écris son gestionnaire Validating avec l'ErrorProvider errorProvider.

Voir la réponseMasquer la réponse de l'exercice 1
csharp
private void txtRpps_Validating(object? sender, CancelEventArgs e)
{
    string rpps = txtRpps.Text.Trim();
    if (rpps.Length > 0 && !Regex.IsMatch(rpps, @"^\d{11}$"))
    {
        errorProvider.SetError(txtRpps, "Le numéro RPPS compte 11 chiffres.");
        e.Cancel = true;
    }
    else
    {
        errorProvider.SetError(txtRpps, "");
    }
}

Le test rpps.Length > 0 laisse passer un champ vide, puisqu'il est facultatif. Pense à effacer l'erreur dans le else, sinon l'icône reste affichée après correction.

Tu avais trouvé ?

Exercice 2 sur 4 Que renvoie TryParse ? Réussi À revoir

Pour chaque appel, donne la valeur renvoyée et celle de la variable out. La culture fr est new CultureInfo("fr-FR").

  1. int.TryParse("007", out int a)
  2. int.TryParse("7,5", out int b)
  3. decimal.TryParse("7,5", NumberStyles.Number, fr, out decimal c)
  4. decimal.TryParse("7.5", NumberStyles.Number, fr, out decimal d)
  5. int.TryParse("3000000000", out int e)
Voir la réponseMasquer la réponse de l'exercice 2
  1. true, a = 7 : les zéros en tête sont acceptés.
  2. false, b = 0 : un int n'a pas de partie décimale.
  3. true, c = 7,5.
  4. false, d = 0 : en français, le point n'est pas le séparateur décimal.
  5. false, e = 0 : 3 milliards dépasse la valeur maximale d'un int (2 147 483 647).

Quand TryParse échoue, la variable out reçoit toujours la valeur par défaut du type, 0 ici.

Tu avais trouvé ?

Exercice 3 sur 4 Le bouton Annuler qui valide Réussi À revoir

Dans la fiche patient, l'utilisateur ouvre la fiche d'un nouveau patient, le curseur est dans txtNom, il change d'avis et clique sur Annuler. Une icône d'erreur « Le nom est obligatoire » apparaît et la fenêtre ne se ferme pas. Explique et corrige.

Voir la réponseMasquer la réponse de l'exercice 3

Cliquer sur Annuler fait perdre le focus à txtNom, ce qui déclenche son Validating. Le nom est vide : e.Cancel = true bloque le focus dans le champ, et le clic sur Annuler n'aboutit pas. Il faut dire que ce bouton ne déclenche pas de validation :

csharp
btnAnnuler.CausesValidation = false;

(ou la propriété CausesValidation à False dans le concepteur). Régler AutoValidate = AutoValidate.EnableAllowFocusChange sur le formulaire évite aussi que le focus reste bloqué dans un champ invalide.

Tu avais trouvé ?

Exercice 4 sur 4 Interface, métier ou base ? Réussi À revoir

Pour chaque règle de GSB Ordonnances, dis à quel(s) niveau(x) tu la vérifies, et comment.

  1. Le nom d'un patient est obligatoire.
  2. Deux patients ne peuvent pas avoir le même numéro de sécurité sociale.
  3. La durée d'un traitement est comprise entre 1 et 90 jours.
  4. Le prénom fait au plus 50 caractères.
Voir la réponseMasquer la réponse de l'exercice 4
  1. Interface (Validating + ErrorProvider), métier (le set de Nom lève une exception), base (NOT NULL).
  2. Métier (le service interroge la base avant d'enregistrer et lève une RegleMetierException) et base (contrainte UNIQUE, le vrai garde-fou si deux postes enregistrent en même temps). L'interface ne peut pas le savoir seule.
  3. Interface (NumericUpDown de 1 à 90, l'erreur est impossible), métier (AjouterLigne refuse une durée hors limites), base (contrainte CHECK (dureeJours BETWEEN 1 AND 90)).
  4. Interface (MaxLength 50) et base (VARCHAR(50), qui refuse un texte trop long en mode strict de MySQL). On peut l'ajouter au métier si d'autres sources que la fiche créent des patients.

Tu avais trouvé ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Liaison de données : BindingSource, BindingList et DataGridViewRevoir le sommaire du cours