2011-11-07 2 views
3

J'ai plusieurs applications dans mon domaine qui acceptent des entrées similaires dans les champs de texte. Chaque application implémente sa propre validation. Je veux apporter cette fonctionnalité dans une bibliothèque de classes afin que, plutôt que de réinventer la roue sur chaque projet, nos développeurs puissent rapidement mettre en œuvre la bibliothèque de validation et passer à autre chose.Comment devrais-je concevoir des classes de validation de chaîne en C#?

Je ne suis pas le meilleur quand il s'agit de la conception OO. Ce dont j'ai besoin, c'est la possibilité pour un utilisateur d'entrer une chaîne arbitraire, puis à la bibliothèque de validation de la vérifier par rapport aux types connus pour s'assurer qu'elle correspond à l'un d'entre eux. Devrais-je construire une interface et faire de chaque type de chaîne une classe qui implémente cette interface? (semble mal puisque je ne connaîtrai pas le type quand je lirai dans la chaîne). Je pourrais utiliser de l'aide pour identifier un motif pour cela.

Merci.

+0

Est-il possible de rendre les champs de formulaire déclarent quel est leur type? –

+0

Dans mon esprit, cette décision dépendrait de la complexité des validations. Parfois, le démarrage simple est la meilleure approche si c'est possible. Une classe statique avec des méthodes de validation, 'ValidateZip (valeur de chaîne)' par exemple, serait suffisante dans de nombreux cas. Peux-tu élaborer? – harlam357

+0

Démarrage de sons simples géniaux pour ce que je fais. Je regarde les identifiants matériels pour différents modules matériels. Mes utilisateurs les connaissent comme des «numéros de série», mais en réalité, il peut s'agir d'adresses MAC, de numéros IMEI pour des parties cellulaires ou d'identifiants uniques générés par notre application Web. J'ai des instructions regex définies pour chaque type d'ID, mais je ne savais pas comment les organiser efficacement afin d'éviter une très longue liste de règles regex à mesure que je grandis. – GregB

Répondre

3

J'ai toujours été un fan de Fluent Validation for .Net. Si elle est plus robuste que nécessaire, sa fonctionnalité est assez facile à imiter par vous-même. Si vous êtes intéressé, voici un lien vers mon very simple validation class. Il est similaire à Fluent Validation, mais utilise lambdas pour créer les assertions de validation. Voici un exemple rapide de la façon de l'utiliser:

 
public class Person 
{ 
    public Person(int age){ Age = age; } 
    public int Age{ get; set;} 
} 

public class PersonValidator : AbstractValidator 
{ 
    public PersonValidator() 
    { 
     RuleFor(p => p.Age >= 0, 
      () => new ArgumentOutOfRangeException(
       "Age must be greater than or equal to zero." 
     )); 
    } 
} 

public class Example 
{ 
    void exampleUsage() 
    { 
     var john = new Person(28); 
     var jane = new Person(-29); 

     var personValidator = new PersonValidator(); 

     var johnsResult = personValidator.Validate(john); 
     var janesResult = personValidator.Validate(jane); 

     displayResult(johnsResult); 
     displayResult(janesResult); 
    } 

    void displayResult(ValidationResult result) 
    { 
     if(!result.IsValid) 
      Console.WriteLine("Is valid"); 
     else 
      Console.WriteLine(result.Exception.GetType()); 
    } 
} 

(voir source code pour un exemple plus complet).

Sortie:

 
Is valid 
System.ArgumentOutOfRangeException 
+0

Cela semble être un très bon outil, mais il ne répond pas à mon problème de conception OO d'où ces règles devraient aller. – GregB

+0

Je suis en train d'appuyer la réponse de @ErOx, utilisez une bibliothèque existante si vous le pouvez. Si vous ne pouvez pas, j'ai construit des bibliothèques dans le passé en utilisant des expressions régulières. Une collection d'objets regex de poids mouche - peut-être des structures - qui savent comment valider une chaîne arbitraire contre eux-mêmes pourrait vous donner une liste de correspondances candidates. Veillez à gérer plusieurs correspondances: assurez-vous d'avoir une hiérarchie qui détermine la correspondance la mieux adaptée. – ssamuel

+0

J'aimerais voir ces «structures regex de poids plume» en C#. –

0

Vous devez effectuer les opérations suivantes:

  1. Parse votre chaîne et comprendre (en quelque sorte) ce type de chaîne est. Je préférerais le savoir avant la validation (en affectant des types aux champs), car si une chaîne est incorrecte, vous pouvez lui assigner un type incorrect.
  2. Validez votre chaîne en fonction des règles de validation applicables au type de champ donné. Ces validateurs doivent implémenter une interface, de sorte que vous pouvez valider n'importe quel type de chaîne. Habituellement, vous avez non seulement une validation spécifique à un type de champ, mais une validation spécifique à un champ, ce type de validateur doit donc également implémenter la même interface.

Tout le reste dépend de la logique de votre application.

2

Chaque application implémente sa propre validation. Je veux apporter cette fonctionnalité dans une bibliothèque de classes afin que, plutôt que de réinventer la roue sur chaque projet, nos développeurs puissent rapidement implémenter la bibliothèque de validation et passer à autre chose.

Votre problème semble similaire aux contraintes NUnit personnalisées. NUnit permet ce qu'il appelle un constraint-based assertion model, et permet à l'utilisateur de créer custom constraints, en indiquant si un objet donné satisfait ou non aux critères de cette contrainte.

En utilisant un modèle de contrainte à base d'objets est supérieure à un modèle de contrainte purement fonction:

  • Il vous permet de sous-contraintes globales pour évaluer une contrainte de niveau supérieur.
  • Il vous permet de fournir des informations de diagnostic indiquant pourquoi une contrainte spécifique ne correspond pas à vos données d'entrée.

Cela semble de fantaisie, mais les contraintes ne sont que des fonctions qui prennent un paramètre de votre type désiré, retourne true si elle correspond, et false si elle ne fonctionne pas.

Adaptation à votre problème

Ce que j'ai besoin est la possibilité pour un utilisateur d'entrer une chaîne arbitraire, puis pour la bibliothèque de validation pour vérifier contre les types connus pour vous assurer que correspond à l'un d'eux.

Vous n'avez pas besoin de construire des assertions à partir de vos contraintes. Vous pouvez évaluer les contraintes sans lancer d'exceptions et faire vos classifications en premier.

Mais je ne vous recommande pas de faire une classification automatique. Je vous recommande d'attacher une contrainte spécifique à une entrée spécifique, plutôt que d'essayer de faire correspondre toutes les contraintes disponibles. Passez le string à cette contrainte et appelez-le done. Si vous devez le faire pour les objets de niveau supérieur, construisez une contrainte pour l'objet de niveau supérieur qui utilise des contraintes spécifiques (existantes) pour chacun de ses sous-champs, ainsi que la validation des contraintes entre les champs. Lorsque vous avez terminé, vous pouvez agréger toutes les violations de contraintes au niveau supérieur et faire en sorte que votre logique de validation génère une exception contenant toutes les violations.

BTW, je ne voudrais pas utiliser exactement la même interface NUnit fait:

  • Il est une conception déroutante
  • Je préfère une approche que les génériques utilisés tout au long
  • Je d préfèrent une approche qui vous a permis de revenir un IEnumerable<ConstraintViolation> ou IEnumerable<string>, plutôt que de prendre une sorte de classe de sortie écrivain comme une dépendance

Mais je serais certainement ste al le concept de base :)

mise en œuvre

Voici un exemple d'implémentation de ce dont je parle:

public class ConstraintViolation 
{ 
    public ConstraintViolation(IConstraintBase source, string description) 
    { 
     Source = source; 
     Description = description; 
    } 

    public IConstraintBase Source { get; } 
    public string Description { get; set; } 
} 

public interface IConstraintBase 
{ 
    public string Name { get; } 
    public string Description { get; } 
} 

public interface IConstraint<T> : IConstraintBase 
{ 
    public IEnumerable<ConstraintViolation> GetViolations(T value); 
} 

Et voici une contrainte d'exemple pour valider la longueur d'une chaîne (un exemple faible, mais voir mes commentaires à ce sujet ci-dessous):

public class StringLengthConstraint : IConstraint<string> 
{ 
    public StringLengthConstraint(int maximumLength) 
     : this(minimumLength: 0, maximumLength: maximumLength) 
    { 
    } 

    public StringLengthConstraint(int minimumLength, int maximumLength, 
     bool isNullAllowed = false) 
    { 
     MinimumLength = minimumLength; 
     MaximumLength = maximumLength; 
     IsNullAllowed = isNullAllowed; 
    } 

    public int MinimumLength { get; private set; } 
    public int MaximumLength { get; private set; } 
    public bool IsNullAllowed { get; private set; } 

    public IEnumerable<ConstraintViolation> GetViolations(string value) 
    { 
     if (value == null) 
     { 
      if (!IsNullAllowed) 
      { 
       yield return CreateViolation("Value cannot be null"); 
      } 
     } 
     else 
     { 
      int length = value.Length; 

      if (length < MinimumLength) 
      { 
       yield return CreateViolation(
        "Value is shorter than minimum length {0}", 
        MinimumLength); 
      } 

      if (length > MaximumLength) 
      { 
       yield return CreateViolation("Value is longer than maximum length {0}", 
        MaximumLength); 
      } 
     } 
    } 

    public string Name 
    { 
     get { return "String Length"; } 
    } 

    public string Description 
    { 
     get 
     { 
      return string.Format("Ensure a string is an acceptable length" 
       + " - Minimum: {0}" 
       + ", Maximum: {1}" 
       + "{2}" 
       , MinimumLength 
       , MaximumLength 
       , IsNullAllowed ? "" : ", and is not null" 
       ); 
     } 
    } 

    private ConstraintViolation CreateViolation(string description, 
     params object[] args) 
    { 
     return new ConstraintViolation(this, string.Format(description, args)); 
    } 
} 

Comment l'utiliser lors de la validation sur un seul champ:

var violations = new StringLengthConstraint(10).GetViolations(value); 

if(violations.Any()) 
{ 
    throw new InvalidArgumentException("value", string.Join(", ", violations)); 
} 

Justification

La contrainte de longueur de la chaîne est beaucoup de code pour faire quelque chose stupidement simple, surtout si vous faites cela une seule fois. Mais il y a des avantages à cette approche:

Il est réutilisable

écrire ceci ou utiliser une fois, et je suis d'accord cela est une douleur.

Mais la plupart du code ici est de permettre que cela soit réutilisable. Par exemple, vous pouvez sélectionner ceci parmi une liste de contraintes pour un type string. Ou vous pouvez afficher une liste de contraintes ou de violations de contraintes sur une interface utilisateur, avec des info-bulles, etc. Ou vous pouvez l'utiliser dans un cadre de test unitaire; Avec une classe d'adaptateur, il pourrait se connecter directement à NUnit.

Ce modèle prend en charge l'agrégation des contraintes et des violations

  • Grâce Linq
  • par la composition objet

Linq:

var violations = new SomeConstraint(someData).GetViolations(value) 
    .Concat(new SomeOtherConstraint(someData).GetViolations(value)) 
    ; 

composition de l'objet:

// ... 
public IEnumerable<ConstraintViolation> GetViolations(SomeType value) 
{ 
    if(value == 42) 
    { 
     yield return new ConstraintViolation(this, "Value cannot be 42"); 
    } 

    foreach(var subViolation in subConstraint.GetViolations(value)) 
    { 
     yield return subViolation; 
    } 
} 

private SomeSubConstraint subConstraint; 
+0

Si vous faites plus de correspondances que de validation (application de l'absence de violation), vous pouvez ajouter un bool IsSatisfiedBy (valeur T) '. –

Questions connexes