Domanda

Ho una situazione in cui voglio confrontare i campi (esempio, assicurandomi che l'ora di inizio sia precedente all'ora di fine). Sto usando gli attributi System.ComponentModel.DataAnnotations per la mia convalida.

Il mio primo pensiero è stato qualcosa del genere:

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 tuttavia, c'è un errore del compilatore in cui l'attributo è contrassegnato:

Expression cannot contain anonymous methods or lambda expressions

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

È stato utile?

Soluzione

Un modo brutto molto che non è così flessibile è metterlo in classe e usare la riflessione. Non l'ho provato, quindi non sono sicuro che funzioni, ma si 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; }
}

Altri suggerimenti

Controlla AccountMOdel nel progetto predefinito di MVC2, c'è un attributo PropertiesMustMatchAttribute applicato a ChangePasswordModel per convalidare che NewPassword e 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);
    }
}

A quanto pare, non è possibile farlo.

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

Suppongo che la domanda non sia astratta e tu hai un vero problema che richiede la presenza di un tale validatore. Probabilmente è la casella di testo di ripetizione della password? : -)

In ogni caso, per ovviare al problema che hai, devi fare affidamento sul contesto in cui lavori. ASP.NET Web Forms lo ha fatto con ControlToCompare e poiché tutto è un controllo e disponiamo di contenitori di denominazione è abbastanza facile capire le cose in base a una semplice stringa.

In ASP.NET MVC puoi teoricamente fare la stessa cosa, MA! Il lato client sarà abbastanza semplice e naturale: basta usare #PropertyName e fare le tue cose in javascript. Per quanto riguarda il server, dovrai 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 cui le cose (non) accadono e, secondo me, 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 del confronto per essere facile da usare ...

Penso che tu abbia bisogno di qualcosa del genere:

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();
 }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top