Accueil
Rechercher:
sur developpez.com sur les forums
Forums | Tutoriels | F.A.Q's | Participez | Hébergement | Contacts
Club Emploi Blogs   TV   Dév. Web PHP XML Python Autres 2D-3D-Jeux Sécurité Windows Linux PC Mac
Accueil Conception Java DotNET Visual Basic  C  C++ Delphi MS-Office SQL & SGBD Oracle  4D  Business Intelligence
FORUMS .NET FAQs .NET TUTORIELS .NET SOURCES .NET LIVRES .NET OUTILS .NET BLOG .NET DOTNET TV

[ASPX] Une TextBox qui s'auto-valide

24.08.2004
R. CHAPUIS - (Autres articles)

Avec les validateurs, ASP.Net offre une solution simple et efficace de s'assurer de la validité des informations postées à une page. Une solution simple, mais de loin pas idéale en terme de design et de réutilisation, et pouvant devenir assez lourde à gérer si notre page comporte un nombre de contrôles important.
Partant du principe que c'est au contrôle lui-même de connaître les règles qui le rendent valide ou pas, il fallait trouver un moyen "d'embarquer" sa logique de validation, tout en lui permettant de participer au processus complet de la page.

Valider : Quoi ?

C'est un point de vue personnel et de ce fait sujet à discussion, mais le rôle de l'interface utilisateur en terme de validation des données devrait rester le plus réduit possible.

Prenons un exemple bien tordu :

La valeur de TextBox1 doit être numérique et inférieure à celle de TextBox2 si, et seulement si la valeur de DropDownList1 est égale à 4. Si DropDownList1 est égale à 3 ou 2, TextBox1 peut être égale à TextBox2...etc...

Il est possible d'aller très loin dans la complexité et l'on va très rapidement dépasser les responsabilités d'une interface utilisateur (UI) pour empiéter sur celles de la couche métier.


Alors que peut-on laisser comme validation à la charge de l'UI ? Deux ou trois choses permettant "d'alléger" le travail de la couche logique, et surtout d'éviter des aller-retours à coup d'exceptions, sans pour autant placer des règles métier au mauvais endroit.

Pour rester simple, nous allons donc donner deux missions à notre contrôle :

  • Vérifier qu'un champ obligatoire soit renseigné.
  • Vérifier qu'une entrée soit du type attendu (currency, date, double, integer, string.).

Au passage, et parce que ça ne coûte pas grand chose, on en profitera pour parer à l'absence de la propriété "MaxLenght" lorsque le TextBox est en mode "Multiline"...

Valider : Comment ?

Pour commencer, nous allons nous servir de l'explorateur d'objets de Visual Studio afin de décortiquer le fonctionnement des validateurs existants.

Nous pouvons constater les choses suivantes :

  • Les validateurs héritent tous de System.Web.UI.WebControls.BaseValidator
  • BaseValidator hérite de Label (et donc de WebControl).
  • BaseValidator implémente l'interface IValidator.
  • BaseValidator "override" les méthodes OnInit() et OnUnload()
  • La classe System.Web.UI.Page possède une propriété du type System.Web.UI.ValidatorCollection.
  • ValidatorCollection possède une méthode Add() qui attend un IValidator

Nous avons tous les éléments requis pour commencer à construire notre contrôle.

Etudions en détails ces différents points avant de nous attaquer au code.

L'interface IValidator

Donc, pour qu'un contrôle puisse être reconnu comme un validateur de page, il lui suffit d'implémenter l'interface System.Web.UI.IValidator. Cette dernière n'est pas bien lourde; elle définit une méthode et deux propriétés :

public interface IValidator
{
	// Evalue les conditions de validité et renseigne IsValid

	void Validate();
	// Le message d'erreur renvoyé lorsque la validation échoue
	string ErrorMessage { get; set; }

	bool IsValid { get; set; }
} 

Lors du déclenchement de processus de validation, la page va appeler les méthodes Validate() de tous les IValidator qu'elle contient.

S'enregistrer comme validateur

Nous avons vu que BaseValidator surchargeait OnInit() et UnLoad(). On pouvait le deviner (et un petit coup de Reflector nous en apporte la confirmation), c'est dans ces méthodes que le contrôle s'enregistre et se retire de la collection de validateurs de la page.

Voici donc le code que devra contenir notre TextBox pour réaliser cette opération :

protected override void OnInit(EventArgs e)
	{
		base.OnInit (e);
		this.Page.Validators.Add(this);
	}
	
	protected override void OnUnload(EventArgs e) 
	{
		if (Page != null) 
		{
			Page.Validators.Remove(this);
		}
		base.OnUnload(e);
	}	

Maintenant que nous savons comment faire passer un contrôle comme un validateur, penchons-nous sur les missions que nous avons définies.

Champ requis

Cette validation est évidemment la plus simple à mettre en place...

Une simple propriété binaire fera l'affaire :

	private bool _Required;

	public bool Required
	{
		get { return _Required; }
		set { _Required = value; }
	}

Valider le type de donnée

C'est un légèrement plus compliqué, mais évitons de réinventer la roue et cherchons dans les différentes classes de validation.

Une classe devrait rapidement vous sauter aux yeux : System.Web.UI.WebControls.BaseCompareValidator.

Cette classe possède une méthode protégée et statique Compare() qui permet de comparer deux chaînes en spécifiant un opérateur et un type de donnée. Les opérateurs disponibles sont :

  • ValidationCompareOperator.DataTypeCheck
  • ValidationCompareOperator.Equal
  • ValidationCompareOperator.GreaterThan
  • ValidationCompareOperator.GreaterThanEqual
  • ValidationCompareOperator.LessThan
  • ValidationCompareOperator.LessThanEqual
  • ValidationCompareOperator.NotEqual

Dans notre cas, ValidationCompareOperator.DataTypeCheck est exactement l'opérateur qu'il nous faut; le seul problème est que la méthode dont nous avons besoin est protégée.

Pour pouvoir y accéder, notre contrôle devrait hériter de BaseCompareValidator, ce qui n'est bien sûr pas possible (surtout peu pratique) puisque nous voulons conserver l'héritage de TextBox.

N'ayant pas l'héritage multiple à disposition, nous n'avons pas tellement le choix. Il va falloir confier ce travail à une nouvelle classe, héritant de BaseCompareValidator, que nous ajouterons comme membre à notre TextBox et qui se chargera de nous fournir cette méthode.

Voici son code :

internal class TypeValidator : BaseCompareValidator
	{
		internal TypeValidator() : base() {}
		protected override bool EvaluateIsValid()
		{
			return true;
		}
		// Renvoi un ValidationDataType à partir d'une chaîne

		protected ValidationDataType GetValidationDataType(string textType)
		{
			if (textType == null || textType.Trim() == string.Empty)
			{
				return ValidationDataType.String;
			}
			switch (textType.ToLower())
			{
				case "string" :
					return ValidationDataType.String;
				case "currency" :
					return ValidationDataType.Currency;
				case "date" :
					return ValidationDataType.Date;
				case "double" :
					return ValidationDataType.Double;
				case "integer" :
					return ValidationDataType.Integer;
			}
			return ValidationDataType.String;
		}
		// Wrapper sur BaseCompareValidator.Compare()

		internal bool CompareType(string textValue, string textType)
		{
			return BaseCompareValidator.Compare(textValue,
			                                    string.Empty, 
			                                    ValidationCompareOperator.DataTypeCheck, 
			                                    this.GetValidationDataType(textType));
		}
	}

TypeValidator comporte une méthode publique CompareType() qui va se charger d'appeler BaseCompareValidator.Compare() et de retourner son résultat.

Notez au passage que CompareType n'attend que 2 paramètres et appelle Compare() en lui passant string.Empty comme valeur pour le second paramètre et fixe l'opérateur de comparaison sur DataTypeCheck.

Nous n'avons pas besoin de plus pour cet exemple, mais si vous reprenez le code, vous aurez peut être à redéfinir CompareType afin de profiter de toute les fonctions offertes par BaseCompareValidator.Compare().

La méthode Validate()

Une ultime étape est nécessaire avant d'assembler notre TextBox : la méthode Validate() requise par IValidator.

Celle-ci va effectuer les tests suivants :

  • Si les propriétés Visible et Enabled du TextBox ne sont pas à true, la validation n'a pas lieu d'être et le contrôle est valide.
  • Si la propriété Required est à true, on vérifie que sa propriété Text n'est pas vide.
  • A l'aide du TypeValidator que nous venons de voir, on vérifie le type de la valeur.
  • En dernier lieu, si la propriété TextBoxMode est égale à Multiline, on fait un test sur MaxLenght.

Et voici donc le code complet de notre contrôle :

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text.RegularExpressions;

using System.ComponentModel;
using System.Reflection;
using System.Drawing;

namespace nx.Web.UI.Controls
{
	public class SelfValidatingTextBox : TextBox, IValidator
	{
		private bool _Required;
		private string _RequiredType = "string";

		private string _ErrorMessage;
		private bool _IsValid;
		private TypeValidator _TypeValidator = new TypeValidator();
	
		public bool Required
		{
			get { return _Required; }
			set { _Required = value; }
		}
		public string RequiredType
		{
			get { return _RequiredType; }
			set { _RequiredType = value; }
		}

		public string ErrorMessage
		{
			get { return _ErrorMessage; }
			set { _ErrorMessage = value; }
		}
		public bool IsValid
		{
			get { return _IsValid; }
			set { _IsValid = value; }
		}


		public SelfValidatingTextBox() : base()
		{
		}

		public virtual void Validate()
		{
			// Si le TextBox est caché ou indisponible il doit renvoyer IsValid = true

			if ( !base.Visible || !base.Enabled )
			{
				this.IsValid = true;
				return; 
			}
			// Si le TextBox doit être renseigné on le test
			if ( this.Required && this.Text.Trim() == string.Empty )
			{
				this.ErrorMessage = String.Format("'{0}' is required.", this.ID);
				this.IsValid = false;
				return;
			}
			// Si le TextBox est valide jusqu'ici, on test qu'il soit du bon type

			if ( this._TypeValidator.CompareType(this.Text,this.RequiredType) )
			{
				this.ErrorMessage = String.Format("'{0}' data type is not valid.", this.ID);
				this.IsValid = false;
				return;
			}
			// Pendant qu'on y est, on va rajouter un test sur MaxLenght si
			// on est en TextBoxMode = Multiline

			if ( this.TextMode == TextBoxMode.MultiLine && this.MaxLength != 0 )
			{
				if ( this.Text.Length > this.MaxLength )
				{
					this.ErrorMessage = String.Format("'{0}' is limited to {1} characters", this.ID, this.MaxLength);
					this.IsValid = false;
					return;
				}
			}
		}

		protected override void OnInit(EventArgs e)
		{
			base.OnInit (e);
			this.Page.Validators.Add(this);
		}
		protected override void OnUnload(EventArgs e) 
		{
			if (this.Page != null) 
			{
				this.Page.Validators.Remove(this);
			}
			base.OnUnload(e);
		}	
	}
}

Conclusion

Très simple d'utilisation, ce contrôle est susceptible de vous faire gagner pas mal de temps dans le développement de vos pages.

De plus, et ce n'est pas négligeable, il vous assure contre les inévitables oublis qui surviennent lorsque vous devez manuellement enregistrer vos contrôles dans un validateur existant.

Responsable bénévole de la rubrique DotNET : Jérôme Lambert (Cardi) - Contacter par EMail :
Vos questions techniques : forum d'entraide DotNET - Publiez vos articles, tutoriels et cours
et rejoignez-nous dans l'équipe de rédaction du club d'entraide des développeurs francophones
Nous contacter - Copyright © 2000-2008 www.developpez.com - Legal informations.