Friday, 8 March 2013

Localised Form Validation in ASP.Net MVC

I've recently been working on a web application that required localisation - the display of translated copy depending on which country the user was visiting from. There was a different sub-domain for each country which was used to identify the appropriate one.

It was also a requirement that the editor's of the website could update the translated copy from a simple back-office tool.

For most of the copy of the website this was fairly straightforward to set up. I had a database table called Texts that contained three columns: Country, Key and Value. And hence by looking up for a particular country and key I could get the text to display.

To ensure this was performant in practice what I did for each web request was to look up all the keys and values for the appropriate market and populate a dictionary object. The look-up was cached using so I was only hitting the database when I needed to rather than on every request.

I then created a base view model that all other view models were derived from that looked like this:

    public abstract class BaseViewModel
        public BaseViewModel(IList<Text> dictionary)
            Dictionary = dictionary;

        public IList<Text> Dictionary { get; set; }

        public string GetText(string key)
            if (Dictionary != null && !string.IsNullOrEmpty(key))
                var text = Dictionary.Where(x => x.Key == key).SingleOrDefault();
                if (text != null)
                    return text.Value;

            return string.Empty;

With this in place, rendering the appropriate text from the view was a simple matter of calling the GetText method and passing the required key:


All very straightforward so far, but the piece that needed a bit more thought was how to handle form validation messages, supporting both client and server side methods. There's a fairly well established method using resource files, but in my case that wasn't ideal as I wanted to support the editor's making amends to the texts, and hence needed to have them in the database rather than baked into a resource.

First step was to apply the usual data annotations, but rather than hard coding the error messages I instead used the keys from my translations table, e.g.

    [Required(ErrorMessage = "contact_validation_first_name_required")]
    [StringLength(20, ErrorMessage = "contact_validation_first_name_length")]
    public string FirstName { get; set; }

In my view I set up the field as follows:

    @Html.EditorFor(m => m.FirstName)
    @Html.LocalizedValidationMessageFor(m => m.FirstName, Model.Dictionary)

LocalizedValidationMessageFor was a custom helper used to render the appropriate error message for the country, and was implemented as an extension method on the HtmlHelper:

    public static class LocalizationHelpers
        public static MvcHtmlString LocalizedValidationMessageFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IList<Text> dictionary)
            // Get HTML returned from base ValidationMessageFor method
            var html = ValidationExtensions.ValidationMessageFor(htmlHelper, expression);

            // Check we are looking at one that is flagging a validation error
            if (html != null && dictionary != null && html.ToString().Contains("field-validation-error"))

                // Get the key from the HTML (it contains a single span tag)
                var key = html.Substring(html.IndexOf(">") + 1).Replace("</span>", string.Empty);

  // Look up the text value based on the key from the passed dictionary
                var text = dictionary.Where(x => x.Key == key).SingleOrDefault();
                if (text != null)

          // Replace the key with the translation
                    var amendedHtml = html.ToString().Replace(key, text.Value);
                    return MvcHtmlString.Create(amendedHtml);

            return html;            

That worked nicely for server side validation messages, but the client side ones would still display the key rather than the translated text. To handle this scenario I added a further method the BaseViewModel which would return the part of the dictionary of key/value pairs as a JSON result:

    public string GetDictionaryAsJson(string stem = "")
        if (Dictionary != null)
            var serializer = new JavaScriptSerializer();
            return serializer.Serialize(Dictionary
                .Where(x => string.IsNullOrEmpty(stem) || x.Key.StartsWith(stem))
                .Select(x => new { Key = x.Key, Value = x.Value }));

        return string.Empty;

From within the view, I made a call to a javascript function, passing in this dictionary in JSON format:


That function looked like the following, where for each tye of validation I was using (required, string length etc.) the key that was currently rendered was replaced with the appropriate translated text retrieved from the dictionary:

    function localizeClientSideValidationMessages(dictionary) {

        // Convert to JSON arry
        dictionary = eval(dictionary);

        // Localize fields (need to call for each type of validation)
        localizeFieldValidation(dictionary, "data-val-required");
        localizeFieldValidation(dictionary, "data-val-length");
        localizeFieldValidation(dictionary, "data-val-regex");
        localizeFieldValidation(dictionary, "data-val-equalto");

        // Reparse form (necessary to ensure updates)


    function localizeFieldValidation(dictionary, validationAttribute) {
        // For each form element with validation attribute, replace the key with the translated text
        $("input[" + validationAttribute + "],select[" + validationAttribute + "],textarea[" + validationAttribute + "]").each(function (index) {
            $(this).attr(validationAttribute, getLocalizedValue(dictionary, $(this).attr(validationAttribute)));


    function getLocalizedValue(dictionary, key) {

 // Look up the value for the passed key
        for (var item in dictionary) {
            if (dictionary.hasOwnProperty(item)) {
                if (dictionary[item].Key == key) {
                    return dictionary[item].Value;

        return "";


No comments:

Post a Comment