Multi-lingual form validation using MVC DataAnnotation attributes and Sitecore – Part 3 (Custom validation, soup to nuts)

So you wanna provide a custom validation data annotation that would function in both the server and client validation arena?

international

Below we have a scenario where there are two form fields highlighted, Password and VerifyPassword. Imagine that we would like to make the VerifyPassword property required IF the Password field was filled out. [This could exist, for example, on a ‘change password’ form.]

The DataAnnotation provided within ASP.NET MVC don’t have a built-in attribute to support this so we will build one based on the Required attribute.

1) Create a custom attribute class that inherits from RequiredAttribute and IClientValidatable. If either of these concepts are new, please see Part 1 and Part 2 of the series.

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
    public class RequiredIfPopulated : RequiredAttribute, IClientValidatable
    {
        public  string PropertyName { get; set; }

        public RequiredIfPopulated(string propertyName)
        {
            PropertyName = propertyName;
        }

        protected override ValidationResult IsValid(object value, ValidationContext context)
        {
            Object instance = context.ObjectInstance;
            Type type = instance.GetType();
            Object propertyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
            if (propertyvalue != null)
            {
                ValidationResult result = base.IsValid(value, context);
                return result;
            }
            return ValidationResult.Success;
        }

        public override string FormatErrorMessage(string name)
        {
            return Translate.Text(base.FormatErrorMessage(name));
        }

        public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            var clientValidationRule = new ModelClientValidationRule()
            {
                ErrorMessage = FormatErrorMessage(ErrorMessage),
                ValidationType = "requiredif"
            };

            clientValidationRule.ValidationParameters.Add("other", OtherProperty));

            return new[] { clientValidationRule };
        }
    }

Something new added to the class is the IsValid override. This gets the value of the parameter, PropertyName (in this case, Password), and validates that, if it is populated, that the source property (i.e. VerifyPassword) is also populated. If it is not, the validation fails on the server side.

The GetClientValidationRules method is similar to Part 2‘s CompareTranslated custom attribute. The primary difference is that the “ValidationType” is defined as ‘requiredif’ — a validation rule not available in jQuery.validate.js. You can name this new ValidationType anything javascript-acceptable (i.e. no script keywords), but it must be 100% lowercase.

2) Decorate the source property (VerifyPassword) with the new custom attribute.

[MinLengthTranslated(6, ErrorMessage = "PasswordMinLength")]
public string Password {get; set;}

[RequiredIfPopulated("Password", ErrorMessage = "VerifyPasswordRequired")]
[CompareTranslated("Password", ErrorMessage = "VerifyPasswordInvalid")]
public string VerifyPassword {get; set;}

3) Now, for the javascript.

$.validator.addMethod(
    "requiredif",
    function (val, element, other) {
        var target = $(other);
        return target.val().length == 0 || $.trim(val).length > 0 || (target.val().length > 0 && $.trim(val).length > 0);
    });

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

$(".myform").data('validator', null);
$.validator.unobtrusive.parse($(".myform"));

First, we have to add a new function to jQuery.validator called ‘requiredif’ (exactly the same name as the ValidationType declared in GetClientValidationRules()). This function is added to the validation list with $.validator.addMethod and contains validation logic in javascript. The first param of the function is the value of the field being evaluated (VerifyPassword). The second is the VerifyPassword element, and the third is the ‘other’ parameter, in this case, Password.

Second, we have to add an adapter to $.validator.unobstrusive.adapters, using the ‘add; function, passing in: a) the name of the validator; b) is the ‘other’ parameter, in this case, Password. … remember the param “other” we declared in GetClientValidationRules()? Here’s one place we are using it. c) Finally, the method ties the rule and message for the attribute into the unobtrusive validator.

And last but not least, we have two very important lines of code. Your page and custom client validation may run just fine without it. It all depends on how you load the previous two snippets of javascript. Here’s the trick … they need to be loaded after the 3 jquery includes (defined in Part 2) but prior to DOM ready and certainly NOT in (document).ready.

These two lines of code first, remove the previous set of validators, and then reload them. In effect, they have a chance to grab all the ones that were missed the first time around.

Stay tuned for future posts on fluent validation and perhaps some validator troubleshooting.

Advertisements

Multi-lingual form validation using MVC DataAnnotation attributes and Sitecore – Part 2 (Client validation)

So you wanna extend your custom MVC data annotations for form validations for client-side validation?

internationalI suggest that you read Part 1 for context and to ensure that you have the prerequisites to fully understand the series.

In the first post of this series, we built custom attributes to allow the language translation of the error messaging. These custom attributes need to be extended to support client validation.

1) Below is the data model properties decorated with custom attributes that provide translated error messaging (via Sitecore.Globalization.Translate and the dictionary domain) for server validation:

public class RequiredTranslated : RequiredAttribute
    {
        public override string FormatErrorMessage(string name)
        {
            return Translate.Text(base.FormatErrorMessage(name));
        }
    }

public class MinLengthTranslated : MinLengthAttribute
    {
        public MinLengthTranslated(int length) : base(length) { }

        public override string FormatErrorMessage(string name)
        {
            return Translate.Text(base.FormatErrorMessage(name));
        }
    }

public class CompareTranslated : CompareAttribute
    {
        public CompareTranslated(string otherProperty) : base(otherProperty) { }

        public override string FormatErrorMessage(string name)
        {
            return Translate.Text(base.FormatErrorMessage(name));
        }
    }

These classes inherit from their ‘non-translated’ equivalents, “Required”, “MinLength”, and “Compare.”

To improve on this translated server validation, let’s add translated client validation.

Each custom attribute class will inherit from IClientValidatable. IClientValidatable requires implementation of a method called GetClientValidationRules() which ensures the propagation of the validation rule(s) within the validation markup and script.

RequiredAttribute -> RequiredTranslatedAttribute
Adding the method to RequiredTranslated is straightforward; implement the method for IClientValidatable and pass the translated text for the error messaging to the front-end.

MinLength -> MinLengthTranslated
For MinLengthTranslated, the front-end not only needs the error message but also the minimum length to properly validate with javascript (More on the javascript in the next section).

Compare -> CompareTranslated
CompareTranslated elevates the complexity of implementing IClientValidatable because we need to pass an existing property thru to the validator (in this case, the VerifyPassword property value needs to be evaluated against the Password property value). The script needs to know in what parameter the property will be passed  (below, that is the ‘other’ param). We also need to declare which validation rule (i.e. ValidationType) of jquery.validate that this property matches to.  In this case, it matches the jquery.validate rule of “equalto”.

2) Implement IClientValidatable for each custom attribute.

public class RequiredTranslated : RequiredAttribute, IClientValidatable
    {
        public override string FormatErrorMessage(string name)
        {
            return Translate.Text(base.FormatErrorMessage(name));
        }

        public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            return new[] { new ModelClientValidationRequiredRule(Translate.Text(ErrorMessage)) };
        }
    }

public class MinLengthTranslated : MinLengthAttribute, IClientValidatable
    {
        public MinLengthTranslated(int length) : base(length) { }

        public override string FormatErrorMessage(string name)
        {
            return Translate.Text(base.FormatErrorMessage(name));
        }

        public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            return new[] { new ModelClientValidationMinLengthRule(Translate.Text(ErrorMessage), Length) };
        }
    }

public class CompareTranslated : CompareAttribute, IClientValidatable
    {
        public CompareTranslated(string otherProperty) : base(otherProperty) { }

        public override string FormatErrorMessage(string name)
        {
            return Translate.Text(base.FormatErrorMessage(name));
        }

        public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            var clientValidationRule = new ModelClientValidationRule()
            {
                ErrorMessage = FormatErrorMessage(ErrorMessage),
                ValidationType = "equalto"
            };

            clientValidationRule.ValidationParameters.Add("other", OtherProperty));

            return new[] { clientValidationRule };
        }
    }

3) What happens when this new “GetClientValidationRules()” method is called when building the HTML for the page and what javascript is required?

In ASP.NET MVC 3 a new feature called “Unobtrustive Client Validation” was added which uses jQuery and jQuery.validate to perform validation based on the data annotations (and in our case, custom attributes). There are plenty of blogs that describe.

What do you need to use this?

First, turn on the functionality in the appSettings.config file:

configvalidatesettings

You will also need to include three javascript files (or their min equivalent) on your page:

1) jQuery
2) jQuery Validate
3) jQuery Validate Unobtrusive (plugin for ASP.NET MVC by Microsoft)

It all works as smooth as butter.  Love it.

What if you want to do some custom validation??  Glad you asked!  Check out Part 3 of this series, Custom Validation, Soup to Nuts.