BattlFrog BattlFrog - 4 days ago 5
C# Question

Custom validation class - How to make universal

I am attempting to create a custom validation attribute for my MVC application. My code as written, works great for the properties specified in the code. I now want to expand it, so it is more general, because I have 5 other properties I would like to use this same attribute on.

The general idea is if the specified other property is true, then the property attached to the attribute must be > 0.

I assume the way to do this is to create a constructor that accepts the value of the property and the value of the other property, but I can't seem to get it going. The specific problem I having is I can't find the correct way to pull in the needed values.

Here is what I have:

public class MustBeGreaterIfTrueAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var model = context.ObjectInstance as HistoryViewModel;
//ValidationResult result = null;

// Validate to ensure the model is the correct one
if (context.ObjectInstance.GetType().Name == null)
{
throw new InvalidOperationException(string.Format(
CultureInfo.InvariantCulture, "Context of type {0} is not supported. "
+ "Expected type HistoryViewModel",
context.ObjectInstance.GetType().Name));
}

// Here is the actual custom rule
if (model.HistoryModel.IsRetired == true)
{
if (model.CounterA == 0)
{
return new ValidationResult("Please enter more information regarding your History");
}
}
else if ( model.HistoryModel.IsRetired == true )
{
if ( model.ROCounter > 0 )

return ValidationResult.Success;
}

// If all is ok, return successful.
return ValidationResult.Success;

}

// Add the client side unobtrusive 'data-val' attributes
//public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
//{

//}

}


Thank you for you time.

Answer

I just did something similar in an attribute I called "RequiredIfTrueAttribute". This will get you the value of the other property in the model. Pass the other property name as a string into the custom attribute constructor.

public class RequiredIfTrueAttribute: ValidationAttributeBase
{

    public string DependentPropertyName { get; private set; }

    public RequiredIfTrueAttribute(string dependentPropertyName) 
        : base()
    {
            this.DependentPropertyName = dependentPropertyName;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        // Get the property we need to see if we need to perform validation
        PropertyInfo property = validationContext.ObjectType.GetProperty(this.DependentPropertyName);
        object propertyValue = property.GetValue(validationContext.ObjectInstance, null);

        // ... logic specific to my attribute

        return ValidationResult.Success;
    }


}

Now if only there was a way to pass the dependentPropertyName to the validation attribute without using a string...


Update:

In C# 6.0 there is now a way to call the dependentPropertyName without using a string. Simply use nameof(thePropertyName) and it will be replaced with the string. This happens in compile time so if you change the property name you will immediately know that you need to change this as well. Or, even better, if you do a Ctrl+R, Ctrl+R to rename the variable it will automatically rename the version inside the nameof as well. Awesome!

See: nameof (C# and Visual Basic Reference)

Comments