小数,逗号和客户端验证问题

时间:2013-11-28 10:37:49

标签: javascript jquery validation asp.net-mvc-4

我正在尝试为nullable<decimal>实现客户端验证,其小数点分隔符可以是逗号(例如:123,45)。

在我的观点中:

...

<div class="editor-label">
    @Html.LabelFor(model => model.Turnover)
</div>
<div class="editor-field">
    @Html.EditorFor(model => model.Turnover)
    @Html.ValidationMessageFor(model => model.Turnover)
</div>

...

@section Scripts {

   @Styles.Render("~/Content/themes/base/css")
   @Scripts.Render("~/bundles/jquery")
   @Scripts.Render("~/bundles/jqueryui")
   @Scripts.Render("~/bundles/jqueryval")
   @Scripts.Render("~/bundles/jQueryFixes")
   ...scripts for this view...
}

我的jQueryFixes会覆盖jquery.validate.jsrange()的{​​{1}}文件:

number()

...正如许多关于此问题的帖子/问题所建议的那样(例如:herehere)。

奇怪的是:

当我尝试提交一个像123,45这样的值时,即使我用Firebug调试了脚本并看到我的overrode函数被调用并返回true,我也无法提交表单。相反,我的EditorFor for decimal值因任何原因而被集中,我似乎无法找出原因。

(我相信我的服务器端验证 - 使用自定义活页夹等 - 工作正常,不是问题:我想要一些关于如何获取表单的帮助提交或为什么输入字段会被聚焦,即使它看起来有效。)

编辑1:

其他信息。在我的BundlesConfig.cs中:

$.validator.methods.range = function (value, element, param) {
    var globalizedValue = value.replace(",", ".");
    return this.optional(element) || (globalizedValue >= param[0] && globalizedValue <= param[1]);
}

$.validator.methods.number = function (value, element) {
    return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:[\s\.,]\d{3})+)(?:[\.,]\d+)?$/.test(value);
}

编辑2:

在@LeftyX建议之后,我尝试使用Globalize脚本(在删除我的jQueryFixes.js之后):

bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                    "~/Scripts/jquery-{version}.js"));
...
bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
                    "~/Scripts/jquery-ui-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                    "~/Scripts/jquery.unobtrusive*",
                    "~/Scripts/jquery.validate*"));
bundles.Add(new ScriptBundle("~/bundles/jQueryFixes").Include(
                    "~/Scripts/jQueryFixes.js")); 
...

...但我仍然面临同样的问题:<script type="text/javascript"> ... $( document ).ready(function() { Globalize.culture("en-US", "pt-PT"); }); $.validator.methods.number = function (value, element) { return this.optional(element) || jQuery.isNumeric(Globalize.parseFloat(value)); } //Fix the range to use globalized methods jQuery.extend(jQuery.validator.methods, { range: function (value, element, param) { var val = Globalize.parseFloat(value); return this.optional(element) || (val >= param[0] && val <= param[1]); } }); ... </script> 返回true,但表单未提交,输入字段也会被重点关注。

如果我在提交时检查输入元素,我可以看到它很快从validator.methods.number转到class="valid",然后再转回class='input-validation-error'。很奇怪。

结论:

@LeftyX为发现相同问题的人提供了一个非常好的完整解决方案。

我已经有了一个可以为空的小数的自定义模型绑定器,但全球化脚本和包含Model / ViewModel中的文化肯定会派上用场。

我的问题的另一个原因可能是我(偶然)包括一些脚本两次。

更新(2015年7月):

globalize.js现在有点不同了。参考。到this answerthe documentation了解更新后的步骤。

2 个答案:

答案 0 :(得分:7)

我为此苦苦挣扎。

对我来说,最好的方法是为小数定义一个自定义绑定器:

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
        {
            //Check if this is a nullable decimal and a null or empty string has been passed
            var isNullableAndNull = (bindingContext.ModelMetadata.IsNullableValueType &&
                                     string.IsNullOrEmpty(valueResult.AttemptedValue));

            //If not nullable and null then we should try and parse the decimal
            if (!isNullableAndNull)
            {
                actualValue = Convert.ToDecimal(valueResult.AttemptedValue, CultureInfo.CurrentCulture);
            }
        }
        catch (FormatException e)
        {
            modelState.Errors.Add(e);
        }

        bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
        return actualValue;
    }
}

并将其绑定在Application_Start中的Global.asax

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

我还使用全球化脚本(带文化),可以找到here或从nuget here下载。

你的包应该是这样的:

bundles.Add(ScriptBundle("~/bundles/jquery").Include(
    "~/Scripts/jquery-{version}.js",
    "~/Scripts/globalize.js",
    "~/Scripts/cultures/globalize.culture.en-GB.js",
    "~/Scripts/cultures/globalize.culture.it-IT.js"
    ));

如果您想支持不同的本地化,当然可以添加更多的文章。

现在,当您的DOM准备就绪(javascript)时,您可以定义您的文化:

Globalize.culture('en-GB');

$.validator.methods.number = function (value, element) {
    return this.optional(element) || jQuery.isNumeric(Globalize.parseFloat(value));
}

//Fix the range to use globalized methods
jQuery.extend(jQuery.validator.methods, {
    range: function (value, element, param) {
    var val = Globalize.parseFloat(value);
    return this.optional(element) || (val >= param[0] && val <= param[1]);
    }
});

$.validator.methods.date = function (value, element) {
    return (this.optional(element) || Globalize.parseDate(value));
}

并自定义您的验证(我也添加了日期)。你已经在jQueryFixes中完成了这项工作。

您可以找到一个可以更改语言的工作示例here(MvcAppForDecimals) 使用工具栏和cookie,以便文化也可以在服务器上更改。

在示例中,我在Application_BeginRequest中读取了cookie或使用了web.config中的默认文化定义:

<globalization enableClientBasedCulture="true" uiCulture="en-GB" culture="en-GB" />

我还定义了一个ActionFilter(LanguageFilterAttribute),它在基本视图模型中注入当前文化,以便客户端使用服务器端的当前集合。

可以找到扩展说明here

有关全球化脚本和文化设置的更多信息here

答案 1 :(得分:1)

关于globalize.js的一小部分更新。现在情况有点不同(而且令人困惑):

包括以下脚本:

<!--
First, we load Globalize's dependencies (`cldrjs` and its supplemental
module).
-->
<script type="text/javascript" src="~/Scripts/cldr.js"></script>
<script type="text/javascript" src="~/Scripts/cldr/event.js"></script>
<script type="text/javascript" src="~/Scripts/cldr/supplemental.js"></script>

<!--
Next, we load Globalize and its modules.
-->
<script type="text/javascript" src="~/Scripts/globalize.js"></script>
<script type="text/javascript" src="~/Scripts/globalize/number.js"></script>

现在我们需要load the I18n content onto Globalize

<script type='text/javascript'>

    // At this point, we have Globalize loaded. But, before we can use it, we
    // need to feed it on the appropriate I18n content (Unicode CLDR). In order
    // to do so, we use `Globalize.load()` and pass the content.

    $.when($.getJSON('/Scripts/cldr/supplemental/likelySubtags.json'),
               $.getJSON('/Scripts/cldr/main/en-GB/numbers.json'),
               ...other locales...
               $.getJSON('/Scripts/cldr/supplemental/numberingSystems.json'))
        .done(function (result1, result2, result3, result4) {
            Globalize.load(result1[0]); //contains data of first executed request
            ...load the other ones...
            Globalize.load(result3[0]); //contains data of third executed request
            Globalize.load(result4[0]); //contains data of fourth executed request

            var glob = Globalize([YOUR-LOCALE]); // e.g. en-UK, pt-PT, pt-BR, es-ES, etc.

            $.validator.methods.number = function (value, element) {
                var number = glob.parseNumber(value);
                return this.optional(element) || jQuery.isNumeric(number);
            }

            //Fix the range to use globalized methods
            jQuery.extend(jQuery.validator.methods, {
                range: function (value, element, param) {
                    var val = glob.formatNumber(value);
                    return this.optional(element) || (val >= param[0] && val <= param[1]);
                }

        });
    });

</script>

I18n内容以JSON here提供。

如果您的getJSON()上有404,请记得添加:

<system.webServer>

    (...)

    <staticContent>
      <mimeMap fileExtension=".json" mimeType="application/json" />
    </staticContent>

</system.webServer>

在您的Web.config(ASP .NET MVC应用程序)中。