【问题标题】:How to make RequiredIf Client-side and server-side validation in ASP.NET Core 3.1 MVC?如何在 ASP.NET Core 3.1 MVC 中进行RequiredIf 客户端和服务器端验证?
【发布时间】:2020-12-03 19:49:56
【问题描述】:

我必须在 ASP.NET Core 3.1 中进行客户端和服务器端验证。我浏览了很多博客,但对 ASP.NET Core MVC 的了解还不够。

我必须进行RequiredIf 类型的验证。当BusinessTypeBusiness 时,VATNumber 字段是必需的,但如果BusinessTypePersonal,则此字段是可选的。请帮忙。

public enum BusinessType
{
    Personal,
    Business
}

public class Profile
{           
        [Required(ErrorMessage = "Please enter name")]
        public string Name { get; set; }

        [Required(ErrorMessage = "Please enter email")]
        [EmailAddress]
        public string Email { get; set; }

        [Required(ErrorMessage = "Please choose business type")]
        [EnumDataType(typeof(BusinessType))]
        public BusinessType BusinessType { get; set; }    

        [Display(Name="VAT Number")]
        public string VATNumber { get; set; }
}

【问题讨论】:

标签: c# asp.net-core asp.net-core-mvc


【解决方案1】:

您可以像下面这样自定义验证:

public class RequieredIfAttribute : ValidationAttribute
{
    private readonly string _otherProperty;

    public RequieredIfAttribute(string otherProperty)
    {
        _otherProperty = otherProperty;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        string data = (string)value;

        BusinessType selectedType = (BusinessType)validationContext.ObjectType.GetProperty(_otherProperty).GetValue(validationContext.ObjectInstance, null);

        if (string.IsNullOrEmpty(data) && selectedType == BusinessType.Business)
        {
            return new ValidationResult("VATNumber is requiered.");
        }

        return ValidationResult.Success;
    }
}

型号:

public class Profile
{
    [Required(ErrorMessage = "Please enter name")]
    public string Name { get; set; }

    [Required(ErrorMessage = "Please enter email")]
    [EmailAddress]
    public string Email { get; set; }

    [Required(ErrorMessage = "Please choose business type")]
    [EnumDataType(typeof(BusinessType))]
    public BusinessType BusinessType { get; set; }

    [RequieredIf("BusinessType")]        //add this...
    [Display(Name = "VAT Number")]
    public string VATNumber { get; set; }
}

查看(索引.cshtml):

@model Profile
<form asp-action="Create">
    <select asp-for="BusinessType" asp-items="Html.GetEnumSelectList<BusinessType>()"></select>
    <div class="form-group">
        <label asp-for="VATNumber" class="control-label"></label>
        <input asp-for="VATNumber" class="form-control" />
        <span asp-validation-for="VATNumber" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Email" class="control-label"></label>
        <input asp-for="Email" class="form-control" />
        <span asp-validation-for="Email" class="text-danger"></span>
    </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">
        <input type="submit" value="Create" class="btn btn-primary" />
    </div>
</form>

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

控制器:

[HttpPost]
public IActionResult Create(Profile profile)
{
    if(!ModelState.IsValid)
    {
        //do your stuff...
        return View("Index");
    }
    //do your stuff...
    return View("Privacy");
}

结果:

【讨论】:

  • 这仅进行服务器端验证。如何扩展它以支持客户端验证?
【解决方案2】:

最后,我实现了客户端和服务器端验证来满足我的要求。完整代码如下:

枚举

   public enum BusinessType
    {
        Personal,
        Business
      }

个人资料模型

 public class Profile
    {
        [Required(ErrorMessage = "Please enter name")]
        public string Name { get; set; }

        [Required(ErrorMessage = "Please enter email")]
        [EmailAddress]
        public string Email { get; set; }

        [Required(ErrorMessage = "Please choose business type")]
        [EnumDataType(typeof(BusinessType))]
        public BusinessType BusinessType { get; set; }

        [RequiredIf("BusinessType", BusinessType.Business, ErrorMessage = "VAT number is required")]
        [Display(Name = "VAT Number")]
        public string VATNumber { get; set; }
    }

RequiredIfAttribute 类

 public class RequiredIfAttribute : ValidationAttribute, IClientModelValidator
    {
        public string PropertyName { get; set; }
        public object Value { get; set; }


        public RequiredIfAttribute(string propertyName, object value, string errorMessage = "")
        {
            PropertyName = propertyName;
            ErrorMessage = errorMessage;
            Value = value;
        }


        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var instance = validationContext.ObjectInstance;
            var type = instance.GetType();
            var proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
            if (proprtyvalue != null)
            {
                if (proprtyvalue.ToString() == Value.ToString() && value == null)
                {
                    return new ValidationResult(ErrorMessage);
                }
            }
            return ValidationResult.Success;
        }


        public void AddValidation(ClientModelValidationContext context)
        {
            context.Attributes.Add("data-val", "true");
            context.Attributes.Add("data-val-vatNumber", ErrorMessage);
            context.Attributes.Add("data-val-vatNumber-businessType", Value.ToString());
        }
    }

requiredIfBusinessTypeValidate.js

$.validator.addMethod('vatNumber', function (value, element, params) {

    var genre = $(params[0]).val(), businessType = params[1], vat = value;

    var selectedBusinessType = $("#BusinessType option:selected").text();

    if (selectedBusinessType === businessType) {
        if (value.length === 0) {
            return false;
        } else {
            return true;
        }
    } else {
        return true;
    }
});

$.validator.unobtrusive.adapters.add('vatNumber', ['businessType'], function (options) {
    var element = $(options.form).find('select#BusinessType')[0];

    options.rules['vatNumber'] = [element, options.params['businessType']];
    options.messages['vatNumber'] = options.message;
});

查看

<h4>Profile</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Index">
            <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="Email" class="control-label"></label>
                <input asp-for="Email" class="form-control" />
                <span asp-validation-for="Email" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="BusinessType" class="control-label"></label>
                <select asp-for="BusinessType" class="form-control" asp-items="Html.GetEnumSelectList<BusinessType>()">
                    <option value="">Choose</option>
                </select>
                <span asp-validation-for="BusinessType" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="VATNumber" class="control-label"></label>
                <input asp-for="VATNumber" class="form-control" />
                <span asp-validation-for="VATNumber" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

 @section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
    <script src="~/js/requiredIfBusinessTypeValidate.js"></script>
 }

控制器

   public class ProfileController : Controller
    {
       
        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Index(Profile profile)
        {
            if (ModelState.IsValid)
            {
                // Write business logic here ...
            }
            return View(profile);
        }
    }

【讨论】:

    【解决方案3】:

    除了使用硬编码名称之外,我们还可以根据注释值使其动态化。 这里是服务器端

    /// <summary>
    /// Provides conditional validation based on related property value.
    /// </summary>
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public sealed class RequiredIfAttribute : RequiredAttribute, IClientModelValidator
    {
        #region Properties
    
        /// <summary>
        /// Gets or sets the other property name that will be used during validation.
        /// </summary>
        /// <value>
        /// The other property name.
        /// </value>
        public string OtherProperty { get; private set; }
    
        /// <summary>
        /// Gets or sets the display name of the other property.
        /// </summary>
        /// <value>
        /// The display name of the other property.
        /// </value>
        public string OtherPropertyDisplayName { get; set; }
    
        /// <summary>
        /// Gets or sets the other property value that will be relevant for validation.
        /// </summary>
        /// <value>
        /// The other property value.
        /// </value>
        public object OtherPropertyValue { get; private set; }
    
        /// <summary>
        /// Gets or sets a value indicating whether other property's value should match or differ from provided other property's value (default is <c>false</c>).
        /// </summary>
        /// <value>
        ///   <c>true</c> if other property's value validation should be inverted; otherwise, <c>false</c>.
        /// </value>
        /// <remarks>
        /// How this works
        /// - true: validated property is required when other property doesn't equal provided value
        /// - false: validated property is required when other property matches provided value
        /// </remarks>
        public bool IsInverted { get; set; }
    
        /// <summary>
        /// Gets a value that indicates whether the attribute requires validation context.
        /// </summary>
        /// <returns><c>true</c> if the attribute requires validation context; otherwise, <c>false</c>.</returns>
        public override bool RequiresValidationContext
        {
            get { return true; }
        }
    
        #endregion
    
        #region Constructor
    
        /// <summary>
        /// Initializes a new instance of the <see cref="RequiredIfAttribute"/> class.
        /// </summary>
        /// <param name="otherProperty">The other property.</param>
        /// <param name="otherPropertyValue">The other property value.</param>
        public RequiredIfAttribute(string otherProperty, object otherPropertyValue)
            : base()
        {
            this.OtherProperty = otherProperty;
            this.OtherPropertyValue = otherPropertyValue;
            this.IsInverted = false;
        }
    
        #endregion
    
        /// <summary>
        /// Applies formatting to an error message, based on the data field where the error occurred.
        /// </summary>
        /// <param name="name">The name to include in the formatted message.</param>
        /// <returns>
        /// An instance of the formatted error message.
        /// </returns>
        public override string FormatErrorMessage(string name)
        {
            return string.Format(
                CultureInfo.CurrentCulture,
                base.ErrorMessageString,
                name,
                this.OtherPropertyDisplayName ?? this.OtherProperty,
                this.OtherPropertyValue,
                this.IsInverted ? "other than " : "of ");
        }
    
        /// <summary>
        /// Validates the specified value with respect to the current validation attribute.
        /// </summary>
        /// <param name="value">The value to validate.</param>
        /// <param name="validationContext">The context information about the validation operation.</param>
        /// <returns>
        /// An instance of the <see cref="T:System.ComponentModel.DataAnnotations.ValidationResult" /> class.
        /// </returns>
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            if (validationContext == null)
            {
                throw new ArgumentNullException("validationContext");
            }
    
            PropertyInfo otherProperty = validationContext.ObjectType.GetProperty(this.OtherProperty);
            if (otherProperty == null)
            {
                return new ValidationResult(
                    string.Format(CultureInfo.CurrentCulture, "Could not find a property named '{0}'.", this.OtherProperty));
            }
    
            object otherValue = otherProperty.GetValue(validationContext.ObjectInstance);
    
            // check if this value is actually required and validate it
            if (!this.IsInverted && object.Equals(otherValue, this.OtherPropertyValue) ||
                this.IsInverted && !object.Equals(otherValue, this.OtherPropertyValue))
            {
                if (value == null)
                {
                    return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
                }
    
                // additional check for strings so they're not empty
                string val = value as string;
                if (val != null && val.Trim().Length == 0)
                {
                    return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
                }
            }
    
            return ValidationResult.Success;
        }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            context.Attributes.Add("data-val", "true");
            context.Attributes.Add("data-val-required-if", ErrorMessage);
            context.Attributes.Add("data-val-required-if-other-name", OtherProperty);
            context.Attributes.Add("data-val-required-if-other-value", OtherPropertyValue.ToString());
        }
    }
    

    这里是客户端 required-if.js

        $.validator.addMethod('required-if', function (value, element, params) {
        var targetValue = params['other-value'];
        var otherElement = $("#" + params['other-name']);
        var otherCurrentValue = otherElement.val();
        if (targetValue != otherCurrentValue) {
            return true;
        }
        if (value.length === 0) {
            return false;
        } else {
            return true;
        }
    });
    
    $.validator.unobtrusive.adapters.add('required-if', ['other-name', 'other-value'], function (options) {
        options.rules['required-if'] = [options.element, options.params];
        options.messages['required-if'] = options.message;
        options.messages['required'] = options.message;
    });
    

    这是用法

    [RequiredIf("OtherPropertyName", otherPropertyValue: true, AllowEmptyStrings = false, ErrorMessage = "This field is required")]
        public string RequiredPropertyTest { get; set; }
    

    现在,当 OtherPropertyName 的值为 true 并且 RequiredPropertyTest 为空时,将弹出验证错误

    【讨论】:

      【解决方案4】:

      添加到 Sahib 的答案,这对我来说真的很有效,你显然需要添加:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-03-15
        • 1970-01-01
        • 2023-04-08
        • 1970-01-01
        • 1970-01-01
        • 2013-10-09
        • 1970-01-01
        相关资源
        最近更新 更多