abp icon indicating copy to clipboard operation
abp copied to clipboard

MVC localization binding/validation not working with decimals and dates

Open ejohnson-dotnet opened this issue 7 months ago • 8 comments

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

ejohnson-dotnet avatar May 21 '25 15:05 ejohnson-dotnet

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

maliming avatar May 22 '25 03:05 maliming

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.

ejohnson-dotnet avatar May 22 '25 15:05 ejohnson-dotnet

hi

Is your problem solved by adding CultureQueryStringValueProviderFactory?

maliming avatar May 23 '25 01:05 maliming

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.

Test.zip

ejohnson-dotnet avatar May 23 '25 02:05 ejohnson-dotnet

hi

For the client side, you can check http://blog.rebuildall.net/2011/03/02/jquery_validate_and_the_comma_decimal_separator

maliming avatar May 23 '25 03:05 maliming

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.

ejohnson-dotnet avatar May 23 '25 03:05 ejohnson-dotnet

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

maliming avatar May 23 '25 04:05 maliming

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.

ejohnson-dotnet avatar May 23 '25 16:05 ejohnson-dotnet

hi @ejohnson-dotnet

I have opened an enhancement PR

https://github.com/abpframework/abp/pull/23099

maliming avatar Jun 18 '25 08:06 maliming