Client Side Validation

Each culture has different standard of number formatting. Learn how validation of decimals and dates in multicultural web application can be done correctly.

Trips form application almost done, we still missing client side validation to complete the validation localization process.

Default client side validation scripts already provided in “Pages/Shared/_ValidationScriptsPartial.cshtml”. Simply include validation partial into “Trips.cshtml” file directly after closing the form tag:

Trips.cshtml
@page

@model MyTrips.Pages.TripsModel

@{
    ViewData["Title"] = Localizer.Text("Trips");
    var cultureInfo = System.Globalization.CultureInfo.CurrentCulture;
}

<h2>@ViewData["Title"]</h2>

<form method="post">
    <p>@Localizer.Text("Please fill below your travel info:")</p>
    <div asp-validation-summary="All" class="alert-danger"></div>
    <div class="form-group">
        <label asp-for="MyTrip.Destination"></label>
        <input asp-for="MyTrip.Destination" class="form-control" />
        <span asp-validation-for="MyTrip.Destination" class="text-danger"></span>
    </div>

    <div class="form-group">
        <label asp-for="MyTrip.TravelDate"></label>
        <input asp-for="MyTrip.TravelDate" class="form-control" />
        <span asp-validation-for="MyTrip.TravelDate" class="text-danger"></span>
    </div>

    <div class="form-group">
        <label asp-for="MyTrip.TicketPrice"></label>
        <input asp-for="MyTrip.TicketPrice" class="form-control" />
        <span asp-validation-for="MyTrip.TicketPrice" class="text-danger"></span>
    </div>
    <button type="submit" class="btn btn-primary">@Localizer.Text("Submit")</button>
</form>


@section Scripts{ 
    <partial name="_ValidationScriptsPartial" />
}

Validation scripts will show validation messages after switching from input control to another one, without the need to click “Submit” button.

Till noe everything seems to be okay, but if you type decimal numbers in non-English cultures (e.g. 15,5), you will see validation error messages; Because some non-English cultures uses comma ( , ) as decimal separator, while it is ( . ) in English cultures.

Client side validation depends on “jquery.validate.js”, it is developed considering English standards and it uses dot ( . ) as decimal separator! In order to fix this; we have to add more scripts to our “_ValidationScriptsPartial”.

Client Side Validating Decimals in Non-English Cultures

Download and install the following packages:

Modify “_ValidationScriptsPartial.cshtml” file to include newly installed libraries:

_ValidationScriptsPartial.cshtml
<environment include="Development">
    <script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</environment>
<environment exclude="Development">
    <script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.17.0/jquery.validate.min.js"
            asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js"
            asp-fallback-test="window.jQuery && window.jQuery.validator"
            crossorigin="anonymous"
            integrity="sha384-rZfj/ogBloos6wzLGpPkkOr/gpkBNLZ6b6yLy4o+ok+t/SAKlL5mvXLr0OXNi1Hp">
    </script>
    <script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.9/jquery.validate.unobtrusive.min.js"
            asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
            asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"
            crossorigin="anonymous"
            integrity="sha384-ifv0TYDWxBHzvAk2Z0n8R434FL1Rlv/Av18DXE43N/1rvHyOG4izKst0f2iSLdds">
    </script>
</environment>

<!-- cldr scripts (needed for globalize) -->
<script src="~/lib/cldrjs/dist/cldr.js"></script>
<script src="~/lib/cldrjs/dist/cldr/event.js"></script>
<script src="~/lib/cldrjs/dist/cldr/supplemental.js"></script>

<!-- globalize scripts -->
<script src="~/lib/globalize/dist/globalize.js"></script>
<script src="~/lib/globalize/dist/globalize/number.js"></script>
<script src="~/lib/globalize/dist/globalize/date.js"></script>

<script src="~/lib/jquery-validation-globalize/jquery.validate.globalize.js"></script>

@inject Microsoft.AspNetCore.Hosting.IHostingEnvironment HostingEnvironment
@{
    string GetDefaultLocale()
    {
        const string localePattern = "lib\\cldr-data\\main\\{0}";
        var currentCulture = System.Globalization.CultureInfo.CurrentCulture;
        var cultureToUse = "en"; //Default regionalisation to use

        if (System.IO.Directory.Exists(System.IO.Path.Combine(HostingEnvironment.WebRootPath, string.Format(localePattern, currentCulture.Name))))
            cultureToUse = currentCulture.Name;
        else if (System.IO.Directory.Exists(System.IO.Path.Combine(HostingEnvironment.WebRootPath, string.Format(localePattern, currentCulture.TwoLetterISOLanguageName))))
            cultureToUse = currentCulture.TwoLetterISOLanguageName;

        return cultureToUse;
    }
}

<script type="text/javascript">
    var culture = "@GetDefaultLocale()";
    $.when(
        $.get("/lib/cldr-data/supplemental/likelySubtags.json"),
        $.get("/lib/cldr-data/main/" + culture + "/numbers.json"),
        $.get("/lib/cldr-data/supplemental/numberingSystems.json"),
        $.get("/lib/cldr-data/main/" + culture + "/ca-gregorian.json"),
        $.get("/lib/cldr-data/main/" + culture +"/timeZoneNames.json"),
        $.get("/lib/cldr-data/supplemental/timeData.json"),
        $.get("/lib/cldr-data/supplemental/weekData.json")
    ).then(function () {
        // Normalize $.get results, we only need the JSON, not the request statuses.
        return [].slice.apply(arguments, [0]).map(function (result) {
            return result[0];
        });
    }).then(Globalize.load).then(function () {
        Globalize.locale(culture);
    });
</script>

Solution from: https://github.com/aspnet/Docs/issues/4076#issuecomment-366162798

These files contains defintions for all cultures and their numbering formats. The application will target the selected culture scripts to read numbering formats and apply it during client side validation.

Build and run the application, submit decimal numbers in different cultures and see how client side validation works.

Using Latin Numbering System in non-Latin Cultures

If the application will use numbers in non-Latin cultures (e.g. Arabic, Farsi, Hindu, etc.) model binding error messages like "Please use numbers for this field” will appear:

digit error

This is happening because of the numbering systems defined in "cldr-data" json files are not latin numbers. For example, Arabic cultures are using these digits “٠١٢٣٤٥٦٧٨٩” and they are defined in “numberingSystems.json” file. However, the numbers we are trying to input in the form are Latin numbers “0123456789”. That is why model-binding error message appears.

The story of numbers and cultural exchange is pretty old; Arabs has founded the numbers as we know it now as Latin numbers “0123456789”, but later on Arab cultures started using Hindu numbering system “٠١٢٣٤٥٦٧٨٩” and the original Arabic numbering system “0123456789” became the standard for Latin cultures. Nevertheless, it worth to mention that currently almost all computer/smartphone users in Arabic cultures are using Latin numbers “0123456789” by default. 

Enough history info :) let’s have a look at numbering systems in "cldr-json" data files. Open “~/lib/cldr-data/main/ar/numbers.json” file, you will see that default numbering system is “arab”:

numbers.json
"numbers": {
    "defaultNumberingSystem": "arab",
    "defaultNumberingSystem-alt-latn": "latn",
    "otherNumberingSystems": {
        "native": "arab"
    },

Notice: If you are targeting country based culture e.g.: “ar-EG” check related json file for that culture.
e.g.: “~/lib/cldr-data/main/ar-EG/numbers.json”

The referenced numbering systems are defined in “~/lib/cldr-data/supplemental/numberingSystems.json”:

numberingSystems.json
"arab": {
    "_digits": "٠١٢٣٤٥٦٧٨٩",
    "_type": "numeric"
    },

So if the application will support only Latin numbering system “0123456789”, just open “~/lib/cldr-data/main/ar/numbers.json” or the country related file and modify “defaultNumberingSystem” and “native” values to be “latn”:

numbers.json
"numbers": {
    "defaultNumberingSystem": "latn",
    "defaultNumberingSystem-alt-latn": "latn",
    "otherNumberingSystems": {
        "native": "latn"
    },

Demo Project: My trips application

Source code on github: MyTrips