Click here to Skip to main content
16,016,678 members
Articles / Web Development / ASP.NET / ASP.NET Core

jQuery culture validation in ASP.NET Core 2.0

Rate me:
Please Sign up or sign in to vote.
3.55/5 (4 votes)
27 Oct 2017CPOL4 min read 26.8K   470   5   7
Getting started with jQuery validation and setting up culture specific validation in ASP.NET Core 2.0

Introduction

This article explains the steps neccessary to include client-side validation using user- or custom culture information. 

Background

Client-side validation is useful to prevent unneccessary round-trips to the server when validation is known to fail. 

By default without client-side validation the model binder does a pretty good job at parsing user input. For instance a date formatted as either dd-mm-yyyy, dd-mm-yy, dd/mm/yy or dd.mm.yy is successfully parsed. Decimal separators are tricky though.

Even so, we wish to use client side validation. By simply specifying the input type (e.g. number, date etc.) we get some basic browser validation, but we don't get further validation (e.g. required) without manually adding attributes to the markup.

This article explains the steps neccessary to get jQuery validation working using data model annotations and taking into account the user culture.

Using the code

The sample application is a newly created ASP.NET Core Web Application with the Model-View-Controller pattern (MVC) and ASP.NET Core 2.0.

The contents of the default (home) view is replaced with a simple form containing three fields of different kinds (text, date/time and a decimal). This form is posted back to the controller where a ViewBag variable is updated with the result of server side validation.

The HomeViewModel is annotated with misc. validation attributes such as RequiredMinLength and Range.

Steps to reproduce

In order to provide client-side validation append the following code to the View, preferrably the end.

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

This code renders the contents of the _ValidationScriptsPartial.cshtml file which is located in the Shared folder. This view currently only adds jquery.validate and results in undesirable behavior on the client - input is validated using defaults such as DD/MM/YYYY and a dot instead of a comma which is the decimal separator in my culture.

The results of this is client-side validation failing if I use comma and server-side validation failing if I use a dot.

To solve this jQuery.Validation needs to be configured for the correct culture. This can either be done by downloading the required cultures from https://github.com/unicode-cldr and placing them in the correct locations. I'll demonstrate a different approach where the data is automatically downloaded, but whichever approach works best for you the sample application shows the folder structure needed.

Downloading from Unicode Common Locale Data Repository (CLDR) Project

There is a file in the project folder that is not visible in Visual Studio even when toggling "Show All Files". This file is named .bowerrc and contains a json structured configuration which in a brand new project only contains a element called directory which is set to wwwroot/lib. We want to change this file to the following.

{
  "directory": "wwwroot/lib",
  "scripts": {
    "preinstall": "npm install cldr-data-downloader@0.2.x",
    "postinstall": "node ./node_modules/cldr-data-downloader/bin/download.js -i wwwroot/lib/cldr-data/index.json -o wwwroot/lib/cldr-data/"
  }
}

This basically tells Bower to install the cldr-data-downloader package and download the files specified in the index.json file. This file does not exist so unless we install cldr-data we'll get errors every time we Manage Bower Packages. 

To Manage Bower Packages right click the bower.json file. Browse for cldr-data and install the latest release. This results in the cldr-data-downloader being installed first (preinstall), cldr-data being installed next and finally the culture specific files being downloaded last (postinstall).

Be aware however that the complete set of cultures is aproximately 250 megabytes on disk.

Installing the rest of the packages

The remaining packages to install once we have the CLDR data locally is globalize and jquery-validation-globalize. Once these are installed the bower.json file should look somewhat like this.

{
  "name": "asp.net",
  "private": true,
  "dependencies": {
    "bootstrap": "3.3.7",
    "jquery": "3.2.1",
    "jquery-validation": "1.17.0",
    "jquery-validation-unobtrusive": "3.2.6",
    "cldr-data": "29.0.0",
    "globalize": "v0.1.1",
    "jquery-validation-globalize": "1.0.0",
    "cldrjs": "0.5.0"
  },
  "resolutions": {
    "globalize": "^1.0.0",
    "jquery": "3.2.1",
    "cldrjs": "0.5.0",
    "jquery-validation": "1.17.0"
  }
}

Including scripts needed for globalization

As mentioned earlier the scripts needed for validation is included where needed by including the _ValidationScriptsPartial.cshtml in the Scripts section. This file needs to be updated to work with the new packages we've installed. For the development stage update to the following. For release configuration update accordingly.

<environment include="Development">
    <script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>

    <!-- 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>
</environment>

Picking up the user culture

The culture can be detected using several mechanisms such as from the QueryString, a Cookie or from the Accept-Language HTTP header.

To set up this localization middleware we add the following code to the application startup.cs file.

C#
var di = new DirectoryInfo(Path.Combine(env.WebRootPath, @"lib\cldr-data\main"));
var supportedCultures = di.GetDirectories().Where(x => x.Name != "root").Select(x => new CultureInfo(x.Name)).ToList();
app.UseRequestLocalization(new RequestLocalizationOptions
{
    DefaultRequestCulture = new RequestCulture(supportedCultures.FirstOrDefault(x => x.Name == "en-GB")),
    SupportedCultures = supportedCultures,
    SupportedUICultures = supportedCultures
});

Wrapping it up

As it is we still haven't specified which culture to use and we'll get an error E_DEFAULT_LOCALE_NOT_DEFINED: Default locale has not been defined. To specify the culture we call Globalize.locale(...) with the culture we wish to use. The name of the culture must match the folder structure as seen in wwwroot\lib\cldr-data\main which contains the downloaded cultures.

At the end of _ValidationScriptsPartial.cshtml add the following code.

@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-GB"; //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>

Testing

Once all the code is assembled you can test if it's working as expected by either changing the browser language or setting the culture in the address field as query string parameters.

Image 1Image 2

Points of Interest

There are some differences between which formats jQuery.Validation accepts and the model binder understands. For instance some date formats are accepted client-side but are unable to be parsed server-side resulting in an invalid model. Using a date-time picker eleminate these inconsistencies.

Sample application

The sample application needs to restore all the packages and download all cultures from CLDR and as such takes some time when initially loading the solution.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Fundator AS
Norway Norway
MCSD/MCSA/MCP working with what I like best - architecture, development and consulting.

Comments and Discussions

 
Praisethanks Pin
zatanax31-May-19 5:05
zatanax31-May-19 5:05 
just add the js files whit libman and the
jquery-validation-globalize
added from your proyect and work , the datapicket change to es , and the validations , now the test of resx files to work .

{
  "version": "1.0",
  "defaultProvider": "cdnjs",
  "libraries": [
    {
      "library": "globalize@1.4.2",
      "destination": "wwwroot/lib/globalize/",
      "provider": "cdnjs",
      "files": [
        "globalize.js",
        "globalize-runtime/currency.js",
        "globalize-runtime/number.js",
        "globalize-runtime/date.js"
      ]

    },
    {
      "library": "cldrjs@0.5.1",
      "destination": "wwwroot/lib/cldr/",
      "provider": "cdnjs",
      "files": [
        "cldr.js",
        "cldr/event.js",
        "cldr/supplemental.js"
      ]
    }
    //,{
    //  "provider": "unpkg",
    //  "library": "",
    //  "destination": "wwwroot/lib/jquery-validation-globalize/"
      
    //}


  ]
}


this is the code in the startup on services


services.AddLocalization(options => options.ResourcesPath = "Resources");

            services.Configure<RequestLocalizationOptions>(
                options =>
                {
                    var supportedCultures = new List<CultureInfo>
                        {
                            new CultureInfo("en-US"),
                            new CultureInfo("es-CL"),

                        };

                    options.DefaultRequestCulture = new RequestCulture(culture: "es-CL", uiCulture: "es-CL");
                    options.SupportedCultures = supportedCultures;
                    options.SupportedUICultures = supportedCultures;

                    // You can change which providers are configured to determine the culture for requests, or even add a custom
                    // provider with your own logic. The providers will be asked in order to provide a culture for each request,
                    // and the first to provide a non-null result that is in the configured supported cultures list will be used.
                    // By default, the following built-in providers are configured:
                    // - QueryStringRequestCultureProvider, sets culture via "culture" and "ui-culture" query string values, useful for testing
                    // - CookieRequestCultureProvider, sets culture via "ASPNET_CULTURE" cookie
                    // - AcceptLanguageHeaderRequestCultureProvider, sets culture via the "Accept-Language" request header
                    options.RequestCultureProviders.Insert(0, new QueryStringRequestCultureProvider());
                });


------

app startup

var locOptions = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
      app.UseRequestLocalization(locOptions.Value);

Questioncontent main folder Pin
Member 1415040816-Feb-19 0:07
Member 1415040816-Feb-19 0:07 
AnswerRe: content main folder Pin
Stefan Vincent Haug24-Feb-19 21:04
professionalStefan Vincent Haug24-Feb-19 21:04 
QuestionThank you Pin
Роман Бачой10-May-18 20:27
Роман Бачой10-May-18 20:27 
AnswerRe: Thank you Pin
Stefan Vincent Haug1-Aug-18 8:05
professionalStefan Vincent Haug1-Aug-18 8:05 
GeneralMy vote of 5 Pin
Alexey Demin14-Mar-18 0:28
professionalAlexey Demin14-Mar-18 0:28 
GeneralRe: My vote of 5 Pin
Stefan Vincent Haug15-Mar-18 10:01
professionalStefan Vincent Haug15-Mar-18 10:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.