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 Required
, MinLength
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.
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.
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.