Conditional Validation in ASP.NET MVC – RangeIf

Standard

Simon Ince made a great post about conditional validation in mvc which he extended in his post conditional validation in asp.net mvc 3. Asp.net mvc validation is one of asp.net mvc’s great extensible points . The framework comes with a decent amount of validations attribute such as range, stringlength etc but sometimes we find ourselves wanting to perform a specify type of validation that the framework doesn’t support and conditional validation is a perfectly good example. This is one of those validation types that most people feel the asp.net mvc team should have included in the framework but hey, they cannot do it all,¬† so we look to ourselves and to good people like Simon to the rescue.

Simon goes into details on how to create a RequiredIf validation so I wont elaborate on that (just dive into the links above for info on RequiredIf validation). In using Simon Requiredif validation, I decided its would be great to build on it to be applicable to other rules like Rangeif, StringLenghtIf, etc, starting with RangeIf. The first thing I needed to do was define the signature of the validation attribute. To keep things consistent with Simon’s, I decided to go with the signature as follows for an age property on the person class-

[RangeIf(11, 25, "IsUKResident", false, ErrorMessage = "If you are not a UK  resident, your age has to be between 11 and 25")]

The next thing to do was to define the RangIf attribute class below , inheriting from RangAttribute
public class RangeIfAttribute : RangeAttribute , IClientValidatable
{
public string DependentProperty { get; set; }
public object TargetValue { get; set; }

public RangeIfAttribute(int minimum, int maximum, string dependentProperty

, object targetValue): base(minimum, maximum)
{
this.DependentProperty = dependentProperty;
this.TargetValue = targetValue;
}

protected override ValidationResult IsValid(object value,

ValidationContext validationContext)
{
// get a reference to the property this validation depends upon
var containerType = validationContext.ObjectInstance.GetType();
var field = containerType.GetProperty(this.DependentProperty);

if (field != null)
{
// get the value of the dependent property
var dependentvalue = field.GetValue(validationContext.ObjectInstance, null);

// compare the value against the target value
if ((dependentvalue == null && this.TargetValue == null) ||
(dependentvalue != null && dependentvalue.Equals(this.TargetValue)))
{
// match => means we should try validating this field
if (!base.IsValid(value))
// validation failed - return an error
return new ValidationResult(this.ErrorMessage, new[]

{ validationContext.MemberName });
}
}

return ValidationResult.Success;
}

public IEnumerable<ModelClientValidationRule> GetClientValidationRules

(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "propertydependencyrule",
};

string depProp = BuildDependentPropertyId(metadata, context as ViewContext);
string targetValue = (this.TargetValue ?? "").ToString();
if (this.TargetValue.GetType() == typeof(bool))
targetValue = targetValue.ToLower();

 

rule.ValidationParameters.Add("dependentproperty", depProp);
rule.ValidationParameters.Add("targetvalue", targetValue);
rule.ValidationParameters.Add("rule", "range");
rule.ValidationParameters.Add("ruleparam","["+Minimum+","+Maximum+"]");

yield return rule;
}

 

private string BuildDependentPropertyId(ModelMetadata metadata,

ViewContext viewContext)
{
// build the ID of the property
string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId

(this.DependentProperty);
// unfortunately this will have the name of the current field appended to the beginning,
// because the TemplateInfo's context has had this fieldname appended to it. Instead, we
// want to get the context as though it was one level higher (i.e. outside the current property,
// which is the containing object (our Person), and hence the same level as the dependent property.
var thisField = metadata.PropertyName + "_";
if (depProp.StartsWith(thisField))
// strip it off again
depProp = depProp.Substring(thisField.Length);
return depProp;
}
}

The code above is self explanatory so I wont elaborate. Note that we pass on the client rule and its parameters through rule.ValidationParameters in the GetClientValidationRules method. The next thing to do was create my client script (unobtrusive adapter) below
$.validator.addMethod('propertydependencyrule',
function (value, element, parameters) {
var id = '#' + parameters['dependentproperty'];

// get the target value (as a string,
// as that's what actual value will be)
var targetvalue = parameters['targetvalue'];
targetvalue =
(targetvalue == null ? '' : targetvalue).toString();

// get the actual value of the target control
// note - this probably needs to cater for more
// control types, e.g. radios
var control = $(id);
var controltype = control.attr('type');
var actualvalue = "";

switch(controltype)
{
case 'checkbox' :
actualvalue = control.attr('checked').toString(); break;
case 'select' :
actualvalue = $('option:selected',control).text(); break;
default:
actualvalue = control.val(); break;
}

// if the condition is true, reuse the existing
// required field validator functionality
var rule = parameters['rule']
var ruleparam = parameters['ruleparam']
if (targetvalue === actualvalue)
return $.validator.methods[rule].call(
this, value, element, ruleparam);

return true;
}
);

$.validator.unobtrusive.adapters.add(
'propertydependencyrule',
['dependentproperty', 'targetvalue', 'rule', 'ruleparam'],
function (options) {

 

options.rules['propertydependencyrule'] = {
dependentproperty: options.params['dependentproperty'],
targetvalue: options.params['targetvalue'],
rule: options.params['rule'],
ruleparam: eval(options.params['ruleparam']),
};
options.messages['propertydependencyrule'] = options.message;
});

That’s it. We now have our RangeIf validation attribute. Notice I modified Simon’s client side code to a generic propertydependencyrule so you can use propertydependencyrule for other propertydependency validation attributes like StringLenghtIf by passing along the max jquery.validator¬† rule and its parameters. In my next post, I will extend this to RegularExpressionIf, StringLenghtIf, and more. Stay tuned.

Advertisements

2 thoughts on “Conditional Validation in ASP.NET MVC – RangeIf

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s