MVC/JQuery validation does not accept comma as decimal separator

Update 07.01.2018

Even though it was suggested, that this is rather a jQuery problem than an MS ASP MVC problem, I think it is an MVC Problem. I've created the whole app in asp.net core 2.0 MVC and the error persist. What links it to MVC for me, is the fact that I can solve the date validation problem by adding the line [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy-MM-dd}")] to the model. Hence, MVC has an influence on the validation. So I would assume there is some way in MVC to fix this (See this post). Please post answers for asp.net core 2.0.

Original Post

In an MVC5 page I render a Double Property in a Textbox. When the page is loaded, the value is shown with a "," as decimal separator, which is correct, as the page runs on an German System. If I want to save the form, I get an validation error. How can this be solved? I know that there are some questions on the topic, but as far as i can see, most of them are outdated... I'm still struggling, that there is no setting or anything built-in that allows users from different countries to work with MVC apps.

Model:

[DisplayFormat(DataFormatString = "{0:n2}", ApplyFormatInEditMode = true)]
public Double Gewicht
{
    get { return gewicht; }
    set { gewicht = value; OnPropertyChanged(new PropertyChangedEventArgs("Gewicht")); }
}

CSHTML:

<div class="form-group">
    @Html.LabelFor(model => model.Gewicht, htmlAttributes: new { @class = "control-label col-md-3" })
    <div class="col-md-8">
        @Html.EditorFor(model => model.Gewicht, new { htmlAttributes = new { @class = "form-control col-md-1" } })
        @Html.ValidationMessageFor(model => model.Gewicht, "", new { @class = "text-danger" })
    </div>
</div>

Web.config

<globalization uiCulture="de-DE" culture="de-DE" />

Box after its loaded --> Value loaded with a comma as decimal separator enter image description here

Box after submit button is clicked --> Validation error for the same value

enter image description here

Box after comma is changed to point --> No validation error enter image description here

Update 05.01.2018

I've tried the solution shown here which unfortunately doesn't work for me. However, I also discovered, that the values not only get not acceptet, but are also changed to numbers where group separator and decimal separator are mixed up (see picture). What happens is, that the value of 22 gets changed to 22.5 and stored in the database. The result beeing, that a value of 2,250.00 is stored to the database.

enter image description here

Update 07.01.2018

What is also interesting, is the fact that the date fields accept the german format perfectly fine.

Property

private DateTime? inbetriebnahmedatum;

[DataType(DataType.Date)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy-MM-dd}")]
public DateTime? Inbetriebnahmedatum
{
    get { return inbetriebnahmedatum; }
    set { inbetriebnahmedatum = value; OnPropertyChanged(new PropertyChangedEventArgs("Inbetriebnahmedatum")); }
}

The value shown below is accepted and processed without any errors. enter image description here

Update 07.01.2018 - 2

If I change the line in edit.cshtml from

<input asp-for="Gewicht" class="form-control" />

to

<input name="Gewicht" id="Gewicht" type="number" class="form-control" value="@Model.Gewicht"/>

The form can be submitted with the value "23,7" without validation errors. In the controller, the bound model property shows a value of "237", where as the IFormCollection shows a value of "23.7". This would suggest a problem with the model binder.


Solution 1:

Even though it was suggested, that this is rather a jQuery problem than an MVC problem, I think it is an MVC Problem.

No that is no correct. You seeing a client side validation error because, by default, jquery.validate.js (an independent 3rd party plugin not associated with MicroSoft, that MVC uses for client side validation) validates numbers based on the decimal separator being a . (dot), not a , (comma).

MVC is server side code and does not run in the browser. To perform client side validation, MVC's HtmlHelper methods that generate form controls render a set of data-val-* attributes in the html used to describe the validation to be performed, which are in turn parsed by the jquery.validate.unobtrusive.js plugin when the DOM is loaded, and uses those to add rules to the $.validator.

In the case of your double property it will add a data-val-number attribute (in addition to data-val-required attribute), which will add the number rule which is defined as

// http://docs.jquery.com/Plugins/Validation/Methods/number
number: function( value, element ) {
    return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value);
},

where the decimal separator is a dot, and the thousands separator is a comma (presumably because the plugin was developed in the US, so uses a US format).

You need to overwrite the default behavior which you can do by using plugins such as jquery.globalize, or including the following script (note the regex just swaps the dot and comma)

$.validator.methods.number = function (value, element) {
    return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:\.\d{3})+)?(?:,\d+)?$/.test(value);
}

Note the above script needs to be after the jquery.validate.js script but not wrapped in $(document).ready()

What links it to MVC for me, is the fact that I can solve the date validation problem by adding the line [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy-MM-dd}")] to the model.

Its actually your [DataType(DataType.Date)] attribute in conjunction with the [DisplayFormat] attribute that is influencing the html that is generated. The [DataType] attribute generates <input type="date" ... /> which in turn renders the browsers HTML-5 datepicker if the browser supports it. In accordance with the specifications the format must be yyyy-MM-dd (ISO format) hence the need for the [DisplayFormat] attribute as well.

The HTML-5 datepicker renders the date in the browsers culture. The image you have shown where the input is 26.1.2018 is because your browser culture is de-DE, but if I navigated to your site, I would see 26/1/2018 in the input because my culture is en-AU (Australian), and if a United States user navigated to your site, they would see 1/26/2018.

The reason client side validation works for the date property is that the jquery.validate.js plugin includes date rules for both US format (MM/dd/yyyy) and ISO format (yyyy-MM-dd).

And if you were to use @Html.TextBoxFor(m => m.Inbetriebnahmedatum) (which ignores your [DataType] and [DisplayFormat] attributes), and entered 26.1.2018 in the input, you would also see a client side validation error.

Solution 2:

I think the problem is jquery validator i'v used thus for resolve the comma/point error

$.validator.methods.number = function (value, element) {
    return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:[\s\.,]\d{3})+)(?:[\.,]\d+)?$/.test(value);
}

just try play with it

Solution 3:

A more robust solution might be to wrap the validator methods in your own function which converts your comma separated number to one that is decimal separated.

One trick is to use .call to call the original validator function as if "this" was the original this that the developers thought (e.g. they use a function "this.optional" for their step validation).

var originalNumber = $.validator.methods.number;
var wrappedNumber = function (value, element) {
    var fixedValue = parseFloat(value.toString().replace(",", "."));
    return originalNumber.call($.validator.prototype, fixedValue, element);     // Call function as if "this" is the original caller
};
$.validator.methods.number = wrappedNumber;

You could make this work for any validator, e.g. the step validation:

var originalStep = $.validator.methods.step;
var wrappedStep = function (value, element, param) {
    var fixedValue = parseFloat(value.toString().replace(",", "."));
    return originalStep.call($.validator.prototype, fixedValue, element, param);
};
$.validator.methods.step = wrappedStep;