How to handle decimal input values in a globalized MVC app
Asked Answered
S

1

1

Currently trying to rework a .NET 4.8 MVC application to support globalization (needs to support both English and French). So far, everything has been working fine.

The issue, however, is with decimal input. This is a financial application, so it is important that I deal with input boxes that are set to type="number". Strictly speaking, I know that the value 15000,25 is not considered a number, and will not convert implicitly to a decimal (the backing field data type that I use in my view model). With this in mind, I have a custom model binder:

public class DecimalModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

        if (valueProviderResult == null)
        {
            return base.BindModel(controllerContext, bindingContext);
        }

        if (string.IsNullOrEmpty(valueProviderResult.AttemptedValue))
        {
            return null;
        }

        var result = decimal.TryParse(valueProviderResult.AttemptedValue, NumberStyles.Currency, CultureInfo.InvariantCulture, out decimal covertedOutput);

        if (result)
        {
            return covertedOutput;
        }

        return null;
    }
}

This model binder is then registered in the Global.asax file. The above appears to work to an extent...especially in Firefox, in that I can enter 15000,25 into a form input and what happens on the backend is that 15000.25 is recorded in the database.

This is a different story in chrome, as

  1. I cannot enter 15000,25 as a value, only decimal point based numbers and
  2. on the reload from the server - chrome will try and take 15000.25 and parse it as 15000,25 in the input...which fails, with the error:

The specified value "15000,25" cannot be parsed, or is out of range

My question is this - should I force users to always deal with true decimal based numbers? So stop them from entering 15000,25 but allow 15000.25? or is there a way around this / best practice that I'm just not seeing?

Here is the decimal field that I'm working with. Both the form control and the view model property:

@Html.TextBoxFor(model => model.SellAmount, "{0:f2}", new {min = "0", type = "number", step = "1000" })
public decimal? SellAmount { get; set; }
Samite answered 21/3, 2024 at 6:41 Comment(6)
You already have one close vote for opinionated question. You may want to consider changing that title, quick. And in future avoid "Best way to", "Most efficient ..." type of titles. What you are asking is "How to ..." - that allows users to suggest their preferred way to solve the problem you are having. If one of those is particularly "better" or "worse" may sometimes be objectively measurable but most of the time it "depends".Minardi
I am not versed in asp.net-mvc but will the context give you a client culture? If so, you need to use that for parsing instead of the invariant culture.Minardi
@Minardi thanks for the tip. It was not my intention to ask an annoying question…but I realize the title was a poor choice. As for your next comment…I use invariant culture to remove the localized adulteration of the decimal value. This is more of an issue with the front end (I believe).Samite
I am not sure if, but it sounds like the text input does not seem to adapt for client culture even when put to "number" type...Minardi
And you didn't ask an annoying question :) just "best way" triggers some people to close vote for "opinionated" without even reading the question.Minardi
Digging through this, it appears that what i'm trying to do is not possible with input type=number #13412704Samite
S
0

So from doing a bunch of research, I managed to come up with a solution.

  1. Use type=text instead of number
  2. Use a custom input by extending MvcHtmlString

An issue I was having was that the browser would try and insert it's own localized value into the text box during view re-renders. This would cause chrome to raise the following error:

The specified value "{value}" cannot be parsed, or is out of range

To prevent this from happening, I extended the MVC input control:

  public static class InputExtensions
  {
     public static MvcHtmlString NumberInput<TModel, TProperty>(this HtmlHelper<TModel> helper
            , Expression<Func<TModel, TProperty>> expression, string format = null)
     {
        // Get decimal value from the model
        var value = (decimal)ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
    
        // Render the input, this stops the browser from inserting it's own 'localized' format as we ignore the culture with ultureInfo.InvariantCulture
        helper.TextBoxFor(expression, format, new { pattern = @"[0-9]+([\.][0-9]{1,2})?", @Value = value.ToString(CultureInfo.InvariantCulture) });
     }
  }

You can just call the extension from the view:

 @Html.NumberInput(model => model.Input.SellAmount, "{0:f2}")

Doing the above means that I can force the user into using a 000.00 format for decimals, elsewhere, I just let the UICulture take over when displaying the values (in labels etc.)

This cures a lot of the localization headaches I was having.

Samite answered 13/4, 2024 at 2:13 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.