【问题标题】:ASP.NET Core 2 - Default number input validation is overriding my custom client validationASP.NET Core 2 - 默认数字输入验证覆盖我的自定义客户端验证
【发布时间】:2020-01-31 05:04:31
【问题描述】:

我有两个输入需要相互验证。它们是最小值和最大值。这是我定义它们的部分观点。

<div class="form-group">
    <label asp-for="MinTubes" class="control-label"></label>
    <input asp-for="MinTubes" class="form-control" />
    <span asp-validation-for="MinTubes" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="MaxTubes" class="control-label"></label>
    <input asp-for="MaxTubes" class="form-control" />
    <span asp-validation-for="MaxTubes" class="text-danger"></span>
</div>

下面是它们映射到的属性:

[Display(Name = "Min Tubes")]
[MinToValidation("MaxTubes")]
public int MinTubes { get; set; }

[Display(Name = "Max Tubes")]
[MaxToValidation("MinTubes")]
public int MaxTubes { get; set; }

这会产生带有标签和验证消息的 &lt;input type='number'&gt; 元素。

我创建了两个自定义验证属性来使用它们。我只会发布 MaxToValidationAttribute 类,因为另一个在功能上是相同的。


using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using System;
using System.ComponentModel.DataAnnotations;

namespace CCM_Util.CustomAttributes
{
    public class MaxToValidationAttribute : ValidationAttribute, IClientModelValidator
    {

        public MaxToValidationAttribute(string min)
            : base("{0} must be greater than or equal to {1}")
        {
            Min = min;
        }

        public string Min { get; set; }

        public string FormatErrorMessage(string name, string minName)
        {
            return string.Format(ErrorMessageString, name, minName);
        }

        protected override ValidationResult
            IsValid(object firstValue, ValidationContext validationContext)
        {
            var firstComparable = firstValue as IComparable;
            var secondComparable = GetSecondComparable(validationContext);

            if (firstComparable != null && secondComparable != null)
            {
                if (firstComparable.CompareTo(secondComparable) < 0)
                {
                    object obj = validationContext.ObjectInstance;
                    var thing = obj.GetType().GetProperty(Min);
                    var displayName = (DisplayAttribute)Attribute.GetCustomAttribute(thing, typeof(DisplayAttribute));

                    return new ValidationResult(
                        FormatErrorMessage(validationContext.DisplayName, displayName.GetName()));
                }
            }

            return ValidationResult.Success;
        }

        protected IComparable GetSecondComparable(
            ValidationContext validationContext)
        {
            var propertyInfo = validationContext
                                  .ObjectType
                                  .GetProperty(Min);
            if (propertyInfo != null)
            {
                var secondValue = propertyInfo.GetValue(
                    validationContext.ObjectInstance, null);
                return secondValue as IComparable;
            }
            return null;
        }

        public void AddValidation(ClientModelValidationContext context)
        {
            context.Attributes.Add("data-val-min", Min);
            context.Attributes.Add("data-val-ismax", "true");
        }
    }
}

然后,在 Default.js 中,我将以下函数作为 document.ready 函数的一部分运行。

function minMaxValidate() {
    $("input[data-val-ismin='true']").each(function (i, ele) {
        $(ele).change(function () {
            var maxName = $(this).attr("data-val-max");
            var minName = $(this).attr("name");
            var minValue = parseFloat($(this).val());
            var max = $("input[data-val-ismax='true'][name='" + maxName + "']");
            var maxValue = max.val();
            if (maxValue == "") { return }
            maxValue = parseFloat(maxValue);
            var validationMessage = $("span[data-valmsg-for='" + $(this).attr("name") + "']");
            if (minValue > maxValue) {
                validationMessage.html(minName + " must not be greater than " + maxName);
                makeError(validationMessage);
            }
            else {
                validationMessage.html("");
                makeValid(validationMessage);
            }
        });
    });
    $("input[data-val-ismax='true']").each(function (i, ele) {
        $(ele).change(function () {
            var minName = $(this).attr("data-val-min");
            var maxName = $(this).attr("name");
            var maxValue = parseFloat($(this).val());
            var min = $("input[data-val-ismin='true'][name='" + minName + "']");
            var minValue = min.val();
            if (minValue == "") { return }
            minValue = parseFloat(minValue);
            var validationMessage = $("span[data-valmsg-for='" + $(this).attr("name") + "']");
            if (minValue > maxValue) {
                validationMessage.html(maxName + " must not be less than " + minName);
                makeError(validationMessage);
            }
            else {
                validationMessage.html("");
                makeValid(validationMessage);
            }

        });
    });
}

makeErrormakeValid 函数本质上只是将validationMessage 的类分别更改为field-validation-errorfield-validation-valid,它们管理提交处理程序。

我知道这种类型的验证设置是有效的,因为我在其他地方使用它没有问题。

我已经逐步完成了更改处理程序,它们正在按应有的方式工作。但是,在错误消息出现并且代码退出后,它又消失了。我的猜测是 .NET 的默认数字输入验证正在接管,并且由于输入都是有效数字,因此它会删除错误消息。

有什么方法可以禁用 .NET 对数字类型输入的默认验证,这样我就可以自己处理它,而不会让外部黑盒代码弄乱我的东西?

谢谢。

编辑:

这里是完整的 view 和 Default.js 文件,以防万一。

@model Coils.CoilParts.Distributor
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@{
    ViewData["Title"] = ViewData["ProgramName"];
}


<h1>Create</h1>

<h4>Distributor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Name" class="control-label"></label>
                <input asp-for="Name" class="form-control" />
                <span asp-validation-for="Name" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Material" class="control-label"></label>
                @Html.DropDownListFor(m => m.Material.Key, ((IEnumerable<SelectListItem>)ViewData["Materials"]))
                <span asp-validation-for="Material" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Type" class="control-label"></label>
                <input asp-for="Type" class="form-control" />
                <span asp-validation-for="Type" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="MinTubes" class="control-label"></label>
                <input asp-for="MinTubes" class="form-control" />
                <span asp-validation-for="MinTubes" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="MaxTubes" class="control-label"></label>
                <input asp-for="MaxTubes" class="form-control" />
                <span asp-validation-for="MaxTubes" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="InletSize" class="control-label"></label>
                <input asp-for="InletSize" class="form-control" />
                <span asp-validation-for="InletSize" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="PartNumber" class="control-label"></label>
                <input asp-for="PartNumber" class="form-control" />
                <span asp-validation-for="PartNumber" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Weight" class="control-label"></label>
                <input asp-for="Weight" class="form-control" />
                <span asp-validation-for="Weight" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>


$(document).ready(function () {
    breadcrumbs();
    measurementsValidate();
    minMaxValidate();
});

var cgi_menus = {
    'act': "the Accounting Menu",
    'adm': "the Administation Menu",
    'car': "the Corrective Action Request Menu",
    'cprA': "the A+Pro Menu",
    'cprC': "the CoilPro Menu",
    'crm': "the Customer Relations Menu",
    'dms': "the Document Management Menu",
    'eng': "the Engineering Menu",
    'etb': "the Engineering Toolbox Menu",
    'ldc': "the Refrigeration Load Simulator Menu",
    'mgt': "the Management Menu",
    'prd': "the Production Menu",
    'pur': "the Purchasing Menu",
    'qcd': "the Quality Control Menu",
    'rpt': "the Report Menu",
    'sls': "the Sales Menu",
    'sup': "the Administrator's Menu",
    'usr': "the User Management Menu",
    'utl': "the Utility Menu",
};

function breadcrumbs() {
    var tab = {};
    localStorage[curURL] = curProgram;
    if (sessionStorage.tab) {
        tab = JSON.parse(sessionStorage.tab);
    }
    else {
        tab.cur_url = "";
        tab.prev_url = "";
        tab.history = [];
    }

    // handle coming from an existing Perl CGI menu
    if (!tab.prev_url && document.referrer) {
        var menu_info = document.referrer.match(/cgi-s[\\/](\w{3})[\\/]menu(\w?)\.cgi/);
        var referrer = new URL(document.referrer);
        if (menu_info && referrer.hostname === document.location.hostname) {
            tab.cur_url = document.referrer;
            var cgi_area = menu_info[1];
            if (cgi_area == "cpr") { cgi_area += menu_info[2]; }
            localStorage[tab.cur_url] = cgi_menus[cgi_area];
        }
    }

    tab.time = Date.now();
    tab.history.push(tab.prev_url);
    tab.prev_url = tab.cur_url;
    tab.cur_url = curURL;
    if (tab.prev_url == tab.cur_url) {
        tab.prev_url = tab.history.pop();
    }
    else if (tab.cur_url == tab.history[tab.history.length - 1]) {
        tab.history.pop();
        tab.prev_url = tab.history.pop();
    }

    if (tab.prev_url) {
        $("#breadcrumbs").css("display", "inline-block");
        $("#breadcrumbs").attr("href", tab.prev_url);
        $("#breadcrumbs").html("< Back to " + localStorage[tab.prev_url]);
    }

    sessionStorage.tab = JSON.stringify(tab);
}

function minMaxValidate() {
    $("input[data-val-ismin='true']").each(function (i, ele) {
        $(ele).change(function () {
            var maxName = $(this).attr("data-val-max");
            var minName = $(this).attr("name");
            var minValue = parseFloat($(this).val());
            var max = $("input[data-val-ismax='true'][name='" + maxName + "']");
            var maxValue = max.val();
            if (maxValue == "") { return }
            maxValue = parseFloat(maxValue);
            var validationMessage = $("span[data-valmsg-for='" + $(this).attr("name") + "']");
            if (minValue > maxValue) {
                validationMessage.html(minName + " must not be greater than " + maxName);
                makeError(validationMessage);
            }
            else {
                validationMessage.html("");
                makeValid(validationMessage);
            }
        });
    });
    $("input[data-val-ismax='true']").each(function (i, ele) {
        $(ele).change(function () {
            var minName = $(this).attr("data-val-min");
            var maxName = $(this).attr("name");
            var maxValue = parseFloat($(this).val());
            var min = $("input[data-val-ismin='true'][name='" + minName + "']");
            var minValue = min.val();
            if (minValue == "") { return }
            minValue = parseFloat(minValue);
            var validationMessage = $("span[data-valmsg-for='" + $(this).attr("name") + "']");
            if (minValue > maxValue) {
                validationMessage.html(maxName + " must not be less than " + minName);
                makeError(validationMessage);
            }
            else {
                validationMessage.html("");
                makeValid(validationMessage);
            }

        });
    });
}

function measurementsValidate() {
    $("input[data-val-measurement='true']").each(function (i, ele) {
        var enforceUnits = $(ele).attr("data-val-units");
        var units = "";
        var hasMax = $(ele).attr("data-val-max");
        var max = 0;
        var hasMin = $(ele).attr("data-val-min");
        var min = 0;
        var value = $(ele).val();
        var validationRegex = /[0-9/.]+ [a-z./\^0-9*()]+/i;
        var name = $("label[for='" + $(ele).attr("name") + "']").html();
        var validationMessage = $("span[data-valmsg-for='" + $(ele).attr("name") + "']");

        if (typeof (enforceUnits) !== undefined && typeof (enforceUnits) !== false) {
            enforceUnits = true;
            units = $(ele).attr("data-val-units");
            if (hasMax != null && hasMax != false) {
                hasMax = true;
                max = $(ele).attr("data-val-max");
            }
            if (hasMin != null && hasMin != false) {
                hasMin = true;
                min = $(ele).attr("data-val-min");
            }
            $(ele).change(function () {
                var value = $(this).val(); // don't know why this has to be re-evaluated, but it does

                if (!value.match(validationRegex)) {
                    validationMessage.html(name + " must be a valid Measurement. (example: 12 in)");
                    makeError(validationMessage);
                }
                else {
                    $.post("/Validations/Measurements", { EnforceUnits: enforceUnits, Units: units, HasMax: hasMax, Max: max, HasMin: hasMin, Min: min, Value: value })
                        .done(function (result) {
                            if (result == "true") {
                                validationMessage.html("");
                                makeValid(validationMessage);
                            }
                            else {
                                validationMessage.html(name + result);
                                makeError(validationMessage);
                            }
                        })
                        .fail(function () {
                            validationMessage.html("");
                            makeValid(validationMessage);
                        });
                }
            });
        }
        else {
            $(ele).change(function () {
                if (!value.match(validationRegex)) {
                    validationMessage.html(name + " must be a valid Measurement. (example: 12 in)");
                    makeError(validationMessage);
                }
                else {
                    validationMessage.html("");
                    makeValid(validationMessage);
                }
            });
        }

    });
}

function makeError(ele) {
    $(ele).removeClass("field-validation-valid");
    $(ele).addClass("field-validation-error");
    $(ele).closest("form").unbind("submit");
    $(ele).closest("form").submit(function () { return false });
}

function makeValid(ele) {
    $(ele).removeClass("field-validation-error");
    $(ele).addClass("field-validation-valid");
    $(ele).closest("form").unbind("submit");
    $(ele).closest("form").submit(checkFormValidation);
}

function checkFormValidation() {
    if ($(this).find(".field-validation-error")[0]) {
        return false;
    }
    return true;
}

编辑 2:

这是正在发生的事情的 GIF:https://i.imgur.com/eAiqxQJ.mp4

【问题讨论】:

  • “出现错误信息,代码退出后,又消失了”是什么意思。我做了一个演示来测试你的代码,但似乎没有问题。您能否分享更多可以重现该问题的 jQuery 文件和查看文件的详细信息?
  • @XueliChen 当我单步执行 javascript 时,它显示错误消息就好了,但是一旦更改处理程序退出,错误消息就会再次消失。当我检查元素时,该类已被重置为有效。我最好的猜测是默认数字输入验证覆盖了我自己的代码。我将编辑问题以包含整个视图文件和 Default.js 文件,但我不确定它们有什么相关性。

标签: c# jquery validation asp.net-core


【解决方案1】:

通常情况下,解决方案是“如果你不能打败他们,就加入他们”。

解决方案不是在我自定义处理的元素上禁用 jQuery 验证,而是依靠 jQuery 验证库并以正确的方式使用它。 .NET 端的一切都保持不变,但 minMaxValidate() 函数变成了这样:

function minMaxValidate() {
    $.validator.addMethod("maxTo", function (value, element, param) {
        var $element = $(element), $min;

        if (typeof (param) === "string") {
            $min = $(param);
        } else {
            $min = $("input[name='" + $element.attr("data-val-min") + "']");
        }

        if (this.settings.onfocusout) {
            $min.off(".validate-maxTo").on("blur.validate-maxTo", function () {
                $element.valid();
            });
        }
        return parseInt(value) >= parseInt($min.val());
    }, "Max must not be less than min");

    $("input[data-val-ismax]").each(function (i, ele) {
        $(ele).rules('add', { maxTo: true });
    });

    $.validator.addMethod("minTo", function (value, element, param) {
        var $element = $(element), $max;

        if (typeof (param) === "string") {
            $max = $(param);
        } else {
            $max = $("input[name='" + $element.attr("data-val-max") + "']");
        }

        if (this.settings.onfocusout) {
            $max.off(".validate-minTo").on("blur.validate-minTo", function () {
                $element.valid();
            });
        }
        return parseInt(value) <= parseInt($max.val());
    }, "Min must not be greater than max");

    $("input[data-val-ismin]").each(function (i, ele) {
        $(ele).rules('add', { minTo: true });
    });
}

这会为正常的 jQuery 验证添加方法和规则以自行运行。

这正是我想要的。感谢This post 的帮助。

【讨论】:

    猜你喜欢
    • 2015-01-01
    • 1970-01-01
    • 2010-09-05
    • 1970-01-01
    • 1970-01-01
    • 2014-03-19
    • 2019-04-18
    • 1970-01-01
    • 2017-05-03
    相关资源
    最近更新 更多