ASP.NET Core 6:自定义验证属性与客户端验证

huangapple go评论61阅读模式
英文:

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

问题

以下是代码的翻译部分:

我有一些表单,我不得不为其创建自定义验证属性。我的自定义属性在服务器端工作,但我不希望它去服务器端验证。我希望它能在客户端工作。

这个验证器的作用是查看已选择的属性。如果属性的值已选择,那么此属性就是必需的。

该属性的应用方式如下:

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

如你所见,只有当 CurrentlyPracticing = 1 时,ProfLicense 属性才是必需的。

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

我的模型类(请忽略已注释部分 - 它还在进行中):

public class ApplicationVM
{
    // 其他属性因 Stackoverflow 空间要求而被移除
    [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;
        }
    }
}

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

<!-- 以下为视图的 HTML 代码,已略过具体内容 -->

希望这能帮助你理解代码的翻译。如果有其他问题,请随时提出。

英文:

I have forms that I have had to create custom validation attributes for. My custom attribute works at the server, but I do not want it to go to the server to validate this. I would love for it to work at the client-side.

What this validator does is looks to see what has been selected for a stated property. If the value of the property is selected, then this property is required.

The attribute is applied like this:

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

As you can see, the ProfLicense property is only required if CurrentlyPracticing = 1.

Here is my model class, custom validation attribute code, and view for this project.

My model class (ignore the commented out portions - it is a work in progress):

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

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

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

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

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

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

Here is my custom validation attribute:

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, &quot;data-val&quot;, &quot;true&quot;);
            MergeAttribute(context.Attributes, &quot;data-val-testif&quot;, errorMessage);

            MergeAttribute(context.Attributes, &quot;data-val-testif-propertyname&quot;, _propertyName);
            MergeAttribute(context.Attributes, &quot;data-val-testif-desiredvalue&quot;, _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&lt;string, string&gt; attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }

            attributes.Add(key, value);

            return true;
        }
    }
}

My view (here I think my issue is at the validator section. This is at the bottom of the scripts):

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

        &lt;div asp-validation-summary=&quot;All&quot; class=&quot;text-danger&quot;&gt;
            &lt;span id=&quot;message2&quot;&gt;&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;form-group&quot;&gt;
            &lt;input type=&quot;submit&quot; value=&quot;Submit&quot; class=&quot;btn btn-primary&quot; /&gt;
        &lt;/div&gt;
        &lt;br /&gt;
    &lt;/div&gt;
&lt;/form&gt;
&lt;/div&gt;

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

&lt;script type=&quot;text/javascript&quot;&gt;
   // Other script code removed because of space limitations on stackoverflow....

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

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

    $.validator.unobtrusive.adapters.add(&quot;testif&quot;, &quot;desiredvalue&quot;, function (options) {
        options.rules.testif = {};
        options.
        options.messages[&quot;testif&quot;] = options.message;
    });
&lt;/script&gt;
}

答案1

得分: 2

我为您提供翻译好的部分:

ApplicationVM.cs

public class ApplicationVM
{
    [Display(Name = "是否正在练习?")]
    public int? CurrentlyPracticing { get; set; }

    [Display(Name = "已获得执照")]
    [AnotherTest("CurrentlyPracticing", 1, ErrorMessage = "练习时必填。")]
    public string? ProfLicense { get; set; }
}

AnotherTestAttribute.cs

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

    public AnotherTestAttribute(string propertyName, object desiredValue)
    {
        _propertyName = propertyName;
        _desiredValue = desiredValue;
    }
    // ...(其余代码不翻译)
}

Controller

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

Index.cshtml

@model ApplicationVM
@{
    List<SelectListItem> choices1 = new List<SelectListItem>
    {
        new SelectListItem { Value = "1", Text = "是" },
        new SelectListItem { Value = "0", Text = "否" },
    };

    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="提交" 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>
}

希望这能帮助您理解和使用代码。如果有其他问题,请随时提出。

英文:

I wrote a small working sample you can try reference<br>
ApplicationVM.cs

    public class ApplicationVM
    {

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

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

AnotherTestAttribute.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, &quot;data-val&quot;, &quot;true&quot;);
            MergeAttribute(context.Attributes, &quot;data-val-anothertest&quot;, errorMessage);

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

        private static bool MergeAttribute(IDictionary&lt;string, string&gt; 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&#39;t implement server validation
            return ValidationResult.Success;

        }
    }

Controller

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

Index.cshtml

@model ApplicationVM
@{
    List&lt;SelectListItem&gt; choices1 = new()
            {
                new SelectListItem { Value = &quot;1&quot;,Text=&quot;Yes&quot;},
                new SelectListItem { Value = &quot;0&quot;,Text=&quot;No&quot;},
            };


    ViewBag.YesNo2 = choices1;
}

&lt;div class=&quot;col-md-8 m-auto&quot;&gt;
    &lt;h1 class=&quot;text-center&quot;&gt;PACE Program Initial Application&lt;/h1&gt;
&lt;/div&gt;

&lt;div class =&quot;col-md-8 m-auto&quot;&gt;@Model.ProfLicense&lt;/div&gt;

&lt;div class=&quot;col-md-8 m-auto&quot;&gt;
    &lt;form asp-action=&quot;index&quot; method=&quot;post&quot;&gt;

        &lt;div class=&quot;form-group col-sm-5 col-md-4 col-lg-3&quot;&gt;
            &lt;label asp-for=&quot;CurrentlyPracticing&quot; class=&quot;control-label&quot;&gt;&lt;/label&gt;
            &lt;select asp-for=&quot;CurrentlyPracticing&quot; class=&quot;form-control&quot; id=&quot;CurrentlyPracticing&quot; asp-items=&quot;ViewBag.YesNo2&quot;&gt;&lt;/select&gt;
            &lt;span asp-validation-for=&quot;CurrentlyPracticing&quot; class=&quot;text-danger&quot;&gt;&lt;/span&gt;
        &lt;/div&gt;

        &lt;div class=&quot;form-group col-sm-5 col-md-4 col-lg-3&quot;&gt;
            &lt;label asp-for=&quot;ProfLicense&quot; class=&quot;control-label&quot;&gt;&lt;/label&gt;
            &lt;input asp-for=&quot;ProfLicense&quot; class=&quot;form-control&quot; /&gt;
            &lt;span asp-validation-for=&quot;ProfLicense&quot; class=&quot;text-danger&quot;&gt;&lt;/span&gt;
        &lt;/div&gt;

        &lt;input type=&quot;submit&quot; value=&quot;Submit&quot; class=&quot;btn btn-primary&quot; /&gt;
    &lt;/form&gt;
&lt;/div&gt;



@section scripts {
    &lt;script src=&quot;~/lib/jquery-validation/dist/jquery.validate.min.js&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js&quot;&gt;&lt;/script&gt;
    &lt;script&gt;
        $.validator.addMethod(&quot;anothertest&quot;,
            function (value, element, parameters) {
                var propertyname = $(element).data(&#39;val-anothertest-propertyname&#39;);
                var desiredvalue = $(element).data(&#39;val-anothertest-desiredvalue&#39;);

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

        $.validator.unobtrusive.adapters.addBool(&quot;anothertest&quot;);
    &lt;/script&gt;
}

Test Result<br>
ASP.NET Core 6:自定义验证属性与客户端验证
ASP.NET Core 6:自定义验证属性与客户端验证

huangapple
  • 本文由 发表于 2023年6月1日 04:05:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/76376888.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定