ASP.NET MVC 绑定十进制值
ASP.NET MVC binding decimal value
我想弄清楚为什么框架拒绝将“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 class 的来源。它正在使用 System.ComponentModel.DecimalConverter。
converter.ConvertFrom((ITypeDescriptorContext) null, culture, value)
此处显示消息“1,234.0000 不是十进制的有效值。”来自.
我已经在我的 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 正确解析相同的字符串。
您可以尝试覆盖 DefaultModelBinder。如果这不起作用,请告诉我,我将删除它 post。我实际上并没有组装 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);
}
}
}
例如,基于 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. 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? 的一篇关于十进制模型绑定的文章的评论。
这里的问题似乎是应用于 Decimal.Parse(string) 的默认 Number Styles。
来自 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.
所以这意味着下面的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);
问题是,DecimalConverter.ConvertFrom
在调用 Number.Parse
时不支持 NumberStyles
枚举的 AllowThousands
标志。好消息是,有一种方法可以 "teach" 做到这一点!
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);
}
数字样式设置为Float
,AllowThousands
标志设置为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请注意,代码看起来与原始实现类似。如果您需要 "unquoted" 东西,请直接将其委托给 base
或自行实现。您可以使用 ILSpy/DotPeek/etc 查看实现。或者通过 Visual Studio.
调试它们
最后,在 Reflection 的帮助下,您可以为 Decimal
设置类型转换器以使用您的新自定义类型转换器!
TypeDescriptor.AddAttributes(typeof(decimal), new TypeConverterAttribute(typeof(NumericDecimalConverter)));
我想弄清楚为什么框架拒绝将“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 class 的来源。它正在使用 System.ComponentModel.DecimalConverter。
converter.ConvertFrom((ITypeDescriptorContext) null, culture, value)
此处显示消息“1,234.0000 不是十进制的有效值。”来自.
我已经在我的 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 正确解析相同的字符串。
您可以尝试覆盖 DefaultModelBinder。如果这不起作用,请告诉我,我将删除它 post。我实际上并没有组装 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);
}
}
}
例如,基于 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. 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? 的一篇关于十进制模型绑定的文章的评论。
这里的问题似乎是应用于 Decimal.Parse(string) 的默认 Number Styles。
来自 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.
所以这意味着下面的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);
问题是,DecimalConverter.ConvertFrom
在调用 Number.Parse
时不支持 NumberStyles
枚举的 AllowThousands
标志。好消息是,有一种方法可以 "teach" 做到这一点!
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);
}
数字样式设置为Float
,AllowThousands
标志设置为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请注意,代码看起来与原始实现类似。如果您需要 "unquoted" 东西,请直接将其委托给 base
或自行实现。您可以使用 ILSpy/DotPeek/etc 查看实现。或者通过 Visual Studio.
最后,在 Reflection 的帮助下,您可以为 Decimal
设置类型转换器以使用您的新自定义类型转换器!
TypeDescriptor.AddAttributes(typeof(decimal), new TypeConverterAttribute(typeof(NumericDecimalConverter)));