【问题标题】:ASP.NET MVC binding decimal valueASP.NET MVC 绑定十进制值
【发布时间】:2023-03-25 00:32:02
【问题描述】:

我试图弄清楚为什么框架拒绝将“1,234.00”值绑定到十进制。可能是什么原因?

“123.00”或“123.0000”等值绑定成功。

我有以下代码在 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;
    }

法语文化在 Web.Config 中设置为默认文化

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

我已经深入研究了 System.Web.Mvc.dll 的 ValueProviderResult 类的来源。它正在使用 System.ComponentModel.DecimalConverter。

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

此处显示消息“1,234.0000 不是 Decimal 的有效值”。来自。

我尝试在我的 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 抛出相同的异常。 Decimal.Parse 正确解析相同的字符串。

【问题讨论】:

    标签: c# asp.net-mvc decimal


    【解决方案1】:

    问题是,DecimalConverter.ConvertFrom 在调用Number.Parse 时不支持NumberStyles 枚举的AllowThousands 标志。好消息是,有一种方法可以“教”它这样做!

    Decimal.Parse 内部调用Number.Parse 并将数字样式设置为Number,为此AllowThousands 标志设置为true。

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

    当您从描述符接收类型转换器时,您实际上得到了一个DecimalConverter 的实例。 ConvertFrom 方法有点笼统和庞大,所以我在这里只引用当前场景的相关部分。缺少的部分正在实现对十六进制字符串和异常处理的支持。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 也覆盖了FromString 实现,问题就出现了:

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

    将数字样式设置为FloatAllowThousands 标志设置为false!但是,您可以使用几行代码编写一个自定义转换器来解决此问题。

    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请注意,代码看起来与原始实现相似。如果您需要“未引用”的内容,请直接将其委托给base 或自行实施。您可以使用 ILSpy/DotPeek/etc 查看实现。或通过从 Visual Studio 调试到它们。

    最后,在 Reflection 的帮助下,您可以设置 Decimal 的类型转换器以使用新的自定义转换器!

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

    【讨论】:

    • 你为什么使用 Double.Parse 而不是 Decimal.Parse?
    • @IvanGritsenko:只是一个错字;)
    • 我已经接受了这个答案。但是,正如@stephen.vakil 指出的那样,我在我的项目中使用了 Phil Haack 的解决方案。
    【解决方案2】:

    根据 Phil Haack here 的一篇关于十进制模型绑定的文章的评论,我相信“为什么”的部分答案是浏览器中的文化很复杂,您无法保证应用程序的文化将与用户/浏览器用于小数的文化设置相同。无论如何,这是一个已知的“问题”,并且之前已经提出过类似的问题,并提供了各种解决方案,除了Accept comma and dot as decimal separatorHow to set decimal separators in ASP.NET MVC controllers? 等。

    【讨论】:

      【解决方案3】:

      您可以尝试覆盖 DefaultModelBinder。如果这不起作用,请告诉我,我将删除此帖子。我实际上并没有组装一个 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);
              }
          }
      }
      

      【讨论】:

        【解决方案4】:

        这里的问题似乎是应用于 Decimal.Parse(string) 的默认 Number Styles

        来自 MSDN 文档

        剩余的单个字段标志定义样式元素,这些元素可能但不一定存在于十进制数的字符串表示中,以使解析操作成功。

        所以这意味着下面的d1和d2都成功解析了

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

        但是,当应用类型转换器时,它似乎只允许允许训练空间、允许小数点和允许前导符号。因此下面的 d3 express 会抛出运行时错误

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-08-07
          • 2016-02-25
          • 1970-01-01
          • 2019-10-23
          • 1970-01-01
          • 1970-01-01
          • 2013-05-07
          • 1970-01-01
          相关资源
          最近更新 更多