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.

Multi-lingual form validation using MVC DataAnnotation attributes and Sitecore – Part 1 (Custom attributes)

So you wanna use MVC data annotations for form validation in a multi-lingual Sitecore site?

internationalWith Sitecore wrapping a firm grip around MVC, it is important to have a solid validation strategy for your data models.  From home-grown controller action validation to fluent validation (new post coming soon …), there are a variety of ways to tackle it.  Making the site multi-lingual and with client validation adds some layers of complexity.

Much of the functionality has been around since MVC 3.  So, why create a new post about it now?  … as I was working on a time-sensitive project, I wasn’t able to find all of the answers to my translation questions in one (or two or three) spots.  I’m striving to knit all that’s out there together when one questions is asked.

Prerequisites:

In this post, I’ll be providing tips and tricks for using MVC DataAnnotation attributes to decorate your data model properties for validation while writing the error messages in the user’s chosen language using Sitecore.Globalization.Translate.Text().

1) Create your data model with desired DataAnnotation attributes

[DisplayName("Email Address")]
[Required(ErrorMessage = "Email address is required.")]
public string EmailAddress {get; set;}

[DisplayName("Password")]
[MinLength(6, ErrorMessage = "Password must be a minimum of 6 characters.")]
public string Password {get; set;}

[DisplayName("Verify Password")]
[Compare("Password", ErrorMessage = "Verify Password must match Password.")]
public string VerifyPassword {get; set;}

2) Create your form that supports your data model

@using (Html.BeginForm()) {

   @Html.ValidationSummary()
   @Html.LabelFor(x=>x.EmailAddress) 
   @Html.TextBoxFor(x => x.EmailAddress) 
   @Html.ValidationMessageFor(x => x.EmailAddress) 

   @Html.LabelFor(x=>x.Password) 
   @Html.PasswordFor(x => x.Password) 
   @Html.ValidationMessageFor(x => x.Password) 

   @Html.LabelFor(x=>x.VerifyPassword) 
   @Html.PasswordFor(x => x.VerifyPassword) 
   @Html.ValidationMessageFor(x => x.VerifyPassword) 

   @Html.ActionLink("Save", "SaveAction", "MainController")
}

screen1

Nice! Server validation works well, however, we want multi-lingual and the attribute argument (in this case, the ErrorMessage) “must be a constant expression, typeof expression, or array creation expression of an attribute parameter type”. It must be a compile-time constant … Sitecore.Globalization.Translate.Text() or any method cannot be used as a replacement for the string within the attribute argument.

3) Create custom attributes to support translating text for multi-lingual sites. Creating custom attributes provide the ability to override and customize methods of the attribute from which you are inheriting.

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.”

4) Decorate your data model properties with your custom attributes. Please make sure that you swap out the message for a translated key that matches your message from your domain dictionary.

For example, “EmailAddressRequired” is my domain dictionary key for the string, “Email Address is required” (and its various other language versions).

[RequiredTranslated(ErrorMessage = "EmailAddressRequired")]
public string EmailAddress {get; set;}

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

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

With this small step of adding custom attributes, you have an easy way to handle multi-lingual server validation.

If you’d like to see how you can use these custom attributes with client validation, see part 2.