Pregunta

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

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 el atributo está marcado:

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?

¿Fue útil?

Solución

Una manera muy fea que no es tan flexible es ponerla en la clase y usar la reflexión. No he probado esto, así que no estoy realmente seguro de que funcione, pero se compila :)

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

Otros consejos

Verifique el AccountMOdel en el proyecto predeterminado de MVC2. Hay un atributo PropertiesMustMatchAttribute aplicado al ChangePasswordModel para validar que NewPassword y ConfirmPassword Match

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

Por su aspecto, esto no se puede hacer.

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

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

En cualquier caso, para solucionar el problema que tiene, debe confiar en el contexto en el que trabaja. ASP.NET Web Forms lo hizo con ControlToCompare y, como todo es un control y tenemos contenedores de nombres en su lugar, es bastante fácil de entender las cosas basadas en una cadena simple.

En ASP.NET MVC, en teoría, puedes hacer lo mismo, ¡PERO! El lado del cliente será bastante fácil y natural, solo use #PropertyName y haga sus cosas en javascript. Sin embargo, debería tener acceso a algo externo a su clase de atributo, el objeto Solicitar, y eso no es un no, en lo que a mí respecta.

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

PERO! Realmente espero que me equivoque. Necesito que la validación de comparación sea fácil de usar ...

Creo que necesitas 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();
 }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top