MVC localization binding/validation not working with decimals and dates
Is there an existing issue for this?
- [x] I have searched the existing issues
Description
I am using Abp with MVC. If I have a page that binds an input to a model decimal/double or a DateTime and I change the language to Spanish or Italian, the data binding will fail when posting the form data. The DateTime will be a null value. The double will not parse the decimal separator correctly so "2.34" returns "234". If I try entering "2,34" the validation fails saying "El campo Amount debe ser un numero" i.e. the value must be a number.
Reproduction Steps
Create a new Razor page with the model having this code:
[BindProperty]
[DataType(DataType.Date)]
public DateTime? StartDate { get; set; }
[BindProperty]
public double? Amount { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (StartDate == null)
throw new ArgumentNullException(nameof(StartDate), "StartDate cannot be null.");
return NoContent();
}
In the razor view, add this:
<form method="post">
<abp-input asp-for="StartDate" />
<abp-input asp-for="Amount" />
<abp-button type="submit" text="Submit" button-type="Primary" />
</form>
When inspecting the posted data in the OnPostAsync, the Amount will not be correct.
Expected behavior
The Amount should be submitted as a decimal value, and the DateTime should not be null.
Actual behavior
No response
Regression?
No response
Known Workarounds
No response
Version
9.1.3
User Interface
MVC
Database Provider
EF Core (Default)
Tiered or separate authentication server
None (Default)
Operation System
Windows (Default)
Other information
No response
hi
This is the ASP.NET Core MVC scope.
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?#globalization-behavior-of-model-binding-route-data-and-query-strings
Yes but the Abp framework adds the localization on top of ASP.NET Core and MVC so I was thinking it could automatically handle this better. This isn't
What about adding an IModelBinder to the Abp framework that handles the datetime according to the current culture.
public Task BindModelAsync(ModelBindingContext context)
{
var value = context.ValueProvider.GetValue(context.ModelName).FirstValue;
if (string.IsNullOrEmpty(value))
return Task.CompletedTask;
var culture = CultureInfo.CurrentCulture;
if (DateTime.TryParse(value, culture, DateTimeStyles.None, out var result))
{
context.Result = ModelBindingResult.Success(result);
return Task.CompletedTask;
}
else if (DateTime.TryParseExact(value, culture.DateTimeFormat.ShortDatePattern.Replace(culture.DateTimeFormat.DateSeparator, "/"), culture, DateTimeStyles.None, out var result2))
{
context.Result = ModelBindingResult.Success(result2);
return Task.CompletedTask;
}
context.ModelState.TryAddModelError(context.ModelName, $"Invalid date format for culture {culture.Name}.");
return Task.CompletedTask;
}
For the client side validation, add the globalizejs and jquery-validation-globalize to the standard packages.
Thanks for at least considering it.
hi
Is your problem solved by adding CultureQueryStringValueProviderFactory?
No, because it is not coming from the route or query string. It is coming from the form post which then goes to the FormValueProviderFactory and FormValueProvider.
Using the above code allows dates to work when changing the language. Maybe the AbpDateTimeModelBinder could be changed to use this code above.
This code also seems to fix the datetime issue:
services.Configure<MvcViewOptions>(options =>
{
options.HtmlHelperOptions.FormInputRenderMode = Microsoft.AspNetCore.Mvc.Rendering.FormInputRenderMode.AlwaysUseCurrentCulture;
});
I have not yet fixed the problem with doubles/decimals because "5,76" is not allowed by the client side validation, and "5.76" gets converted to "576" when the language is Spanish, German, French, Italian.
Use the following test razor page to see the problem.
hi
For the client side, you can check http://blog.rebuildall.net/2011/03/02/jquery_validate_and_the_comma_decimal_separator
Yes, this http://blog.rebuildall.net/2011/03/02/jquery_validate_and_the_comma_decimal_separator should fix the client side.
globalizejs and jquery-validation-globalize do almost the exact same thing by overriding the default jquery validator.methods.number function, with one that is culture aware. That is why I suggested to add these or something similar to the set of standard JS packages that come with the Abp framework.
ok, I will check and see what we can do.
https://github.com/globalizejs/globalize https://github.com/johnnyreilly/jquery-validation-globalize/blob/master/jquery.validate.globalize.js#L20-L47
Thanks for considering this and possibly adding it in a future version. I looked into globalize but it has dependencies on cldrjs and then needs json data to initialize it and was very complicated.
I ended up with simply overriding the jquery-validate $.validator.methods.number function with this simple function that works great.
$.validator.methods.number = function (value, element) {
const patterns = {
'en': /^-?(?:\d{1,3}(?:,\d{3})*|\d+)(?:\.\d+)?$/,
'en-GB': /^-?(?:\d{1,3}(?:,\d{3})*|\d+)(?:\.\d+)?$/,
'de': /^-?(?:\d{1,3}(?:\.\d{3})*|\d+)(?:,\d+)?$/,
'fr': /^-?(?:\d{1,3}(?:\s\d{3})*|\d+)(?:,\d+)?$/,
'ch': /^-?(?:\d{1,3}(?:'\d{3})*|\d+)(?:[.,]\d+)?$/,
};
const pattern = patterns[abp.localization.currentCulture.name] || /^-?(?:\d+|\d{1,3}(?:[\s.,']\d{3})+)(?:[.,]\d+)?$/;
return this.optional(element) || pattern.test(value);
}
The const patterns has a few defined patterns for specific languages, otherwise it uses the default pattern.
If you added this to Abp, that would be great.
hi @ejohnson-dotnet
I have opened an enhancement PR
https://github.com/abpframework/abp/pull/23099