ASP.NET MVC绑定十进制值

我试图找出为什么框架拒绝将“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中被设置为默认文化

   

我潜入了System.Web.Mvc.dll的ValueProviderResult类的源代码。 它使用的是System.ComponentModel.DecimalConverter。

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

这是消息“1,234.0000不是十进制的有效值”。 来自。

我试图在我的操场上运行以下代码:

 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抛出相同的exception。 Decimal.Parse正确解析相同的字符串。

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

Decimal.Parse内部调用Number.Parse ,其数字样式设置为NumberAllowThousands标志设置为true。

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

当您从描述符接收类型转换器时,实际上您获得了DecimalConverter的实例。 ConvertFrom方法有点普遍而且很大,所以我只引用当前场景的相关部分。 缺少的部分正在实现对hex字符串和exception处理的支持。 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))); 

根据Phil Haack关于十进制模型绑定的文章的评论,我认为“为什么”的部分答案是浏览器中的文化很复杂,你无法保证你的应用程序的文化将是同一种文化用户/浏览器用于小数的设置。 在任何情况下,它都是一个已知的“问题”,除了提供的各种解决方案之外,之前已经提出了类似的问题: 接受逗号和点作为小数分隔符以及如何在ASP.NET MVC控制器中设置小数分隔符? 例如。

您可以尝试覆盖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); } } } 

此处的问题似乎是应用于Decimal.Parse(字符串)的默认数字样式 。

从MSDN文档

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

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

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

然而,当应用类型转换器时,似乎这基本上只允许允许训练空间,允许小数点并允许前导符号。 因此,下面的d3表示将抛出运行时错误

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