Dec 14 2011

Globalization, Validation and Date/Number Formats in Asp.Net MVC

Category: MVC | Asp.netFrancesco @ 16:18

This post is a tutorial about how to handle number and date formats in different cultures on both client and server side in Asp.Net Mvc. A good tutorial about the Internationalization of web site contents through resource files can be found here.

On the server side, cultures, and culture aware date and number formats are properly handled automatically by the asp.net framework both in WebForms and in Mvc through the culture attribute of the <globalization/> tag of the Web.Config. For instance, <globalization culture=’en-US’/> forces the use of the United States culture in all Web requests. With culture=’auto (the default) a different culture is used in each request because the culture is taken from the culture settings of the Browser that issued the request. The uiCulture attribute of the globalization tag controls the language of the resource files in a completely analogous way(see here for more details).

Both the culture and the uiCulture attributes work by autonatically setting the CurrentCulture and CurrentUICulture of the thread that is serving the current request with something like:

System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");

If we use Client Validation, we are forced to reflect the server side culture settings also on the client side, otherwise numbers and dates are not validated correctly on the client and may cause a well formatted form to fail validation.

if we use MicrosoftaAjax and MicrosoftMvcValidation the server culture is easily reflected on the client with:

<% AjaxHelper.GlobalizationScriptPath = "http://ajax.microsoft.com/ajax/4.0/1/globalization/"; %>
<%: Ajax.GlobalizationScript() %>

Where the link http://ajax.microsoft.com/ajax/4.0/1/globalization/ is a public directory containing the globalization scripts for all supported cultures.

If we use Mvc 3 with the jQuery validation plugin, we need an external globalization library, since globalization is not supported natively, neither by the jQUery validation plugin nor by the interface layer between Mvc3 and the jQuery validation plugin. A good choice is the Globalize library.

This library furnishes the javascript methods to parse and format dates and numbers with the rules of a given culture: we need to connect it with both the server side culture and with the validation rules of the of the jQuery validation plugin to globalize our application.

In the Mvc Controls Toolkit I wrote a simple extension method that do this job:

private static string script = @"
        <script type='text/javascript' src='{0}'></script>
        <script type='text/javascript'>
            jQuery.global = Globalize;
            $.validator.methods.number = function (value, element) {{
                if (value == '' || !isNaN(jQuery.global.parseFloat(value))) {{
                    return true;
                }}
                return false;
            }}
            $.validator.methods.date = function (value, element) {{
                if (value == '' || !isNaN(jQuery.global.parseDate(value))) {{
                    return true;
                }}
                return false;
            }}
           
            $(document).ready(function () {{
                jQuery.global.culture('{1}');
                var culture=Globalize.culture();
                culture.calendar.patterns['G']=culture.calendar.patterns.d+' '+culture.calendar.patterns.T;
            }});
public static MvcHtmlString GlobalizationScript(this HtmlHelper htmlHelper, string globalizationFolder = "~/Scripts/cultures/", string localizationFolder = "~/Scripts/localization/")
{
    string cultureName = System.Threading.Thread.CurrentThread.CurrentCulture.Name;

    string culturePath = string.Format("{0}globalize.culture.{1}.js", globalizationFolder, cultureName);
    if (culturePath[0] == '~') culturePath = UrlHelper.GenerateContentUrl(culturePath, htmlHelper.ViewContext.HttpContext);

    return MvcHtmlString.Create(string.Format(script, culturePath, cultureName));

}

The above extension method does what follows:

  1. Adds a reference to the javascript globalization file associated with the same culture that is set on the server.
  2. Redefines the jQuery plugin rules for the right format of numbers and dates in terms of the parsing functions of the Globalize library
  3. Sets the default culture of the client to the same culture set on the server side.
  4. Defines the ‘G’ date format (short date plus long time) that is not among the standard formats provided by the Globalize library. This is an important step since the ‘G’ format is the default .Net format for dates.

Further globalization settings are needed if we use also the jQuery DateTimePicker. In the Mvc Controls Toolkit such settings are provided with the extension method below:

private static string scriptDataPicker = @"
            <script type='text/javascript' src='{0}'></script>          
            
        ";
public static MvcHtmlString JQueryDatePickerGlobalizationScript(this HtmlHelper htmlHelper, string cultureName=null, string globalizationFolder = "http://jquery-ui.googlecode.com/svn/trunk/ui/i18n/")
        {
            if (cultureName==null)
                cultureName = System.Threading.Thread.CurrentThread.CurrentCulture.Name.Substring(0, 2);
            string culturePath = string.Format("{0}jquery.ui.datepicker-{1}.js", globalizationFolder, cultureName);
            if (culturePath[0] == '~') culturePath = UrlHelper.GenerateContentUrl(culturePath, htmlHelper.ViewContext.HttpContext);


            return MvcHtmlString.Create(string.Format(scriptDataPicker, culturePath));

        }

It takes the globalization javscript file assocaited with the same culture that is set on the server from the public url http://jquery-ui.googlecode.com/svn/trunk/ui/i18n/ (see here).

Summing up we may globalize our web application by adding just:

<script type='text/javascript' src="@Url.Content("~/Scripts/globalize.min.js")"></script>
@Html.GlobalizationScript()
@Html.JQueryDatePickerGlobalizationScript()

However, this is not enough to have all our validation rules working properly! In fact the client side code of the RangeAttribute still uses the standard javascript parsing functions for numbers and dates instead of their globalized counterparts.

The Mvc Controls Toolkit autodetects the gloabalization library used, if any, and uses its parsing functions in all client side validation rules. Therefore, if we use the Mvc Controls Toolkit the few lines of code I showed before are enough to put in place the globalization of the whole validation library.

The FormatAttribute of the Mvc Controls Toolkit enables the developer to define the format to be used in the representation of a property on the client side, as shown in the example below:

[Format(Prefix="Date of birth is: ", ClientFormat = "d", NullDisplayText="No date of birth available")]
public DateTime? BirthDate { get; set; }

The “d” format just displays the date without the time in its short format, that depends on the culture that is set. We can use other standard formats like the  “d” format or we can specify a custom format by writing something like “M/d/yyyy” where M represents the month number, d the day number and y a digit of the year.

The Prefix is added before the formatted value just in case the helper we are rendering supports a display-only mode. In particular it is used with the display only helper _D. However it is used also with the TypedTextBox and with the TypedEditDisplay when they are in display mode. Also the NullDisplayText is used just in display-only mode.

We can specify also a Postfix to add after the formatted value.

If we set the ExtractClientFormat property of the FormatAttribute to true(the default is false) when the ClientFormat is not specified, both Prefix, Postfix and ClientFormat are extracted from the DataFormatString property, that specifies the format to be used on the server side,  according to the rule: “Prefix{0:ClientFormat}Postfix”. Therefore, in the above example we obtain the same effect by specifying DataFormatString=”Date of birth is: {0:d}”.

If ClientFormat is specified but DataFormatString is not, DataFormatString is always computed from Prefix, ClientFormat, and Postfix according to the same rule.

Prefix and Postfix can be also taken from a resource file by using a different constructor of the FormatAttribute that accepts the type of the resource file and two resource keys as first parameters, as shown belowe:

[Format(typeof(Resource1), "BirthdayPrefixKey", "BirthdayPostfixKey", ClientFormat = "d")]
public DateTime? BirthDate { get; set; }

This way, the strings can be globalized as explained here.

Below the list of all supported date format:

Format Meaning "en-US"
f Long Date, Short Time dddd, MMMM dd, yyyy h:mm tt
F Long Date, Long Time dddd, MMMM dd, yyyy h:mm:ss tt
G Short Date, Long Time M/d/yyyy h:mm:ss tt
t Short Time h:mm tt
T Long Time h:mm:ss tt
d Short Date M/d/yyyy
D Long Date dddd, MMMM dd, yyyy
Y Month/Year MMMM, yyyy
M Month/Day yyyy MMMM

The above table adds just the G format to the analogous table reported in the Globalize library. Both the custom date format and all number formats supported in the  Mvc Controls Toolkit are the exactly the same ones of the Globalize library.

That’s all! Examples of globalization settings and formatting can be found in the binary distribution in the download area of the Mvc Controls Toolkit web site.

Stay Tuned! Next post will be about the customization and globalization of validation errors!

Francesco

Tags: , ,