ASP.NET MVC 4 jQuery Validation Globalization

Going thru the ASP.NET MVC 4 intro on www.asp.net/mvc/tutorials/mvc-4/getting-started-with-aspnet-mvc4/adding-a-model, I thought that I couldn’t believe what I saw.

In this intro, a MVC Web application is built, having a page to enter movie data, including a date and a price. The price is a decimal value.

Now on my machine, the culture is en-US, while the UI culture is de-DE. Saving a price of 9.78 (where the point is the decimal separator), the app writes 978 into the database. And trying to save a date in German format (dd.MM.yyyy) leads to an “invalid date format” error on the client. On the other hand, the date entered in en-US format (MM/dd/yyyy), was displayed in German format after reading.

The kind of problem is not new to me. Working with UI apps (and not just in that case), one always has to consider the culture settings. What causes my surprise was the fact that still there seems to be no built-in support to handle this issue. Even ASP.NET MVC 4 is not able to handle this itself, making sure the client side validator validates the values using the correct culture settings :-(

Well, I looked around a little bit, and found a solution. It doesn’t make me happy, but it works.

First, I extended the system.web section of the web.config to enable ASP.NET to set the UI culture and culture for a Web page automatically, based on the values that are sent by a browser:

<globalization enableClientBasedCulture="true" 
  culture="auto:en-US" 
  uiCulture="auto:en"/>

And then I extended the view with this code:

<script src="~/Scripts/globalize.js" type="text/javascript">
</script>
<script src="~/Scripts/globalize.culture.de-DE.js" type="text/javascript">
</script>
<script src="~/Scripts/globalize.culture.en-US.js" type="text/javascript">
</script>

<script>
  $.validator.methods.number = function (value, element) {
    return this.optional(element) ||
        !isNaN(Globalize.parseFloat(value));
  }

  $.validator.methods.date = function (value, element) {
    return this.optional(element) ||
        Globalize.parseDate(value);
  }

  $(document).ready(function () {
    Globalize.culture('@System.Threading.Thread.CurrentThread.CurrentCulture');
  });
</script>

<script>
  jQuery.extend(jQuery.validator.methods, {
    range: function (value, element, param) {
      //Use the Globalization plugin to parse the value        
      var val = Globalize.parseFloat(value);
      return this.optional(element) || (
          val >= param[0] && val <= param[1]);
    }
  });
</script>

Update: Please have a look at the comments below. Dan shared his solution for the situation when the user is in a browser that has native datepicker/number support and an unknown culture (i.e. you don’t want to account for and load ALL globalize.js culture files. Thanks, Dan!

Since this code is needed by several views, I put it into a partial view and included it into the Scripts section using

@Html.Partial("_PartialViewName");

In this context I think it’s worth it to mention that the link to the jQuery Globalization plugin https://github.com/nje/jquery-glob, which is mentioned in the Web several times, works, but the new location, where it should reside now (https://github.com/jquery/jquery-global, is not valid. I found the required files on https://github.com/jquery/globalize and copied the required files from the lib directory.

12 thoughts on “ASP.NET MVC 4 jQuery Validation Globalization

  1. Indeed thanks a lot!

    A little change/extra:
    - System.Threading.Thread.CurrentThread.CurrentCulture added to pick a culture in any language (not only German).
    - If you want datepicker then you need some extra lines (get jquery-ui-i18n from NuGet). LCID because you don’t need jquery-ui-i18n for 1033. And System.Threading.Thread.CurrentThread.CurrentCulture.Parent because for example nl-NL is not part of jquery-ui-i18n. So you need .Parent (= nl).

    And a wish: still no proper solution for the error messages (xyz is not a number etc.) not translated (in English only).

    Regards, Jan

    The change/extra:

    <script src="@Url.Content("~/Scripts/globalize.js")" type="text/javascript"></script>
    <script src="/Scripts/cultures/globalize.culture.@(System.Threading.Thread.CurrentThread.CurrentCulture).js" type="text/javascript"></script>
    @if (Session.LCID != 1033)
    {
        <script src="@Url.Content("~/Scripts/jquery-ui-i18n.min.js")" type="text/javascript"></script>
        <script type="text/javascript">
            $(document).ready(function () {
                $('.date-pick').datepicker($.datepicker.regional["@(System.Threading.Thread.CurrentThread.CurrentCulture.Parent)"]);
            });
        </script>
    }
    <script>
        $.validator.methods.number = function (value, element) {
            return this.optional(element) ||
                !isNaN(Globalize.parseFloat(value));
        }
    
        $.validator.methods.date = function (value, element) {
            return this.optional(element) ||
                !isNaN(Globalize.parseDate(value));
        }
    
        $(document).ready(function () {
            Globalize.culture('@System.Threading.Thread.CurrentThread.CurrentCulture');
        });
    </script>
    
    <script>
        jQuery.extend(jQuery.validator.methods, {
            range: function (value, element, param) {
                //Use the Globalization plugin to parse the value        
                var val = Globalize.parseFloat(value);
                return this.optional(element) || (
                    val >= param[0] && val <= param[1]);
            }
        });
    </script>
    
  2. Great, concise and “straight to the point” post, it brought me on to the path to solve a very annoying problem.

    I had to change a bit the date validation function though: Globalize.parseDate returns a valid date object when validation succeeds and null when it fails.

    isNan(null) return false, so your original code always validates any string. Unless I’m missins something…

    Anyway, If I replace the !isNan with != null it all works great (Chrome, Firefox and IE9)

    Thanks again!!!!

    • Mario,

      You’re right. Validating a date using isNaN does not make sense. From what I saw also the ‘!== null’ is not needed. I’ve changed the code.

      Stefan

  3. I had to change the date validator to this for it to work:

    $.validator.methods.date = function(value, element) {
    	return this.optional(element) || Globalize.parseDate(value);
    };

    Wrapping the the parseDate in a !isNaN just returns true for all values. Could it be that the Globalize framework has changed since this post was made? With the above code and Globalize.js downloaded today the parseDate function returns null when the value can not be parsed and null is falsy so no !isNaN is needed…

    /Dan

  4. Hi again!
    Just wanted to share my solution for the situation when the user is in a browser that has native datepicker/number support and an unknown culture (i.e. you don’t want to account for and load ALL globalize.js culture files):

    $.validator.methods.number = function(value, element) {
    	if ($(element).attr('type') === 'number' && Modernizr.inputtypes.number) {
    		return true;
    	}
    
    	return this.optional(element) ||
    		!isNaN(Globalize.parseFloat(value));
    };
    
    $.validator.methods.date = function (value, element) {
    	if ($(element).attr('type') === 'date' && Modernizr.inputtypes.date) {
    		return true;
    	}
    	return this.optional(element) || Globalize.parseDate(value);
    };
    
    jQuery.extend(jQuery.validator.methods, {
    	range: function (value, element, param) {
    		if ($(element).attr('type') === 'number' && Modernizr.inputtypes.number) {
    			return this.optional(element) || (value >= param[0] && value = param[0] && val <= param[1]);
    	}
    });

    These versions of the validators just skips the format validation if the browser has native support för type="date/number" in which case the user can not enter a value in an erroneous format. This solution has a dependency towards the Modernizr.js library.

  5. The one for range should be like this:

    jQuery.extend(jQuery.validator.methods, {
    range: function (value, element, param) {
    if ($(element).attr(‘type’) === ‘number’ && Modernizr.inputtypes.number) {
    return this.optional(element) || (value >= param[0] && value = param[0] && val <= param[1]);
    }
    });

    • Dan,

      Thanks for your input!

      What I do not understand is the assignment “value = param[0]” in the return statement. Even a comparison to value === param[0] is not required here from my point of view, because I want to make sure a value is inside a given range. I do not want to make sure the value is equal to the min value.

      Stefan

  6. It’s a good article.But I have a problem with different culture.

    When I change CurrentCulture to “fa-IR” that decimal separator for this culture is (“/”).When I put for example 55/02 for Price I got a validation Error “The field Price must be a number”

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>