我有一个自定义模型类,其中包含一个十进制成员和一个接受此类的条目的视图。一切都运行良好,直到我添加javascripts来格式化输入控件内的数字。当焦点模糊时,格式代码格式化输入的数字与千分隔符','。
问题是我的模态类中的十进制值没有用千位分隔符很好地绑定/解析。当我用“1,000.00”测试它时,ModelState.IsValid返回false,但是对于“100.00”没有任何更改它是有效的。
如果您有任何解决方案,可以与我分享吗?
提前致谢。
样本类
public class Employee
{
public string Name { get; set; }
public decimal Salary { get; set; }
}
样本控制器
public class EmployeeController : Controller
{
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult New()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult New(Employee e)
{
if (ModelState.IsValid) // <-- It is retruning false for values with ','
{
//Subsequence codes if entry is valid.
//
}
return View(e);
}
}
示例视图
<% using (Html.BeginForm())
{ %>
Name: <%= Html.TextBox("Name")%><br />
Salary: <%= Html.TextBox("Salary")%><br />
<button type="submit">Save</button>
<% } %>
我尝试使用Custom ModelBinder解决方法正如亚历山大所建议的那样。问题解决了。但是IDataErrorInfo实现的解决方案并不顺利。由于验证,输入0时Salary值变为null。请问有什么建议吗? Asp.Net MVC团队成员是否来到stackoverflow?我能从你那里得到一些帮助吗?
使用自定义模型Binder更新代码,如Alexander建议
模型活页夹
public class MyModelBinder : DefaultModelBinder {
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
if (bindingContext == null) {
throw new ArgumentNullException("bindingContext");
}
ValueProviderResult valueResult;
bindingContext.ValueProvider.TryGetValue(bindingContext.ModelName, out valueResult);
if (valueResult != null) {
if (bindingContext.ModelType == typeof(decimal)) {
decimal decimalAttempt;
decimalAttempt = Convert.ToDecimal(valueResult.AttemptedValue);
return decimalAttempt;
}
}
return null;
}
}
员工类
public class Employee : IDataErrorInfo {
public string Name { get; set; }
public decimal Salary { get; set; }
#region IDataErrorInfo Members
public string this[string columnName] {
get {
switch (columnName)
{
case "Salary": if (Salary <= 0) return "Invalid salary amount."; break;
}
return string.Empty;
}
}
public string Error{
get {
return string.Empty;
}
}
#endregion
}
答案 0 :(得分:15)
背后的原因是,在ValueProviderResult.cs中的ConvertSimpleType中使用了TypeConverter。
小数的TypeConverter不支持千位分隔符。 请在此处阅读:http://social.msdn.microsoft.com/forums/en-US/clr/thread/1c444dac-5d08-487d-9369-666d1b21706e
我还没有检查,但在那篇帖子中他们甚至说没有使用传入TypeConverter的CultureInfo。它永远是不变的。
string decValue = "1,400.23";
TypeConverter converter = TypeDescriptor.GetConverter(typeof(decimal));
object convertedValue = converter.ConvertFrom(null /* context */, CultureInfo.InvariantCulture, decValue);
所以我想你必须使用一种解决方法。不太好......
答案 1 :(得分:7)
我不喜欢上述解决方案,并想出了这个:
在我的自定义模型绑定器中,如果它是一个小数,我基本上用文化不变量值替换该值,然后将其余的工作移交给默认的模型绑定器。 作为数组的rawvalue对我来说似乎很奇怪,但这是我在原始代码中看到/偷走的内容。
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if(bindingContext.ModelType == typeof(decimal) || bindingContext.ModelType==typeof(Nullable<decimal>))
{
ValueProviderResult valueProviderResult = bindingContext.ValueProvider[bindingContext.ModelName];
if (valueProviderResult != null)
{
decimal result;
var array = valueProviderResult.RawValue as Array;
object value;
if (array != null && array.Length > 0)
{
value = array.GetValue(0);
if (decimal.TryParse(value.ToString(), out result))
{
string val = result.ToString(CultureInfo.InvariantCulture.NumberFormat);
array.SetValue(val, 0);
}
}
}
}
return base.BindModel(controllerContext, bindingContext);
}
答案 2 :(得分:4)
为了使默认模型绑定器满意,似乎总会找到某种形式或其他形式的变通方法!我想知道你是否可以创建一个仅由模型绑定器使用的“伪”属性? (注意,这绝不是优雅的。我自己,我似乎越来越多地采用这样的类似技巧,因为它们起作用并且他们得到了“完成”的工作......)另请注意,如果你使用单独的“ViewModel”(我推荐这个),您可以将此代码放在那里,让您的域模型保持干净整洁。
public class Employee
{
private decimal _Salary;
public string MvcSalary // yes, a string. Bind your form values to this!
{
get { return _Salary.ToString(); }
set
{
// (Using some pseudo-code here in this pseudo-property!)
if (AppearsToBeValidDecimal(value)) {
_Salary = StripCommas(value);
}
}
}
public decimal Salary
{
get { return _Salary; }
set { _Salary = value; }
}
}
P.S。,在我输入之后,我现在回头看它,甚至犹豫发布这个,它太丑了!但如果你认为它可能会有所帮助,我会让你决定......
祝你好运!答案 3 :(得分:0)
您是否尝试将其转换为控制器中的Decimal?这应该可以解决问题:
string _val =“1,000.00”; 十进制_decVal = Convert.ToDecimal(_val); Console.WriteLine(_decVal.ToString());
答案 4 :(得分:0)
嘿我还有一个想法...这是建立在Naweed的答案之上,但仍然会让你使用默认的模型绑定器。这个概念是截取已发布的表单,修改其中的一些值,然后将[modified]表单集合传递给UpdateModel(默认模型绑定程序)方法...我对dealing with checkboxes/booleans使用了此修改版本,以避免除“true”或“false”之外的任何内容在模型绑定器中导致未处理/静默异常的情况。
(你当然希望重构这个更可重用,或许处理所有小数)
public ActionResult myAction(NameValueCollection nvc)
{
Employee employee = new Employee();
string salary = nvc.Get("Salary");
if (AppearsToBeValidDecimal(salary)) {
nvc.Remove("Salary");
nvc.Add("Salary", StripCommas(salary));
}
if (TryUpdateModel(employee, nvc)) {
// ...
}
}
P.S。,我可能对我的NVC方法感到困惑,但我认为这些方法都有效。
答案 5 :(得分:0)
我实现了自定义验证器,增加了分组的有效性。 问题(我在下面的代码中解决了)是parse方法删除了所有千位分隔符,所以1,2,2也被视为有效。
这里是我的十进制活页夹
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web.Mvc;
namespace EA.BUTruck.ContactCenter.Model.Extensions
{
public class DecimalModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
ValueProviderResult valueResult = bindingContext.ValueProvider
.GetValue(bindingContext.ModelName);
ModelState modelState = new ModelState { Value = valueResult };
object actualValue = null;
try
{
var trimmedvalue = valueResult.AttemptedValue.Trim();
actualValue = Decimal.Parse(trimmedvalue, CultureInfo.CurrentCulture);
string decimalSep = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
string thousandSep = CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator;
thousandSep = Regex.Replace(thousandSep, @"\u00A0", " "); //used for culture with non breaking space thousand separator
if (trimmedvalue.IndexOf(thousandSep) >= 0)
{
//check validity of grouping thousand separator
//remove the "decimal" part if exists
string integerpart = trimmedvalue.Split(new string[] { decimalSep }, StringSplitOptions.None)[0];
//recovert double value (need to replace non breaking space with space present in some cultures)
string reconvertedvalue = Regex.Replace(((decimal)actualValue).ToString("N").Split(new string[] { decimalSep }, StringSplitOptions.None)[0], @"\u00A0", " ");
//if are the same, it is a valid number
if (integerpart == reconvertedvalue)
return actualValue;
//if not, could be differences only in the part before first thousand separator (for example original input stirng could be +1.000,00 (example of italian culture) that is valid but different from reconverted value that is 1.000,00; so we need to make a more accurate checking to verify if input string is valid
//check if number of thousands separators are the same
int nThousands = integerpart.Count(x => x == thousandSep[0]);
int nThousandsconverted = reconvertedvalue.Count(x => x == thousandSep[0]);
if (nThousands == nThousandsconverted)
{
//check if all group are of groupsize number characters (exclude the first, because could be more than 3 (because for example "+", or "0" before all the other numbers) but we checked number of separators == reconverted number separators
int[] groupsize = CultureInfo.CurrentCulture.NumberFormat.NumberGroupSizes;
bool valid = ValidateNumberGroups(integerpart, thousandSep, groupsize);
if (!valid)
throw new FormatException();
}
else
throw new FormatException();
}
}
catch (FormatException e)
{
modelState.Errors.Add(e);
}
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
return actualValue;
}
private bool ValidateNumberGroups(string value, string thousandSep, int[] groupsize)
{
string[] parts = value.Split(new string[] { thousandSep }, StringSplitOptions.None);
for (int i = parts.Length - 1; i > 0; i--)
{
string part = parts[i];
int length = part.Length;
if (groupsize.Contains(length) == false)
{
return false;
}
}
return true;
}
}
}
小数?你可以在
之前添加一些代码using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web.Mvc;
namespace EA.BUTruck.ContactCenter.Model.Extensions
{
public class DecimalNullableModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
ValueProviderResult valueResult = bindingContext.ValueProvider
.GetValue(bindingContext.ModelName);
ModelState modelState = new ModelState { Value = valueResult };
object actualValue = null;
try
{
//need this condition against non nullable decimal
if (string.IsNullOrWhiteSpace(valueResult.AttemptedValue))
return actualValue;
var trimmedvalue = valueResult.AttemptedValue.Trim();
actualValue = Decimal.Parse(trimmedvalue,CultureInfo.CurrentCulture);
string decimalSep = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
string thousandSep = CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator;
thousandSep = Regex.Replace(thousandSep, @"\u00A0", " "); //used for culture with non breaking space thousand separator
if (trimmedvalue.IndexOf(thousandSep) >=0)
{
//check validity of grouping thousand separator
//remove the "decimal" part if exists
string integerpart = trimmedvalue.Split(new string[] { decimalSep }, StringSplitOptions.None)[0];
//recovert double value (need to replace non breaking space with space present in some cultures)
string reconvertedvalue = Regex.Replace(((decimal)actualValue).ToString("N").Split(new string[] { decimalSep }, StringSplitOptions.None)[0], @"\u00A0", " ");
//if are the same, it is a valid number
if (integerpart == reconvertedvalue)
return actualValue;
//if not, could be differences only in the part before first thousand separator (for example original input stirng could be +1.000,00 (example of italian culture) that is valid but different from reconverted value that is 1.000,00; so we need to make a more accurate checking to verify if input string is valid
//check if number of thousands separators are the same
int nThousands = integerpart.Count(x => x == thousandSep[0]);
int nThousandsconverted = reconvertedvalue.Count(x => x == thousandSep[0]);
if(nThousands == nThousandsconverted)
{
//check if all group are of groupsize number characters (exclude the first, because could be more than 3 (because for example "+", or "0" before all the other numbers) but we checked number of separators == reconverted number separators
int[] groupsize = CultureInfo.CurrentCulture.NumberFormat.NumberGroupSizes;
bool valid = ValidateNumberGroups(integerpart, thousandSep, groupsize);
if (!valid)
throw new FormatException();
}
else
throw new FormatException();
}
}
catch (FormatException e)
{
modelState.Errors.Add(e);
}
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
return actualValue;
}
private bool ValidateNumberGroups(string value, string thousandSep, int[] groupsize)
{
string[] parts = value.Split(new string[] { thousandSep }, StringSplitOptions.None);
for(int i = parts.Length-1; i > 0; i--)
{
string part = parts[i];
int length = part.Length;
if (groupsize.Contains(length) == false)
{
return false;
}
}
return true;
}
}
}
你需要为double,double?,float,float创建类似的binder? (代码与DecimalModelBinder和DecimalNullableModelBinder相同;你需要在2点替换类型,其中有&#34;十进制&#34;)。
然后在global.asax
ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());
ModelBinders.Binders.Add(typeof(decimal?), new DecimalNullableModelBinder());
ModelBinders.Binders.Add(typeof(float), new FloatModelBinder());
ModelBinders.Binders.Add(typeof(float?), new FloatNullableModelBinder());
ModelBinders.Binders.Add(typeof(double), new DoubleModelBinder());
ModelBinders.Binders.Add(typeof(double?), new DoubleNullableModelBinder());
此解决方案在服务器端正常工作,例如使用jquery globalize和我在此处报告的修复的客户端部分 https://github.com/globalizejs/globalize/issues/73#issuecomment-275792643