ASP.NET Core 6:具有客户端验证的自定义 vallidation 属性

ASP.NET Core 6 : custom vallidation attribute with client-side validation

提问人:Chris Woodard 提问时间:6/1/2023 最后编辑:Chris Woodard 更新时间:6/2/2023 访问量:328

问:

我有必须为其创建自定义验证属性的表单。我的自定义属性在服务器上起作用,但我不希望它转到服务器来验证这一点。我希望它在客户端工作。

此验证器所做的是查看为所述属性选择的内容。如果选择了该属性的值,则此属性是必需的。

该属性的应用方式如下:

[AnotherTest("CurrentlyPracticing", 1,
 ErrorMessage = "Required if practicing.")]
public string? ProfLicense { get; set; } = string.Empty;

如您所见,仅当 .ProfLicenseCurrentlyPracticing = 1

下面是我的模型类、自定义验证属性代码和此项目的视图。

我的模型类(忽略注释掉的部分 - 这是一项正在进行的工作):

public class ApplicationVM
{
    // other attributes removed because of Stackoverflow space requirements
    [Required(ErrorMessage = "You must answer this question.")]
    [Display(Name = "Are you a licensed medical practitioner?")]
    public int? LicMedPract { get; set; }

    [RequiredIfTrue(nameof(LicMedPract), ErrorMessage = "An answer is required.")]
    [Display(Name = "Currently Practicing?")]
    public int? CurrentlyPracticing { get; set; }

    [RequiredIfTrue(nameof(CurrentlyPracticing), ErrorMessage = "You must select a Specialty.")]
    [Display(Name = "Practice Specialty:")]
    public int? SpecialtyID { get; set; }

    [RequiredIfTrue(nameof(SpecialtyID), ErrorMessage = "You must type an Other Specialty.")]
    [StringLength(25)]
    [Display(Name = "Other Specialty:")]
    public string PracSpecOther { get; set; } = string.Empty;

    [Display(Name = "License Number")]
    [AnotherTest("CurrentlyPracticing", 1,
     ErrorMessage = "Required if practicing.")]
    public string? ProfLicense { get; set; } = string.Empty;

    [MaxLength(2)]
    [Display(Name = "State Licensed")]
    [AnotherTest("CurrentlyPracticing", 1,
     ErrorMessage = "Required if practicing.")]
    public string? ProfLicenseState { get; set; } = string.Empty;
}

这是我的自定义验证属性:

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

namespace pportal2.common.Validation
{
    public class AnotherTestAttribute : ValidationAttribute, IClientModelValidator
    {
        private readonly string _propertyName;
        private readonly object _desiredValue;
        private readonly RequiredAttribute _required;

        public AnotherTestAttribute(string propertyName, Object desiredValue)
        {
            _propertyName = propertyName;
            _desiredValue = desiredValue;
            _required = new RequiredAttribute();
        }

        public void AddValidation(ClientModelValidationContext context)
        {
            var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-testif", errorMessage);

            MergeAttribute(context.Attributes, "data-val-testif-propertyname", _propertyName);
            MergeAttribute(context.Attributes, "data-val-testif-desiredvalue", _desiredValue.ToString());
        }

        protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
        {
            var dependentValue = validationContext.ObjectInstance.GetType().GetProperty(_propertyName).GetValue(validationContext.ObjectInstance, null);

            if (dependentValue.ToString() == _desiredValue.ToString())
            {
                if (!_required.IsValid(value))
                {
                    return new ValidationResult(ErrorMessage);
                }
            }

            return ValidationResult.Success;
        }

        private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }

            attributes.Add(key, value);

            return true;
        }
    }
}

我的观点(在这里我认为我的问题出在验证器部分。这是在脚本的底部):

<div class="col-md-8 m-auto">
<h1 class="text-center">PACE Program Initial Application</h1>
</div>
<hr />
<div class="col-md-8 m-auto">
<form asp-action="Application" id="application">
            <hr />
        </div>
        <div class="form-group">
            <h4>
                <label>Professional Information:</label>
            </h4>
            <div class="form-group col-sm-12">
                <div class="row">
                    <div class="form-group col-sm-6">
                        <label asp-for="LicMedPract" class="control-label"></label>
                        <select asp-for="LicMedPract" class="form-control" asp-items="ViewBag.YesNo2"></select>
                        <span asp-validation-for="LicMedPract" class="text-danger"></span>
                    </div>
                </div>
                <div id="divHiddenPracticeInfo" style="display:none;">
                    <div class="row">
                        <div class="form-group col-sm-5 col-md-4 col-lg-3">
                            <label asp-for="CurrentlyPracticing" class="control-label"></label>
                            <select asp-for="CurrentlyPracticing" class="form-control" asp-items="ViewBag.YesNo2"></select>
                            <span asp-validation-for="CurrentlyPracticing" class="text-danger"></span>
                        </div>
                        <div class=" col-sm-5 col-md-4 col-lg-3" id="divPracticeSpecialty" style="display: none;">
                            <div class="form-group col-md-12">
                                <label asp-for="SpecialtyID" class="control-label"></label>
                                <select asp-for="SpecialtyID" class="form-control" asp-items="ViewBag.Specialty"></select>
                                <span asp-validation-for="SpecialtyID" class="text-danger"></span>
                            </div>
                        </div>
                        <div class=" col-sm-5 col-md-4 col-lg-3" id="divPracSpecOther" style="display: none;">
                            <div class="form-group col-md-12">
                                <label asp-for="PracSpecOther" class="control-label"></label>
                                <input asp-for="PracSpecOther" class="form-control" />
                                <span asp-validation-for="PracSpecOther" class="text-danger"></span>
                            </div>
                        </div>
                    </div>
                    <div class="row">
                        <h5 class="col-md-12">Please include all letters and numbers.</h5>
                        <div class="form-group col-sm-5 col-md-4 col-lg-3">
                            <label asp-for="ProfLicense" class="control-label"></label>
                            <input asp-for="ProfLicense" class="form-control" />
                            <span asp-validation-for="ProfLicense" class="text-danger"></span>
                        </div>
                        <div class="form-group  col-sm-5 col-md-4 col-lg-3">
                            <label asp-for="ProfLicenseState" class="control-label"></label>
                            <input asp-for="ProfLicenseState" class="form-control" />
                            <span asp-validation-for="ProfLicenseState" class="text-danger"></span>
                        </div>
                    </div>
                    <div class="row">
                        <div class="form-group col-sm-5 col-md-4 col-lg-3">
                            <label asp-for="DEA" class="control-label"></label>
                            <input asp-for="DEA" class="form-control" />
                            <span asp-validation-for="DEA" class="text-danger"></span>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <div asp-validation-summary="All" class="text-danger">
            <span id="message2"></span>
        </div>
        <div class="form-group">
            <input type="submit" value="Submit" class="btn btn-primary" />
        </div>
        <br />
    </div>
</form>
</div>

@section Scripts {
@{
    await Html.RenderPartialAsync("_ValidationScriptsPartial");
}

<script type="text/javascript">
   // Other script code removed because of space limitations on stackoverflow....

    $.validator.addMethod("testif", function (value, element, parameters) {
        var practicing = $(parameters[0]).val(), desiredvalue = parameters[1], proflicense = value;
        console.log(practicing[0].val());

        if (practicing && practicing[0] === 1) {
            alert(value);
            return value != null;
        }
    });

    $.validator.unobtrusive.adapters.add("testif", "desiredvalue", function (options) {
        options.rules.testif = {};
        options.
        options.messages["testif"] = options.message;
    });
</script>
}
jquery asp.net-core .net-core .net-6.0 unobtrusive-validation

评论

0赞 Sparky 6/1/2023
你从来没有说过问题是什么。“不工作”是不够的。描述它在做什么以及你期望它做什么。此外,请说明您执行了哪些故障排除,以及是否存在任何 JavaScript 控制台错误。阅读并遵循“询问”部分:stackoverflow.com/conduct
0赞 Chris Woodard 6/1/2023
我该如何描述它。我不知道如何做JavaScript方面。目前,JavaScript 端总是返回该字段无效。我知道代码不正确。我需要这方面的帮助。它需要验证属性中列出的字段是否为列出的选择。如果是这样,则需要验证该字段是否为空。
0赞 Qiang Fu 6/1/2023
你能把问题集中在一个属性上吗?
0赞 Chris Woodard 6/1/2023
我可以。它是属性/验证器,属性 ProfLIcense 或 ProfLicenseState 的 AnotherTest。如果 CurrentPracticing 的值为 1(是),则这两者都需要。正如我所说,属性/验证在服务器端完美运行。我在编写客户端时遇到问题。我无法让它做我需要的事情。我需要 JavaScript 验证器才能发挥相同的功能。
0赞 Chris Woodard 6/1/2023
我试图将视图缩小到jquery/javascript验证器将应用于的部分。

答:

2赞 Qiang Fu 6/2/2023 #1

我写了一个小的工作示例,你可以尝试参考
ApplicationVM.cs

    public class ApplicationVM
    {

        [Display(Name = "Currently Practicing?")]
        public int? CurrentlyPracticing { get; set; }

        [Display(Name = "State Licensed")]
        [AnotherTest("CurrentlyPracticing", 1,ErrorMessage = "Required if practicing.")]
        public string? ProfLicense { get; set; }
    }

另一个测试属性.cs

    public class AnotherTestAttribute : ValidationAttribute, IClientModelValidator
    {
        private readonly string _propertyName;
        private readonly object _desiredValue;

        public AnotherTestAttribute(string propertyName, object desiredValue)
        {
            _propertyName = propertyName;
            _desiredValue = desiredValue;

        }
        public void AddValidation(ClientModelValidationContext context)
        {
            var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-anothertest", errorMessage);

            MergeAttribute(context.Attributes, "data-val-anothertest-propertyname", _propertyName);
            MergeAttribute(context.Attributes, "data-val-anothertest-desiredvalue", _desiredValue.ToString());
        }

        private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
            attributes.Add(key, value);

            return true;
        }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            //I didn't implement server validation
            return ValidationResult.Success;

        }
    }

控制器

        [HttpGet]
        public IActionResult Index()
        {
            return View(new ApplicationVM());
        }
        [HttpPost]
        public IActionResult Index(ApplicationVM applicationVM)
        {
            return View(applicationVM);
        }

索引.cshtml

@model ApplicationVM
@{
    List<SelectListItem> choices1 = new()
            {
                new SelectListItem { Value = "1",Text="Yes"},
                new SelectListItem { Value = "0",Text="No"},
            };


    ViewBag.YesNo2 = choices1;
}

<div class="col-md-8 m-auto">
    <h1 class="text-center">PACE Program Initial Application</h1>
</div>

<div class ="col-md-8 m-auto">@Model.ProfLicense</div>

<div class="col-md-8 m-auto">
    <form asp-action="index" method="post">

        <div class="form-group col-sm-5 col-md-4 col-lg-3">
            <label asp-for="CurrentlyPracticing" class="control-label"></label>
            <select asp-for="CurrentlyPracticing" class="form-control" id="CurrentlyPracticing" asp-items="ViewBag.YesNo2"></select>
            <span asp-validation-for="CurrentlyPracticing" class="text-danger"></span>
        </div>

        <div class="form-group col-sm-5 col-md-4 col-lg-3">
            <label asp-for="ProfLicense" class="control-label"></label>
            <input asp-for="ProfLicense" class="form-control" />
            <span asp-validation-for="ProfLicense" class="text-danger"></span>
        </div>

        <input type="submit" value="Submit" class="btn btn-primary" />
    </form>
</div>



@section scripts {
    <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
    <script>
        $.validator.addMethod("anothertest",
            function (value, element, parameters) {
                var propertyname = $(element).data('val-anothertest-propertyname');
                var desiredvalue = $(element).data('val-anothertest-desiredvalue');

                if ((value.trim() == "") && (document.getElementById(propertyname).value==desiredvalue)) {
                    return false
                }
                else{
                    return true
                }               
            });

        $.validator.unobtrusive.adapters.addBool("anothertest");
    </script>
}

测试结果
enter image description here enter image description here

评论

1赞 Chris Woodard 6/2/2023
非常感谢傅强。我看到我只是不知道如何引用数据元素来完成这项工作。这太完美了,我真的很感谢你给我一个开始,并让我踏上我的自定义验证之旅。非常感谢我的朋友。
1赞 Chris Woodard 6/2/2023
PS - 现在我可以在我的项目中给它起真正有意义的名字并继续前进。我给它起了个名字叫AnotherTest,因为我太沮丧了。