如何在ASP.NET MVC控制器中设置小数分隔符?

23

我正在使用NerdDinner应用程序学习ASP.NET MVC。然而,我遇到了一个与全球化相关的问题,我的服务器使用逗号作为小数点分隔符呈现浮点数,但Virtual Earth地图要求使用点作为小数点分隔符,这导致一些问题。

我已经解决了在视图中映射JavaScript的问题,但是如果我现在尝试提交一个以点号作为小数分隔符的编辑过的晚餐条目,则控制器在更新模型时失败(抛出InvalidOperationException)。我觉得我必须在控制器中设置合适的区域设置,我尝试在OnActionExecuting()中设置它,但没有帮助。

4个回答

65

我最近在一个真实项目中重新审视了这个问题,并最终找到了一个可行的解决方案。正确的解决方案是为类型decimal(和decimal?,如果您使用它们)创建自定义模型绑定器:

using System.Globalization;
using System.Web.Mvc;

public class DecimalModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        object result = null;

        // Don't do this here!
        // It might do bindingContext.ModelState.AddModelError
        // and there is no RemoveModelError!
        // 
        // result = base.BindModel(controllerContext, bindingContext);

        string modelName = bindingContext.ModelName;
        string attemptedValue = bindingContext.ValueProvider.GetValue(modelName)?.AttemptedValue;

        // in decimal? binding attemptedValue can be Null
        if (attemptedValue != null)
        {
            // Depending on CultureInfo, the NumberDecimalSeparator can be "," or "."
            // Both "." and "," should be accepted, but aren't.
            string wantedSeperator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
            string alternateSeperator = (wantedSeperator == "," ? "." : ",");

            if (attemptedValue.IndexOf(wantedSeperator, StringComparison.Ordinal) == -1
                && attemptedValue.IndexOf(alternateSeperator, StringComparison.Ordinal) != -1)
            {
                attemptedValue = attemptedValue.Replace(alternateSeperator, wantedSeperator);
            }

            try
            {
                if (bindingContext.ModelMetadata.IsNullableValueType && string.IsNullOrWhiteSpace(attemptedValue))
                {
                    return null;
                }

                result = decimal.Parse(attemptedValue, NumberStyles.Any);
            }
            catch (FormatException e)
            {
                bindingContext.ModelState.AddModelError(modelName, e);
            }
        }

        return result;
    }
}

然后在Global.asax.cs文件中的Application_Start()方法中:

ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());
ModelBinders.Binders.Add(typeof(decimal?), new DecimalModelBinder());

请注意,这段代码不是我的,我实际上是在Kristof Neirynck的博客这里找到它。 我只编辑了几行并添加了特定数据类型的绑定器,而不是替换默认绑定器。

2
我刚刚更新了我的答案,添加了一个额外的代码检查,以使其正确处理可空属性。我发现,即使要绑定的属性是可空的,如果它找到一个空字符串作为尝试的值,它仍会抛出异常。现在它应该可以正常工作了。 - Pawel Krakowiak
1
这里有一个补充的客户端修复程序需要一起应用。你可以在这里找到它:https://dev59.com/qmsz5IYBdhLWcg3wHUS0#8102159 - Pawel Krakowiak
你不需要这样做。你需要做的是在模型绑定器执行之前设置当前线程的文化,就像这个例子中所示。 - NightOwl888

7

请在您的web.config文件中进行设置

  <system.web>
    <globalization uiCulture="en" culture="en-US" />

您似乎在使用一种使用逗号而不是小数点的语言设置的服务器。您可以将区域设置调整为与您的应用程序设计所需的逗号方式相匹配的一种区域设置,例如en-US。


你的aspx页面头部有什么设置吗?它们中是否有任何文化相关的内容? - Nick Berardi
@Nick:不,里面没有自定义的东西。 - Pawel Krakowiak

0

我对此有不同的看法,你可能会喜欢。我不喜欢接受的答案是它没有检查其他字符。我知道会有一种情况,货币符号会出现在框中,因为我的用户不知道更好的方法。所以是的,我可以用JavaScript来检查并删除它,但如果由于某种原因JavaScript不可用呢?那么额外的字符可能会通过。或者如果有人试图通过传递未知字符来垃圾邮件轰炸你...谁知道呢!所以我决定使用正则表达式。它稍微慢一点,仅仅是微小的性能损耗-对于我的情况来说,100万次正则表达式迭代只需要不到3秒,而用逗号和句号进行字符串替换大约需要1秒。但考虑到我不知道可能出现什么字符,我对这个微小的性能损耗感到满意。

public class DecimalModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext,
                                     ModelBindingContext bindingContext)
    {
        string modelName = bindingContext.ModelName;
        string attemptedValue =
            bindingContext.ValueProvider.GetValue(modelName).AttemptedValue;

        if (bindingContext.ModelMetadata.IsNullableValueType
                && string.IsNullOrWhiteSpace(attemptedValue))
        {
            return null;
        }

        if (string.IsNullOrWhiteSpace(attemptedValue))
        {
            return decimal.Zero;
        }

        decimal value = decimal.Zero;
        Regex digitsOnly = new Regex(@"[^\d]", RegexOptions.Compiled);
        var numbersOnly = digitsOnly.Replace(attemptedValue, "");
        if (!string.IsNullOrWhiteSpace(numbersOnly))
        {
            var numbers = Convert.ToDecimal(numbersOnly);
            value = (numbers / 100m);

            return value;
        }
        else
        {
            if (bindingContext.ModelMetadata.IsNullableValueType)
            {
                return null;
            }

        }

        return value;
    }
}

基本上,对于一个非空的字符串,删除所有不是数字的字符。转换为十进制数。除以100。返回结果。
对我来说没问题。

1
虽然在某些情况下这种方法很有效,但如果小数没有 .00(即存在 JavaScript 验证或其他剥离它的东西),除以 100 的技巧就不起作用了。 - Ben
是的。我注意到在某些情况下,然后确保我的代码总是在发布之前通过JavaScript添加小数位。但在现实生活中,你不能总是期望这种情况发生。 - Colin Wiseman

0

你能使用不变的文化来解析文本吗?抱歉,我没有NerdDinner代码在我面前,但是如果你传递点分十进制数,那么如果告诉它使用不变的文化,解析应该是可以的。例如:

 float i = float.Parse("0.1", CultureInfo.InvariantCulture);

编辑。顺便说一下,我怀疑这是NerdDinner代码中的一个错误,与您之前遇到的问题类似。


不,我不会触碰任何属性。它应该在UpdateModel()中“神奇地”发生,但是它抛出了一个没有详细信息的不太有用的异常。存在两个问题-当从模型呈现数据时,我需要点,因为映射JS会出错,但是当再次将数据放回模型时,这次我需要逗号,否则控制器就会出错。 这一切都有点奇怪,我不确定如何解决它,谷歌也没有帮助,SO也没有。 :( - Pawel Krakowiak
是的,我认为你不能在不改变代码的情况下解决它。我有ND代码,我会在某个时候查看并尝试验证这一点,如果我能修复它,我会发送给你,但恐怕不会很快,因为我必须工作:-) - Steve
好的,我认为你需要撤销你在另一个问题中所做的更改,我已经在那里发布了一个建议来解决这个问题,希望我的修复不会引起你在这里看到的副作用。 - Steve
然后您需要查看https://dev59.com/uXRB5IYBdhLWcg3wvpmc,以了解MVC中日期的工作方式(或其他方式)。 - Steve

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接