Dot.Blog

C#, XAML, Xamarin, UWP/Android/iOS

Validation des données sous Silverlight

[new:30/09/2012]Lorsqu’une nouvelle version de Silverlight sort on parle souvent des ajouts les plus visibles. Par exemple la PathListBox avait fait l’objet de nombreux tutors sur le Web mais personne ne s’en sert... C’est fun à regarder, mais presque inutile. Alors que la validation des données, c’est moins rigolo, mais c’est d’une absolue nécessité !

IDataErrorInfo

La validation des données est un élément important de toute application. Si vous avez écrit du code pour valider des données dans des applications Silverlight avant la sortie de la version 4, vous avez probablement remarqué qu'il n'existait pas de moyens simples de s'assurer que les données sont saisies correctement ni que les erreurs soient affichées en cohérence avec les contrôles de saisie.

La technique alors classique utilisée pour la validation des données consistait à lever des exceptions dans les blocs d'accesseur de propriété (Set) si les données étaient jugées non valides. Les contrôles de saisie liés à une propriété pouvaient ainsi être informés de l’exception de validation de données en définissant ValidatesOnExceptions à true dans le Binding.

Bien que cette technique fonctionne toujours et ne soit pas si mauvaise, il y a de nouvelles options disponibles depuis Silverlight 4 qui peuvent être utilisées sans avoir recours à la levée d'exceptions.

Et bizarrement, certainement par l’habitude prise avec la technique précédente, les nouvelles possibilités de Silverlight me semblent très peu utilisées, alors même que nous sommes passés à la version 5 depuis un petit moment.

Pourtant SL4 a introduit des moyens de validation plus sophistiqués. Une bonne raison de parler de IDataErrorInfo aujourd’hui !

(on notera que d’autres interfaces comme INotifyDataErrorInfo ont été rendues disponibles en même temps. Elles ne seront pas vues dans ce billet pour tenter de lui garder une taille de billet de blog !).

Voici la définition de IDataErrorInfo :

public interface IDataErrorInfo
{
string this[string columnName] { get; }
string Error { get; }
}

On ne peut pas dire que cela soit bien lourd ni compliqué à comprendre...

Les membres de IDataErrorInfo

L'interface IDataErrorInfo ne contient que deux membres, une propriété Error qui permet de retourner un message d’erreur global concernant l’objet validé et une propriété indexée par défaut qui peut être utilisée pour écrire la logique de validation pour les différentes propriétés au sein de l’objet.

Les interfaces sont toujours simples... par définition elle ne contiennent pas de code, ce qui implique que c’est au développeur de respecter le contrat en écrivant ce qu’il convient pour supporter ce dernier.

Implémenter IDataErrorInfo

On implémente généralement l'interface IDataErrorInfo directement sur une entité côté client (dans votre projet Silverlight donc) puisque le procédé de validation proposé n’est “visible” que si l’entité (ses propriétés) est liée en Binding à un contrôle Xaml.

Le squelette d’une telle implémentation sur une classe fictive “Personne” est le suivant :

public class Personne : INotifyPropertyChanged, IDataErrorInfo
{
public string this[string propertyName]
{
get
{
….
}
}

public string Error
{
get { …. }
}
}

Assez souvent la propriété Error n’est pas utilisée, il suffit donc de retourner systématiquement null :

public string Error
{
get { return null; }
}

Mais on peut bien entendu s’en servir pour retourner une information sur l’état global de l’entité validée :

string _Errors;
const string _ErrorsText = "Les données de la Personne sont invalides.";

public string Error
{
get { return _Errors; }
}

La majeure partie du travail de validation est effectuée dans la propriété indexée par défaut de l’interface.

Elle est appelée par les contrôles liés aux propriétés de l'objet (par Binding) pour voir si une propriété liée particulière est valide ou non.

La classe Personne fictive ci-dessus possède deux propriétés fondamentales que sont le nom et l'âge qui doivent être validés. Cela peut se faire dans la propriété indexée ajoutée à la classe suite au support de l’interface IDataErrorInfo, comme le montre l’exemple qui suit :

public string this[string propertyName]
{
get
{
_Errors = null;
switch(propertyName)
{
case "Nom":
if (string.IsNullOrEmpty(Nom))
{
_Errors = _ErrorsText;
return "Le nom ne peut être vide !";
}
break;
case "Age":
if (Age < 1)
{
_Errors = _ErrorsText;
return "L'age doit être plus grand que zéro !";
}
break;
}
return null;
}
}

Ici, les opérations de validation effectuées sont très simples. Dans la réalité les tests peuvent être aussi sophistiqués que nécessaire.

La clé du procédé consiste à utiliser un Switch sur le nom de la propriété pour valider chaque propriété de l’entité. On retourne null s’il n’y a aucun problème, ou bien une chaine précisant l’erreur dans le cas contraire.

Je préconise toujours la création de méthodes privées pour valider chaque propriété. Le Switch du getter de la propriété indexée ne doit faire qu’appeler ces méthodes et ne pas s’emmêler les pinceaux dans du code de validation proprement dit. Dans l’exemple ci-dessus cela passe encore, dans du code réel, cela devient très vite du code spaghetti !

On peut même se passer du Switch dans le getter, c’est ma préférence. Le getter ne fait qu’appeler une méthode qui elle contient le Switch et qui appelle elle-même les méthodes de validation de chaque propriété. Un code bien architecturé est toujours, un jour ou l’autre, un choix dont on se félicite !

Retour visuel sous Xaml

Valider c’est bien... Ne pas lever d’exception pour le faire c’est encore mieux. Mais encore faut-il que cela puisse se voir côté client donc en Xaml !

C’est dans le Binding des propriétés liées à l’entité que se jouera la détection de l’erreur via IDataErrorInfo. Prenons le cas d’une TextBox liée à la propriété Nom de la Personne, on écrira un code comme le suivant :

<TextBox Text="{Binding Nom,Mode=TwoWay,ValidatesOnDataErrors=true}"  />

La capture suivante montre l’effet de la validation sur l’objet TextBox :

image

 

Faiblesse

L'inconvénient de l'interface IDataErrorInfo, c'est qu'elle ne fournit pas un moyen d'effectuer la validation de façon asynchrone. Dans certaines situations vous devrez peut-être vous connecter à un serveur pour vérifier qu'un ID utilisateur ou qu’une adresse mail sont uniques au sein du système, ou qu'un numéro de compte de plan comptable correspond bien à un code existant stocké dans la base de données. IDataErrorInfo ne supporte pas ce type de scénario directement.

Dans un prochain billet je vous parlerai de l'interface INotifyDataErrorInfo qui prend en charge la validation de données asynchrone...

Conclusion

Valider les entités par le simple support de IDataErrorInfo est un procédé que je qualifierai de “non violent” comparativement à la levée d’une exception. Et je préfère le code pacifié que le code warrior qui pète dans tous les sens avec des exceptions.

Toutefois IDataErrorInfo n’est pas une panacée.

D’abord elle ne gère pas, comme je l’ai dit plus haut, les cas de validations asynchrones qui sont de plus en plus souvent nécessaires dans les applications LOB.

Ensuite elle pose un problème de choix. Car IDataErrorInfo n’a absolument aucun effet en elle-même... Ce n’est que lorsqu’une propriété est liée par un Binding (bien programmé de plus) que l’effet de la validation se voit.

Or, quand on implémente une classe d’entité, peut-on toujours savoir à l’avance si la classe sera utilisée uniquement dans un Binding Xaml ou bien si elle risque d’être utilisée en interne par du code '”normal” ? Dans ce dernier cas seules les exceptions peuvent garantir une véritable validation.

De ce fait, IDataErrorInfo ne peut concerner que des entités vouées au seul Binding avec une UI Xaml. Et finalement ce genre d’entité est très rare...

Mais la gestion de cette interface en Xaml est pratique, simple, et le retour visuel peut être templaté pour l’améliorer.

Dilemme...

C’est pourquoi je préconise dans un code réel d’implémenter à la fois IDataErrorInfo et une levée d’exception. Une propriété statique de la classe de l’entité permet de dire si on veut utiliser l’un ou l’autre des procédés. On peut compliquer encore un peu en rendant ce choix propre à chaque instance. Une instance Bindée en Xaml utilisera le procédé de l’interface, et une instance de la même classe utilisée dans du code préfèrera activer la levée d’exception.

Je pense que la popularité de IDataErrorInfo n’a jamais été très grande à cause de cette ambigüité : elle sert à valider des objets, mais seulement vis à vis de Xaml, utilisez la même entité par code et aucune validation ne semble être faite. Pour que cela fonctionne il faut, comme je l’explique ci-dessus mettre en œuvre un code autrement plus complexe. Et les gens n’aiment pas écrire trop de code, et ils ont raison, seuls les paresseux sont de bons développeurs... les fous du clavier sont des dangers à éliminer de son équipe (ou à transférer dans l’équipe de testing !).

On notera aussi que IDataErrorInfo impose le support d’une propriété indexée par défaut. C# ne gérant pas les indexeurs nommés comme le faisait Delphi sont grand frère (et c’est vraiment dommage) cela peut poser de sérieux problèmes si le code des entités utilise déjà un indexeur par défaut...

Fausse bonne idée que IDataErrorInfo ? Peut-être. Ou pas. A vous de voir selon le contexte !

et Stay Tuned !

blog comments powered by Disqus