2009-10-22 5 views
9

J'ai une situation dans laquelle je souhaite comparer des champs (par exemple, s'assurer que l'heure de début est antérieure à l'heure de fin). J'utilise les attributs System.ComponentModel.DataAnnotations pour ma validation.Ecriture d'un attribut CompareTo DataAnnotation

Ma première pensée était quelque chose comme ceci:

public enum CompareToOperation 
{ 
    EqualTo, 
    LessThan, 
    GreaterThan 
} 

public class CompareToAttribute : ValidationAttribute 
{ 
    CompareToOperation _Operation; 
    IComparable _Comparision; 

    public CompareToAttribute(CompareToOperation operation, Func<IComparable> comparison) 
    { 
     _Operation = operation; 
     _Comparision = comparison(); 
    } 

    public override bool IsValid(object value) 
    { 
    if (!(value is IComparable)) 
     return false; 

    switch (_Operation) 
    { 
     case CompareToOperation.EqualTo: return _Comparision.Equals(value); 
     case CompareToOperation.GreaterThan: return _Comparision.CompareTo(value) == 1; 
     case CompareToOperation.LessThan: return _Comparision.CompareTo(value) == -1; 
    } 

    return false; 
    } 
} 

public class SimpleClass 
{ 
    public DateTime Start {get;set;} 
    [CompareTo(CompareToOperation.GreaterThan,() => this.Start)] // error here 
    public DateTime End {get;set;} 
} 

Cela ne fonctionne cependant pas, il y a une erreur de compilation où l'attribut est marqué:

Expression cannot contain anonymous methods or lambda expressions 

Quelqu'un at-il une solution à ce ? Ou une approche différente pour valider un champ par rapport à la valeur d'un autre?

+0

Comment effectuez-vous la validation? Les annotations de données ne sont que des attributs, ce qui importe un peu lorsque vous analysez si une approche particulière fonctionnera ou non. Veuillez poster un extrait de code court de la validation elle-même. – Aaronaught

+0

'[CompareTo (CompareToOperation.GreaterThan,() => this.Start)]' ne fonctionne pas car la classe obtient les attributs appliqués à la compilation plutôt qu'à l'exécution. C'est pourquoi vous êtes autorisé à fournir des expressions constantes uniquement. (et '() => this.Start' n'est pas une expression constante. – Regent

Répondre

8

Un très laid façon qui est loin d'être aussi flexible est de le mettre sur la classe et utiliser la réflexion. Je ne l'ai pas testé, donc je ne suis pas vraiment sûr que cela fonctionne, mais il ne compile :)

public enum CompareToOperation 
{ 
    EqualTo, 
    LessThan, 
    GreaterThan 
} 

public class CompareToAttribute : ValidationAttribute 
{ 
    CompareToOperation _Operation; 
    string _ComparisionPropertyName1; 
    string _ComparisionPropertyName2; 

    public CompareToAttribute(CompareToOperation operation, string comparisonPropertyName1, string comparisonPropertyName2) 
    { 
     _Operation = operation; 
     _ComparisionPropertyName1 = comparisonPropertyName1; 
     _ComparisionPropertyName2 = comparisonPropertyName2; 
    } 

    private static IComparable GetComparablePropertyValue(object obj, string propertyName) 
    { 
     if (obj == null) return null; 
     var type = obj.GetType(); 
     var propertyInfo = type.GetProperty(propertyName); 
     if (propertyInfo == null) return null; 
     return propertyInfo.GetValue(obj, null) as IComparable; 
    } 

    public override bool IsValid(object value) 
    { 
     var comp1 = GetComparablePropertyValue(value, _ComparisionPropertyName1); 
     var comp2 = GetComparablePropertyValue(value, _ComparisionPropertyName2); 

     if (comp1 == null && comp2 == null) 
      return true; 

     if (comp1 == null || comp2 == null) 
      return false; 

     var result = comp1.CompareTo(comp2); 

     switch (_Operation) 
     { 
      case CompareToOperation.LessThan: return result == -1; 
      case CompareToOperation.EqualTo: return result == 0; 
      case CompareToOperation.GreaterThan: return result == 1; 
      default: return false; 
     } 
    } 
} 

[CompareTo(CompareToOperation.LessThan, "Start", "End")] 
public class SimpleClass 
{ 
    public DateTime Start { get; set; } 
    public DateTime End { get; set; } 
} 
+0

et comment afficheriez-vous l'erreur de validation provenant de ce message dans votre page de vue? C'est un barrage routier que j'ai rencontré. –

+1

@ Erx_VB.NExT.Coder: ajoutez simplement '<% ​​= Html.ValidationMessage (string.Empty)%>' et vous recevrez le message de cette erreur de validation de classe globale * * globale. –

+0

@ Erx_VB.NExT.Coder: Lors de la création d'un attribut de niveau classe par sous-classe de ValidationAttribute, si la validation échoue, il n'y a pas de clé correspondante dans ModelState => il s'agira d'une chaîne vide mais un lien est fourni ci-dessous aidera U à afficher le message d'erreur dans ur View en utilisant simplement html.ValidationMessage ("urpropertyname"). http://stackoverflow.com/questions/4266632/unable-to-set-membernames-from-custom-validation-attribute-in-mvc2 – Vipresh

0

D'après le regard, cela ne peut pas être fait. ValidationAttribut est appliqué sur une propriété et en tant que tel est limité à cette propriété uniquement.

Je suppose que la question n'est pas abstraite et que vous avez un vrai problème qui nécessite la présence d'un tel validateur. Probablement c'est la boîte de texte de répétition de mot de passe? :-)

Dans tous les cas, pour contourner le problème, vous devez vous fier au contexte dans lequel vous travaillez. ASP.NET Web Forms l'a fait avec ControlToCompare et puisque tout est un contrôle et que nous avons des conteneurs de noms en place, il est assez facile de comprendre les choses en fonction d'une chaîne simple.

Dans ASP.NET MVC, vous pouvez en théorie faire la même chose, MAIS! Le côté client sera assez facile et naturel - il suffit d'utiliser #PropertyName et de faire vos trucs en javascript. Serverside bien que vous ayez besoin d'accéder à quelque chose d'extérieur à votre classe d'attribut - l'objet Request - et c'est un non non en ce qui me concerne. Dans l'ensemble, il y a toujours une raison pour que ça n'arrive pas et, à mon avis, une raison pour laquelle Microsoft n'a pas implémenté ce type de validateur en premier lieu est - ce n'est pas possible sans les choses décrites ci-dessus .

MAIS! J'espère vraiment que j'ai tort. J'ai besoin de la validation de comparer pour être facile à utiliser ...

0

Je pense que vous avez besoin de quelque chose comme ceci:

public class EqualsAttribute : ValidationAttribute 
{ 
private readonly String _To; 

public EqualsAttribute(String to) 
{ 
    if (String.IsNullOrEmpty(to)) 
    { 
    throw new ArgumentNullException("to"); 
    } 
    if (String.IsNullOrEmpty(key)) 
    { 
    throw new ArgumentNullException("key"); 
    } 
    _To = to; 
} 


protected override Boolean IsValid(Object value, ValidationContext validationContext, out ValidationResult validationResult) 
{ 
    validationResult = null; 
    var isValid = IsValid(value, validationContext); 
    if (!isValid) 
    { 
    validationResult = new ValidationResult(
    FormatErrorMessage(validationContext.DisplayName), 
    new [] { validationContext.MemberName }); 
    } 
    return isValid; 
} 

private Boolean IsValid(Object value, ValidationContext validationContext) 
{ 
    var propertyInfo = validationContext.ObjectType.GetProperty(_To); 
    if (propertyInfo == null) 
    { 
    return false; 
    } 
    var propertyValue = propertyInfo.GetValue(validationContext.ObjectInstance, null); 
    return Equals(value, propertyValue); 
} 

public override Boolean IsValid(Object value) 
{ 
    throw new NotSupportedException(); 
} 
} 
14

Vérifiez le AccountMOdel dans le projet par défaut de MVC2, Il est un attribut PropertiesMustMatchAttribute appliqué à la ChangePasswordModel à validez que les mots-clés NewPassword et ConfirmPassword

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] 
public sealed class PropertiesMustMatchAttribute : ValidationAttribute 
{ 
    private const string _defaultErrorMessage = "'{0}' and '{1}' do not match."; 

    private readonly object _typeId = new object(); 

    public PropertiesMustMatchAttribute(string originalProperty, string confirmProperty) 
     : base(_defaultErrorMessage) 
    { 
     OriginalProperty = originalProperty; 
     ConfirmProperty = confirmProperty; 
    } 

    public string ConfirmProperty 
    { 
     get; 
     private set; 
    } 

    public string OriginalProperty 
    { 
     get; 
     private set; 
    } 

    public override object TypeId 
    { 
     get 
     { 
      return _typeId; 
     } 
    } 

    public override string FormatErrorMessage(string name) 
    { 
     return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString, 
      OriginalProperty, ConfirmProperty); 
    } 

    public override bool IsValid(object value) 
    { 
     PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value); 
     object originalValue = properties.Find(OriginalProperty, true /* ignoreCase */).GetValue(value); 
     object confirmValue = properties.Find(ConfirmProperty, true /* ignoreCase */).GetValue(value); 
     return Object.Equals(originalValue, confirmValue); 
    } 
}