60

I want to create a custom validation attribute, in which I want to compare the value of my property with another property's value in my model class. For example I have in my model class:

...    
public string SourceCity { get; set; }
public string DestinationCity { get; set; }

And I want to create a custom attribute to use it like this:

[Custom("SourceCity", ErrorMessage = "the source and destination should not be equal")]
public string DestinationCity { get; set; }
//this wil lcompare SourceCity with DestinationCity

How can I get there?

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
TheForbidden
  • 1,533
  • 4
  • 22
  • 30
  • 1
    http://haacked.com/archive/2009/11/19/aspnetmvc2-custom-validation.aspx – Joe Aug 14 '12 at 20:05
  • 1
    @Joe, that's for ASP.NET MVC 2 and no longer applies to MVC 3. Also this blog post doesn't illustrate how to retrieve a dependent property value in the validator which is what the OP is trying to achieve here. – Darin Dimitrov Aug 14 '12 at 20:14

3 Answers3

97

Here's how you could obtain the other property value:

public class CustomAttribute : ValidationAttribute
{
    private readonly string _other;
    public CustomAttribute(string other)
    {
        _other = other;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var property = validationContext.ObjectType.GetProperty(_other);
        if (property == null)
        {
            return new ValidationResult(
                string.Format("Unknown property: {0}", _other)
            );
        }
        var otherValue = property.GetValue(validationContext.ObjectInstance, null);

        // at this stage you have "value" and "otherValue" pointing
        // to the value of the property on which this attribute
        // is applied and the value of the other property respectively
        // => you could do some checks
        if (!object.Equals(value, otherValue))
        {
            // here we are verifying whether the 2 values are equal
            // but you could do any custom validation you like
            return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
        }
        return null;
    }
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 1
    Great, this is just the answer **I'm** looking for! Except my validation context is always null. Any ideas? – Grimm The Opiner Nov 07 '13 at 10:18
  • 6
    @GrimmTheOpiner I know this is old, but for anyone looking try adding `public override bool RequiresValidationContext { get { return true; } }` in `CustomAttribute` – Ryan Jun 29 '17 at 02:53
  • It works. Thank you. Also is it possible to add to Swagger document? – anhtv13 Jan 07 '22 at 11:44
6

Please look below for my example:

Model class implements INotifyPropertyChanged

public class ModelClass : INotifyPropertyChanged
{
    private string destinationCity;

    public string SourceCity { get; set; }

    public ModelClass()
    {
        PropertyChanged += CustomAttribute.ThrowIfNotEquals;
    }

    [Custom("SourceCity", ErrorMessage = "the source and destination should not be equal")]
    public string DestinationCity
    {
        get
        {
            return this.destinationCity;
        }
        set
        {
            if (value != this.destinationCity)
            {
                this.destinationCity = value;
                NotifyPropertyChanged("DestinationCity");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void NotifyPropertyChanged(string info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

Attribute class also contains event hendler.

internal sealed class CustomAttribute : Attribute
{
    public CustomAttribute(string propertyName)
    {
        PropertyName = propertyName;
    }

    public string PropertyName { get; set; }

    public string ErrorMessage { get; set; }

    public static void ThrowIfNotEquals(object obj, PropertyChangedEventArgs eventArgs)
    {
        Type type = obj.GetType();

        var changedProperty = type.GetProperty(eventArgs.PropertyName);

        var attribute = (CustomAttribute)changedProperty
            .GetCustomAttributes(typeof(CustomAttribute), false)
            .FirstOrDefault();

        var valueToCompare = type.GetProperty(attribute.PropertyName).GetValue(obj, null);

        if (!valueToCompare.Equals(changedProperty.GetValue(obj, null)))
            throw new Exception("the source and destination should not be equal");
    }
}

Usage

    var test = new ModelClass();
    test.SourceCity = "1";
    // Everything is ok
    test.DestinationCity = "1";
    // throws exception
    test.DestinationCity ="2";

To simplify code I decided to omit a validation.

Massimiliano Kraus
  • 3,638
  • 5
  • 27
  • 47
user854301
  • 5,383
  • 3
  • 28
  • 37
0

The best way to do this, is through of IValidatableObject. See http://msdn.microsoft.com/en-us/data/gg193959.aspx

anmaia
  • 948
  • 6
  • 8
  • the IValidatableObject is for sure a good solution but based on the potential reusability a ValidationAttribute would make much more – WiiMaxx Mar 21 '14 at 15:52