Asp.Net MVC 中带有千位分隔符的十进制值

发布于 2024-07-24 20:32:59 字数 2838 浏览 4 评论 0原文

我有一个自定义模型类,其中包含一个十进制成员和一个接受此类条目的视图。 一切都很顺利,直到我添加了 JavaScript 来格式化输入控件内的数字。 当焦点模糊时,格式代码用千位分隔符“,”格式化输入的数字。

问题是我的模态类中的十进制值没有与千位分隔符很好地绑定/解析。 当我用“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>

<% } %>

我按照 Alexander 的建议尝试了使用自定义 ModelBinder 的解决方法。 问题解决了。 但该解决方案与 IDataErrorInfo 实现并不一致。 由于验证而输入 0 时,Salary 值将变为空。 请问有什么建议吗? Asp.Net MVC 团队成员会来 stackoverflow 吗? 我可以从你那里得到一些帮助吗?

按照 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
}

I have a custom model class which contains a decimal member and a view to accept entry for this class. Everything worked well till I added javascripts to format the number inside input control. The format code format the inputted number with thousand separator ',' when focus blur.

The problem is that the decimal value inside my modal class isn't bind/parsed well with thousand separator. ModelState.IsValid returns false when I tested it with "1,000.00" but it is valid for "100.00" without any changes.

Could you share with me if you have any solution for this?

Thanks in advance.

Sample Class

public class Employee
{
    public string Name { get; set; }
    public decimal Salary { get; set; }
}

Sample Controller

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);
    }
}

Sample View

<% using (Html.BeginForm())
   { %>

    Name:   <%= Html.TextBox("Name")%><br />
    Salary: <%= Html.TextBox("Salary")%><br />

    <button type="submit">Save</button>

<% } %>

I tried a workaround with Custom ModelBinder as Alexander suggested. The probelm solved. But the solution doesn't go well with IDataErrorInfo implementation. The Salary value become null when 0 is entered because of the validation. Any suggestion, please?
Do Asp.Net MVC team members come to stackoverflow? Can I get a little help from you?

Updated Code with Custom Model Binder as Alexander suggested

Model Binder

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;
    }
}

Employee Class

    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
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

深巷少女 2024-07-31 20:32:59

其背后的原因是,在 ValueProviderResult.cs 的 ConvertSimpleType 中使用了 TypeConverter。

小数的 TypeConverter 不支持千位​​分隔符。
请阅读此处:http: //social.msdn.microsoft.com/forums/en-US/clr/thread/1c444dac-5d08-487d-9369-666d1b21706e

我还没有检查,但在那篇文章中他们甚至说 CultureInfo 传递到未使用类型转换器。 它永远是不变的。

           string decValue = "1,400.23";

        TypeConverter converter = TypeDescriptor.GetConverter(typeof(decimal));
        object convertedValue = converter.ConvertFrom(null /* context */, CultureInfo.InvariantCulture, decValue);

所以我想你必须使用解决方法。 不太好...

The reason behind it is, that in ConvertSimpleType in ValueProviderResult.cs a TypeConverter is used.

The TypeConverter for decimal does not support a thousand separator.
Read here about it: http://social.msdn.microsoft.com/forums/en-US/clr/thread/1c444dac-5d08-487d-9369-666d1b21706e

I did not check yet, but at that post they even said the CultureInfo passed into TypeConverter is not used. It will always be Invariant.

           string decValue = "1,400.23";

        TypeConverter converter = TypeDescriptor.GetConverter(typeof(decimal));
        object convertedValue = converter.ConvertFrom(null /* context */, CultureInfo.InvariantCulture, decValue);

So I guess you have to use a workaround. Not nice...

迷乱花海 2024-07-31 20:32:59

我不喜欢上面的解决方案,并提出了这个:

在我的自定义模型绑定程序中,我基本上将值替换为区域性不变值(如果它是小数),然后将其余工作移交给默认模型绑定程序。
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);
    }

I didn't like the solutions above and came up with this:

In my custom modelbinder, I basically replace the value with the culture invariant value if it is a decimal and then hand over the rest of the work to the default model binder.
The rawvalue being a array seems strange to me, but this is what I saw/stole in the original code.

        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);
    }
能否归途做我良人 2024-07-31 20:32:59

似乎总是可以找到某种形式的解决方法,以使默认模型绑定器满意! 我想知道您是否可以创建一个仅由模型绑定器使用的“伪”属性? (注意,这绝不是优雅的。我自己,我似乎越来越频繁地诉诸类似的技巧,只是因为它们有效并且它们“完成”了工作......)还要注意,如果您使用的是单独的“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; }
    }
}

PS,写完之后现在回头看还犹豫要不要发,太丑了! 但如果您认为这可能有帮助,我会让您决定...

祝您好运!
-麦克风

It seems there are always workarounds of some form or another to be found in order to make the default model binder happy! I wonder if you could create a "pseudo" property that is used only by the model binder? (Note, this is by no means elegant. Myself, I seem to resort to similar tricks like this more and more often simply because they work and they get the job "done"...) Note also, if you were using a separate "ViewModel" (which I recommend for this), you could put this code in there, and leave your domain model nice and clean.

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., after I typed this up, I look back at it now and am even hesitating to post this, it is so ugly! But if you think it might be helpful I'll let you decide...

Best of luck!
-Mike

放赐 2024-07-31 20:32:59

我实现了自定义验证器,添加了分组的有效性。
问题(我在下面的代码中解决)是解析方法删除所有千位分隔符,因此 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;
    }
 }
}

对于小数? nullable 你需要在之前添加一些代码

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? 创建类似的绑定器 (代码与 DecimalModelBinder 和 DecimalNullableModelBinder 相同;您只需替换有“decimal”的 2 点中的类型)。

然后在 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

I implement custom validator, adding validity of grouping.
The problem (that i solved in code below)is that parse method remove all thousands separator, so also 1,2,2 is considered valid.

Here my binder for decimal

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;
    }
 }
}

For decimal? nullable you need to add a little code before

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;
    }
 }

}

You need to create similar binder for double, double?, float, float? (the code is the same of DecimalModelBinder and DecimalNullableModelBinder; you need just to replace type in 2 point where there is "decimal").

Then in 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());

This solution works fine on server side, like the client part using jquery globalize and my fixing reported here
https://github.com/globalizejs/globalize/issues/73#issuecomment-275792643

诗化ㄋ丶相逢 2024-07-31 20:32:59

您是否尝试在控制器中将其转换为十进制? 这应该可以解决问题:

string _val = "1,000.00";
十进制 _decVal = Convert.ToDecimal(_val);
Console.WriteLine(_decVal.ToString());

Did you try to convert it to Decimal in the controller? This should do the trick:

string _val = "1,000.00";
Decimal _decVal = Convert.ToDecimal(_val);
Console.WriteLine(_decVal.ToString());

一抹微笑 2024-07-31 20:32:59

嘿,我还有一个想法......这建立在 Naweed 的答案之上,但仍然允许您使用默认的模型绑定器。 这个概念是拦截发布的表单,修改其中的一些值,然后将[修改的]表单集合传递给 UpdateModel(默认模型绑定器)方法...我使用此的修改版本 处理复选框/布尔值,以避免出现“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)) {
        // ...
    }
}

PS,我可能对我的 NVC 方法感到困惑,但我认为这些会起作用。

Hey I had one more thought... This builds on Naweed's answer, but will still let you use the default model binder. The concept is to intercept the posted form, modify some of the values in it, then pass the [modified] form collection to the UpdateModel (default model binder) method... I use a modified version of this for dealing with checkboxes/booleans, to avoid the situation where anything other than "true" or "false" causes an unhandled/silent exception within the model binder.

(You would of course want to refactor this to be more re-useable, to perhaps deal with all decimals)

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., I may be confused on my NVC methods, but I think these will work.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文