There is a nasty globalization bug in ASP.NET MVC: the client-side validation message for numeric is hard coded to a string that is not localizable.
Take the following view model class:
public class SomeViewModel { public int SomeNumber { get; set; } }
… and this view:
@model SomeViewModel @{ Layout = "~/Views/Shared/_Layout.cshtml"; Html.EnableClientValidation(); Html.EnableUnobtrusiveJavaScript(); } @using (Html.BeginForm()) { @Html.EditorFor(vm => vm.SomeNumber) @Html.ValidationMessageFor(vm => vm.SomeNumber) }
If the user enters a non-numeric value into the text box form SomeNumber, client side validation kicks in and displays a message. This is excellent, you can’t have validation for less effort. This message is “The field SomeNumber must be a number.”. Umm. That might need some rewording, especially when you wish to translate the user interface into a language other than English. The trouble is: you can’t.
The string “The field {0} must be a number.” is baked into ASP.NET MVC in a resource file. There is no way to define a different resource file, or overwrite the message in any other way (like there is for server-side default messages by setting DefaultModelBinder.ResourceClassKey
). For other messages, like the message for required fields, this is not a big deal because a custom message can be applied by setting a DataAnnotation attribute – however, this is not possible for data type based validation.
The options at hand are:
- Do without the integration of data types and client side validation, and not use EditorFor. Instead, declare the text box directly, and set the
data-val-number
attribute that controls the message directly. Yuck – that feels like gearing the progeamming model around a platform bug. I’m not desperate for that yet. - Fix ASP.NET MVC. It’s open sourced, you can run your own fixed branch, or at least replace the part that causes the problem. Yes, I might do this, but it causes some maintenance problem – each time a new version of the framework is released without a fix, I’d need to recompile. Sounds like a lot of work.
- Use AOP to basically do the same. That solutions yields a lot less code, but still has a very tight dependency on the current version of the framework; in addition, I’d need to throw another framework into the project. Maybe.
- The problem lies within ASP.NET MVC, but in the outcome, it’s just a client-side message. Hence, replacing the non-localized message with a localized one does not have to happen on the server. Let’s see where these messages are applied!
The validation rules and messages for client side validation are defined in HTML5-style “data-” attributes that are attached to the form’s input elements. As an example, this is what ASP.NET emits for the integer value from the example above:
<input class="text-box single-line" data-val="true" data-val-number="The field SomeNumber must be a number." data-val-required="The SomeNumber field is required." id="SomeNumber" name="SomeNumber" type="text" value="0" />
There are two JavaScript modules that construct the client side validation:
- jquery.validate provides all the heavy lifting for evaluating and comparing values, and for displaying the messages.
- jquery.validate.unobstrusive hooks the validation functionality provided by jquery.validate into the page in an “unobstrusive” way – that is, without any JavaScript code that clutters the page. Instead, attributes on the validated controls declare what should be validated.
When jquery.validate.unobstrusive is loaded, it reads all validation attributes on the current page. This is the first chance to change the content of the attribute. The script is cleverly structured; it attaches a host of adapters that match attributes and then initialize the appropriate jquery.validate function. A set of options is passed through the adapter, containing (among other things) the message. Directly after jquery.validate.unobstrusive is loaded, we can hook into its adapters and change the one for numbers:
(function ($) { // Walk through the adapters that connect unobstrusive validation to jQuery.validate. // Look for all adapters that perform number validation $.each($.validator.unobtrusive.adapters, function () { if (this.name === "number") { // Get the method called by the adapter, and replace it with one // that changes the message to the jQuery.validate default message // that can be globalized. If that string contains a {0} placeholder, // it is replaced by the field name. var baseAdapt = this.adapt; this.adapt = function (options) { var fieldName = new RegExp("The field (.+) must be a number").exec(options.message)[1]; options.message = $.validator.format($.validator.messages.number, fieldName); baseAdapt(options); }; } }); } (jQuery));
Now, instead of the message defined by ASP.NET MVC, the default message provided by jQuery.validate is used. This is much better because this one _can_ be localized:
$.validate.messages.number="Eine Nummer für {0}, bitteschön";
Instead of inventing your own messages you should maybe use the jquery-glob plugin that comes with a variety of langauges.
Using this solution in your own project
- Copy the JavaScript code above into a new JavaScript file named
jquery.validate.unobstrusive.fixMvcNumberMessage.js
in the Scripts directory of your ASP.NET MVC project. - Add a reference to that file directly beneath jquery.validate.unobstrusive. The references to scripts are normally held in the _Layout shared view.
Feel free to use the code in this article as it serves you.
