ASP.NET MVC binding decimal value

asp.net-mvc, c#, decimal

Solution

The problem is, that `DecimalConverter.ConvertFrom` does not support the `AllowThousands` flag of the `NumberStyles` enumeration when it calls `Number.Parse`. The good news is, that there exists a way to "teach" it to do so!

`Decimal.Parse` internally calls `Number.Parse` with the number style set to `Number`, for which the `AllowThousands` flag is set to true.

[__DynamicallyInvokable]
public static decimal Parse(string s)
{
    return Number.ParseDecimal(s, NumberStyles.Number, NumberFormatInfo.CurrentInfo);
}

When you are receiving a type converter from the descriptor, you actually get an instance of `DecimalConverter`. The `ConvertFrom` method is a kinda general and large, so I only quote the relevant parts for the current scenario here. The missing parts are implementing support for hex strings and exception handling.1

public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
    if (value is string) 
    {
        // ...

        string text = ((string)value).Trim();

        if (culture == null) 
            culture = CultureInfo.CurrentCulture;

        NumberFormatInfo formatInfo = (NumberFormatInfo)culture.GetFormat(typeof(NumberFormatInfo));
        return FromString(text, formatInfo);

        // ...
    }

    return base.ConvertFrom(context, culture, value);
}

`DecimalConverter` also overwrites the `FromString` implementation and there the problem raises:

internal override object FromString(string value, NumberFormatInfo formatInfo) 
{
    return Decimal.Parse(value, NumberStyles.Float, formatInfo);
}

With the number style set to `Float`, the `AllowThousands` flag is set to false! However you can write a custom converter with a few lines of code that fixes this issue.

class NumericDecimalConverter : DecimalConverter
{
    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value is string)
        {
            string text = ((string)value).Trim();

            if (culture == null) 
                culture = CultureInfo.CurrentCulture;

            NumberFormatInfo formatInfo = (NumberFormatInfo)culture.GetFormat(typeof(NumberFormatInfo));
            return Decimal.Parse(text, NumberStyles.Number, formatInfo);
        }
        else
        {
            return base.ConvertFrom(value);
        }
    }
}

1Note that the code looks similar to the original implementation. If you need the "unquoted" stuff either delegate it directly to `base` or implement it on your own. You can view the implementation using ILSpy/DotPeek/etc. or by debugging into them from Visual Studio.

Finally, with a little help from Reflection, you can set the type converter for `Decimal` to use your new custom one!

TypeDescriptor.AddAttributes(typeof(decimal), new TypeConverterAttribute(typeof(NumericDecimalConverter)));

Problem

I'm trying to figure out why framework refuses to bind "1,234.00" value to decimal. What can be the reason for it? Values like "123.00" or "123.0000" bind successfully. I have the following code setting my culture config in Global.asax ``` public void Application_AcquireRequestState(object sender, EventArgs e) { var culture = (CultureInfo)Thread.CurrentThread.CurrentCulture.Clone(); culture.NumberFormat.NumberDecimalSeparator = culture.NumberFormat.CurrencyDecimalSeparator = culture.NumberFormat.PercentDecimalSeparator = "."; culture.NumberFormat.NumberGroupSeparator = culture.NumberFormat.CurrencyGroupSeparator = culture.NumberFormat.PercentGroupSeparator = ","; Thread.CurrentThread.CurrentCulture = culture; } ``` French culture is set as default culture in Web.Config ``` <globalization uiCulture="fr-FR" culture="fr-FR" /> ``` I've dived into sources of System.Web.Mvc.dll's ValueProviderResult class. It is using System.ComponentModel.DecimalConverter. ``` converter.ConvertFrom((ITypeDescriptorContext) null, culture, value) ``` Here is where the message "1,234.0000 is not a valid value for Decimal." comes from. I've tried to run the following code in my playground: ``` static void Main() { var decConverter = TypeDescriptor.GetConverter(typeof(decimal)); var culture = new CultureInfo("fr-FR"); culture.NumberFormat.NumberDecimalSeparator = culture.NumberFormat.CurrencyDecimalSeparator = culture.NumberFormat.PercentDecimalSeparator = "."; culture.NumberFormat.NumberGroupSeparator = culture.NumberFormat.CurrencyGroupSeparator = culture.NumberFormat.PercentGroupSeparator = ","; Thread.CurrentThread.CurrentCulture = culture; var d1 = Decimal.Parse("1,232.000"); Console.Write("{0}", d1); // prints 1234.000 var d2 = decConverter.ConvertFrom((ITypeDescriptorContext)null, culture, "1,232.000"); // throws "1,234.0000 is not a valid value for Decimal." Console.Write("{0}", d2); } ``` DecimalConverter throws same exception. Decimal.Parse correctly parses the same string.

Original source

Related problems