Loading...

Asp.net JQuery Unobtrusive Conditional Validation

Software
Jan 1, 2012
#csharp
#js
Avatar
Author
Matt Kruskamp

I ran into an issue where I was doing Asp.Net Mvc validation on one of my forms. I wanted it so that three things were required only if a checkbox was checked. I know that out of the box this isn’t supported so I googled around until I found this article:

http://blogs.msdn.com/b/simonince/archive/2011/02/04/conditional-validation-in-asp-net-mvc-3.aspx

The article goes over conditional validation with a custom validator and some extension code so that it works client side. Good stuff. Thanks Simon Ince.

It worked like a champ. Note: THIS IS NOT MY CODE. I am putting it in here so that people don’t have to download the solution file from his website to analyze and re-produce the solution.

public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
    private RequiredAttribute _innerAttribute = new RequiredAttribute();

    public string DependentProperty { get; set; }
    public object TargetValue { get; set; }

    public RequiredIfAttribute(string dependentProperty, object targetValue)
    {
        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 (!_innerAttribute.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 = "requiredif",
        };

        string depProp = BuildDependentPropertyId(metadata, context as ViewContext);

        // find the value on the control we depend on;
        // if it's a bool, format it javascript style
        // (the default is True or False!)
        string targetValue = (this.TargetValue ?? "").ToString();
        if (this.TargetValue.GetType() == typeof(bool))
            targetValue = targetValue.ToLower();

        rule.ValidationParameters.Add("dependentproperty", depProp);
        rule.ValidationParameters.Add("targetvalue", targetValue);

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

Here is the Javascript:

.validator.addMethod('requiredif',
    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 =
            controltype === 'checkbox' ?
            control.attr('checked').toString() :
            control.val();

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

        return true;
    }
);

$.validator.unobtrusive.adapters.add(
    'requiredif',
    ['dependentproperty', 'targetvalue'],
    function (options) {
        options.rules['requiredif'] = {
            dependentproperty: options.params['dependentproperty'],
            targetvalue: options.params['targetvalue']
        };
        options.messages['requiredif'] = options.message;
    });

Hope this helps.

3 min read
Share this post:

Comments

comments powered by Disqus

Related Articles

All articles
Jan 1, 2012

Medistack

Medistack is a communication solution for medical professionals to collaborate in a secure environment. It supports groups, posts, audio/video processing, and teams.

Nov 18, 2011

NUVI

Social media analytics in real time. Nuvi is a powerful platform for analyzing, monitoring, and engaging with social media. Nuvi quickly weeds through the noise of the internet and delivers the most pertinent information.

The pixel mine

I'm currently the Co-Founder / CTO with an amazing team at Rentler. We build property management software for independant landlords and their renters using a plethora of technologies.

View work
Illustration Illustration
Top