2009-10-22 10 views
9

Ho una situazione in cui desidero confrontare i campi (ad esempio, assicurandomi che l'ora di inizio sia prima dell'ora di fine). Sto usando gli attributi System.ComponentModel.DataAnnotations per la mia convalida.Scrittura di un attributo CompareTo DataAnnotation

Il mio primo pensiero è stato qualcosa di simile:

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;} 
} 

Questo non funziona però, c'è un errore di compilazione in cui l'attributo è segnato:

Expression cannot contain anonymous methods or lambda expressions 

Qualcuno ha una soluzione per questo ? O un approccio diverso per convalidare un campo rispetto al valore di un altro?

+0

Come si esegue la convalida? Le annotazioni dei dati sono solo attributi, quindi questo importa un po 'quando si analizza o meno un particolare approccio funzionerà. Si prega di inviare un breve frammento di codice della convalida stessa. – Aaronaught

+0

'[CompareTo (CompareToOperation.GreaterThan,() => this.Start)]' non funziona perché la classe ottiene gli attributi applicati in fase di compilazione anziché in fase di esecuzione. Ecco perché sei autorizzato a fornire solo espressioni costanti. (e '() => this.Start' non è un'espressione costante.) – Regent

risposta

8

Un molto brutto modo che non è quasi più flessibile è quello di mettere sul classe e utilizzare la riflessione. Non ho ancora testato questo, quindi non sono realmente sicuro che funziona, ma lo fa compilare :)

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

e come visualizzeresti l'errore di convalida proveniente da questo messaggio nella tua pagina di visualizzazione? questo è un blocco stradale che ho incontrato. –

+1

@ Erx_VB.NExT.Coder: aggiungi semplicemente '<% = Html.ValidationMessage (string.Empty)%>' e riceverai il messaggio di questo particolare * errore di convalida della classe * globale. –

+0

@ Erx_VB.NExT.Coder: quando si crea un attributo di livello di classe per sottoclasse da ValidationAttribute, se la convalida fallisce, non esiste una chiave corrispondente in ModelState => sarà una stringa vuota ma un aggiramento è fornito nel collegamento fornito di seguito aiuterà U a visualizzare il messaggio di errore nella tua vista usando solo html.ValidationMessage ("urpropertyname"). http://stackoverflow.com/questions/4266632/unable-to-set-membernames-from-custom-validation-attribute-in-mvc2 – Vipresh

0

Dal suo aspetto, questo non può essere fatto.

ValidationAttribute viene applicato su una proprietà e come tale è limitato solo a tale proprietà.

Presumo che la domanda non sia astratta e si ha un problema reale che richiede la presenza di un tale validatore. Probabilmente è la casella di testo per la ripetizione della password? :-)

In ogni caso, per aggirare il problema è necessario fare affidamento sul contesto in cui si lavora. ASP.NET Web Form lo ha fatto con ControlToCompare e poiché tutto è un controllo e abbiamo i contenitori di denominazione sul posto è abbastanza facile capire le cose in base a una semplice stringa.

In ASP.NET MVC è possibile in teoria fare la stessa cosa, MA! Il lato client sarà abbastanza semplice e naturale: basta usare #PropertyName e fare le tue cose in javascript. Serverside anche se avresti bisogno di accedere a qualcosa di esterno alla tua classe di attributi - l'oggetto Request - e questo è un no no per quanto mi riguarda.

Tutto sommato, c'è sempre un motivo per le cose (non) che accadono e, a mio parere, un motivo per cui Microsoft non ha implementato questo tipo di validatore in un primo momento è - non è possibile senza le cose sopra descritte .

MA! Spero davvero di sbagliarmi. Ho bisogno della convalida confrontare per essere facile da usare ...

0

Penso che avete bisogno di qualcosa di simile:

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

Check The AccountMOdel nel progetto di default di MVC2, v'è un attributo PropertiesMustMatchAttribute applicato alla ChangePasswordModel a convalidare che la corrispondenza NewPassword e 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); 
    } 
} 
Problemi correlati