简体   繁体   English

ASP.NET MVC绑定十进制值

[英]ASP.NET MVC binding decimal value

I'm trying to figure out why framework refuses to bind "1,234.00" value to decimal. 我试图弄清楚为什么框架拒绝将“ 1,234.00”值绑定到十进制。 What can be the reason for it? 可能是什么原因呢?

Values like "123.00" or "123.0000" bind successfully. 像“ 123.00”或“ 123.0000”之类的值成功绑定。

I have the following code setting my culture config in Global.asax 我有以下代码在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 在Web.Config中将法语文化设置为默认文化

  <globalization uiCulture="fr-FR" culture="fr-FR" />

I've dived into sources of System.Web.Mvc.dll's ValueProviderResult class. 我已经深入探讨了System.Web.Mvc.dll的ValueProviderResult类的源。 It is using System.ComponentModel.DecimalConverter. 它使用System.ComponentModel.DecimalConverter。

converter.ConvertFrom((ITypeDescriptorContext) null, culture, value)

Here is where the message "1,234.0000 is not a valid value for Decimal." 这是消息“ 1,234.0000不是十进制的有效值”的地方。 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. DecimalConverter引发相同的异常。 Decimal.Parse correctly parses the same string. Decimal.Parse正确解析相同的字符串。

The problem is, that DecimalConverter.ConvertFrom does not support the AllowThousands flag of the NumberStyles enumeration when it calls Number.Parse . 问题是, DecimalConverter.ConvertFrom调用Number.Parse时不支持NumberStyles枚举的AllowThousands标志。 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. Decimal.Parse内部调用Number.Parse ,其数字样式设置为Number ,其AllowThousands标志设置为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 . 当您从描述符接收类型转换器时,您实际上会获得DecimalConverter的实例。 The ConvertFrom method is a kinda general and large, so I only quote the relevant parts for the current scenario here. ConvertFrom方法有点一般,它很大,因此在这里我仅引用当前场景的相关部分。 The missing parts are implementing support for hex strings and exception handling. 缺少的部分正在实现对十六进制字符串和异常处理的支持。 1 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: DecimalConverter还会覆盖FromString实现,并且会引发问题:

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! 在数字样式设置为FloatAllowThousands标志设置为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);
        }
    }
}

1 Note that the code looks similar to the original implementation. 1 请注意,该代码看起来与原始实现类似。 If you need the "unquoted" stuff either delegate it directly to base or implement it on your own. 如果您需要“未引用”的东西,则可以直接将其委派给base ,也可以自己实现。 You can view the implementation using ILSpy/DotPeek/etc. 您可以使用ILSpy / DotPeek / etc查看实施。 or by debugging into them from Visual Studio. 或通过从Visual Studio对其进行调试。

Finally, with a little help from Reflection, you can set the type converter for Decimal to use your new custom one! 最后,在Reflection的帮助下,您可以将Decimal的类型转换器设置为使用新的自定义变量!

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

Based on a comment from an article about decimal model binding by Phil Haack here , I believe part of the answer to the "why" is that culture in browsers is complicated and you can't be guaranteed that your application's culture will be the same culture settings used by the user/ browser for decimals. 根据Phil Haack 在此处发表的有关十进制模型绑定的文章的评论,我相信“为什么”的部分答案是浏览器中的区域性很复杂,您不能保证应用程序的区域性是相同的区域性用户/浏览器用于小数的设置。 In any case it is a known "issue" and similar questions have been asked before with a variety of solutions offered, in addition to the so: Accept comma and dot as decimal separator and How to set decimal separators in ASP.NET MVC controllers? 无论如何,这都是已知的“问题”,并且除了提供以下解决方案外,还曾提出过类似的问题,并提供了多种解决方案: 接受逗号和点作为小数点分隔符,以及如何在ASP.NET MVC控制器中设置小数点分隔符? for example. 例如。

You can try overriding the DefaultModelBinder. 您可以尝试覆盖DefaultModelBinder。 Let me know if this doesn't work and I'll delete this post. 让我知道这是否无效,我将删除此帖子。 I didn't actually put together an MVC app and test it, but based on experience this should work: 我实际上并没有组合MVC应用程序并对其进行测试,但是根据经验,这应该可行:

public class CustomModelBinder : DefaultModelBinder
{
    protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
    {
        if(propertyDescriptor.PropertyType == typeof(decimal))
        {
            propertyDescriptor.SetValue(bindingContext.Model, double.Parse(propertyDescriptor.GetValue(bindingContext.Model).ToString()));
            base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
        }
        else
        {
            base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
        }
    }
}

The issue here appears to be the default Number Styles applied to Decimal.Parse(string). 这里的问题似乎是应用于Decimal.Parse(string)的默认数字样式

From MSDN documentation 从MSDN文档

The remaining individual field flags define style elements that may be, but do not have to be, present in the string representation of a decimal number for the parse operation to succeed. 其余的各个字段标志定义样式元素,这些样式元素可以但不必须以十进制数字的字符串表示形式存在,以使解析操作成功。

So this means that both d1 and d2 below successfully parse 所以这意味着下面的d1和d2都可以成功解析

                var d1 = Decimal.Parse("1,232.000");

                var d2 = Decimal.Parse("1,232.000", NumberStyles.Any);

However when applying the type convertor it appears that this essentially only allows the allow training spaces, allow decimal point and allow leading sign. 但是,在应用类型转换器时,似乎基本上只允许允许训练空间,允许小数点和允许前导符号。 As such the d3 express below will throw a runtime error 因此,下面的d3 express将抛出运行时错误

                var d3 = Decimal.Parse("1,232.000", NumberStyles.AllowLeadingSign | NumberStyles.AllowLeadingWhite | 
                                        NumberStyles.AllowTrailingWhite | NumberStyles.AllowDecimalPoint);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM