2009-10-22 20 views
9

Tengo una situación en la que quiero comparar campos (por ejemplo, asegurar que la hora de inicio sea anterior a la hora de finalización). Estoy usando los atributos System.ComponentModel.DataAnnotations para mi validación.Escribir un atributo CompareTo DataAnnotation

Mi primer pensamiento fue algo como esto:

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

Esto no funciona, sin embargo, hay un error de compilación, donde está marcado el atributo:

Expression cannot contain anonymous methods or lambda expressions 

¿Alguien tiene una solución para esto ? ¿O un enfoque diferente para validar un campo en comparación con el valor de otro?

+0

¿Cómo está realizando la validación? Las anotaciones de datos son solo atributos, por lo que esto importa un poco al analizar si un enfoque particular funcionará o no. Por favor, publique un breve fragmento de código de la validación en sí. – Aaronaught

+0

'[CompareTo (CompareToOperation.GreaterThan,() => this.Start)]' no funciona porque la clase obtiene atributos aplicados en tiempo de compilación en lugar de en tiempo de ejecución. Es por eso que solo puedes proporcionar expresiones constantes. (y '() => this.Start' no es una expresión constante.) – Regent

Respuesta

8

Un muy manera fea que no es tan flexible es ponerlo en el clase y usa la reflexión. No he probado esto, así que no estoy realmente seguro de que funciona, pero lo hace compilar :)

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

y cómo mostraría el error de validación proveniente de este mensaje en su página de vista? este es un obstáculo con el que me he encontrado. –

+1

@ Erx_VB.NExT.Coder: simplemente agregue '<% = Html.ValidationMessage (string.Empty)%>' y obtendrá el mensaje de este particular * validación de clase * global error. –

+0

@ Erx_VB.NExT.Coder: al crear un atributo de nivel de clase subclasificando desde ValidationAttribute, si la validación falla, no hay una clave correspondiente en ModelState => será una cadena vacía, pero se proporciona una solución alternativa en el enlace proporcionado a continuación, que ayudará a U a mostrar el mensaje de error en su Vista utilizando solo html.ValidationMessage ("urpropertyname"). http://stackoverflow.com/questions/4266632/unable-to-set-membernames-from-custom-validation-attribute-in-mvc2 – Vipresh

0

Por el aspecto de esto, esto no se puede hacer.

ValidationAttribute se aplica a una propiedad y, como tal, está restringido solo a esa propiedad.

Supongo que la pregunta no es abstracta y que tiene un problema real que requiere la presencia de dicho validador. ¿Probablemente es el cuadro de texto de contraseña de repetición? :-)

En cualquier caso, para evitar el problema que tiene, necesita confiar en el contexto en el que trabaja. ASP.NET Web Forms lo hizo con ControlToCompare y ya que todo es un control y tenemos nombres de contenedores en su lugar es bastante fácil resolver las cosas en base a una cadena simple.

En ASP.NET MVC, en teoría, puede hacer lo mismo, PERO! El lado del cliente será bastante fácil y natural: solo use el #PropertyName y haga sus cosas en javascript. Por otro lado, necesitaría acceder a algo externo a su clase de atributo, el objeto Solicitud, y eso es un no no, en lo que a mí respecta.

En general, siempre hay una razón para que las cosas (no) sucedan y, en mi opinión, una razón por la cual Microsoft no implementó este tipo de validador en primer lugar es - no es posible sin las cosas descritas anteriormente .

PERO! Realmente espero estar equivocado. Yo sí es necesario comparar la validación para ser fácil de usar ...

0

Creo que se necesita algo como esto:

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

Compruebe El AccountMOdel en el proyecto predeterminado de MVC2, hay un atributo PropertiesMustMatchAttribute aplica a la ChangePasswordModel a valide que NewPassword y ConfirmPassword coincidan

[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); 
    } 
} 
Cuestiones relacionadas