【问题标题】:RequiredIf Conditional Validation AttributeRequiredIf 条件验证属性
【发布时间】:2011-11-15 11:53:28
【问题描述】:

我正在寻找有关实现执行以下操作的验证属性的最佳方法的一些建议。

型号

public class MyInputModel 
{
    [Required]
    public int Id {get;set;}

    public string MyProperty1 {get;set;}
    public string MyProperty2 {get;set;}
    public bool MyProperty3 {get;set;}

}

我希望至少 prop1 prop2 prop3 有一个值,如果 prop3 是唯一填充它的值,它不应该等于 false。 我将如何为此编写验证属性?

感谢您的帮助!

【问题讨论】:

  • 您可以查看following blog post 以获取[RequiredIf] 自定义验证属性的示例实现。它与单个其他属性值进行比较,但您可以轻松调整 IsValid 方法以满足您的要求。

标签: asp.net-mvc-3 validationattribute


【解决方案1】:

我昨天遇到了同样的问题,但我以一种非常干净的方式完成了它,它适用于客户端和服务器端验证。

条件:根据模型中其他属性的值,您要使其他属性成为必需。代码如下:

public class RequiredIfAttribute : RequiredAttribute
{
  private String PropertyName { get; set; }
  private Object DesiredValue { get; set; }

  public RequiredIfAttribute(String propertyName, Object desiredvalue)
  {
    PropertyName = propertyName;
    DesiredValue = desiredvalue;
  }

  protected override ValidationResult IsValid(object value, ValidationContext context)
  {
    Object instance = context.ObjectInstance;
    Type type = instance.GetType();
    Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
    if (proprtyvalue.ToString() == DesiredValue.ToString())
    {
      ValidationResult result = base.IsValid(value, context);
      return result;
    }
    return ValidationResult.Success;
  }
}

PropertyName 是您要设置条件的属性
DesiredValue 是您的 PropertyName(属性)的特定值必须验证其他属性是否需要

假设你有以下几点:

public enum UserType
{
  Admin,
  Regular
}

public class User
{
  public UserType UserType {get;set;}

  [RequiredIf("UserType",UserType.Admin,
              ErrorMessageResourceName="PasswordRequired", 
              ErrorMessageResourceType = typeof(ResourceString))]
  public string Password { get; set; }
}

最后但并非最不重要的一点是,为您的属性注册适配器,以便它可以进行客户端验证(我把它放在 global.asax,Application_Start 中)

DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute),
                                                      typeof(RequiredAttributeAdapter));

已编辑

有些人抱怨客户端无论如何都会触发或不起作用。所以我修改了上面的代码,也用 Javascript 做有条件的客户端验证。对于这种情况,您不需要注册适配器

 public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
    {
        private String PropertyName { get; set; }
        private Object DesiredValue { get; set; }
        private readonly RequiredAttribute _innerAttribute;

        public RequiredIfAttribute(String propertyName, Object desiredvalue)
        {
            PropertyName = propertyName;
            DesiredValue = desiredvalue;
            _innerAttribute = new RequiredAttribute();
        }

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

            if (dependentValue.ToString() == DesiredValue.ToString())
            {
                if (!_innerAttribute.IsValid(value))
                {
                    return new ValidationResult(FormatErrorMessage(context.DisplayName), new[] { context.MemberName });
                }
            }
            return ValidationResult.Success;
        }

        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            var rule = new ModelClientValidationRule
            {
                ErrorMessage = ErrorMessageString,
                ValidationType = "requiredif",
            };
            rule.ValidationParameters["dependentproperty"] = (context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(PropertyName);
            rule.ValidationParameters["desiredvalue"] = DesiredValue is bool ? DesiredValue.ToString().ToLower() : DesiredValue;

            yield return rule;
        }
    }

最后是 javascript(捆绑并渲染...放入自己的脚本文件中)

$.validator.unobtrusive.adapters.add('requiredif', ['dependentproperty', 'desiredvalue'], function (options) {
    options.rules['requiredif'] = options.params;
    options.messages['requiredif'] = options.message;
});

$.validator.addMethod('requiredif', function (value, element, parameters) {
    var desiredvalue = parameters.desiredvalue;
    desiredvalue = (desiredvalue == null ? '' : desiredvalue).toString();
    var controlType = $("input[id$='" + parameters.dependentproperty + "']").attr("type");
    var actualvalue = {}
    if (controlType == "checkbox" || controlType == "radio") {
        var control = $("input[id$='" + parameters.dependentproperty + "']:checked");
        actualvalue = control.val();
    } else {
        actualvalue = $("#" + parameters.dependentproperty).val();
    }
    if ($.trim(desiredvalue).toLowerCase() === $.trim(actualvalue).toLocaleLowerCase()) {
        var isValid = $.validator.methods.required.call(this, value, element, parameters);
        return isValid;
    }
    return true;
});

您显然需要将不显眼的验证 jQuery 作为要求包含在内

【讨论】:

  • @Dan Hunex :在 MVC4 中,我无法在客户端正常工作,无论 DesiredValue 是什么,它都会启动验证。有什么帮助吗?
  • 我可以看看你创建的属性
  • 与@H.Johnson 相同的问题。无论如何,客户端验证都会触发。有什么办法解决这个问题?
  • 我遇到了同样的问题。无论如何它都会触发。
  • 对于那些发现客户端无法正常工作的人,我添加了一个带有 javascript 的更新版本
【解决方案2】:

扩展了 Adel Mourad 和 Dan Hunex 的注释,我修改了代码以提供一个示例,该示例仅接受 与给定值匹配的值。

我还发现我不需要 JavaScript。

我在我的 Models 文件夹中添加了以下类:

public class RequiredIfNotAttribute : ValidationAttribute, IClientValidatable
{
    private String PropertyName { get; set; }
    private Object InvalidValue { get; set; }
    private readonly RequiredAttribute _innerAttribute;

    public RequiredIfNotAttribute(String propertyName, Object invalidValue)
    {
        PropertyName = propertyName;
        InvalidValue = invalidValue;
        _innerAttribute = new RequiredAttribute();
    }

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

        if (dependentValue.ToString() != InvalidValue.ToString())
        {
            if (!_innerAttribute.IsValid(value))
            {
                return new ValidationResult(FormatErrorMessage(context.DisplayName), new[] { context.MemberName });
            }
        }
        return ValidationResult.Success;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = ErrorMessageString,
            ValidationType = "requiredifnot",
        };
        rule.ValidationParameters["dependentproperty"] = (context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(PropertyName);
        rule.ValidationParameters["invalidvalue"] = InvalidValue is bool ? InvalidValue.ToString().ToLower() : InvalidValue;

        yield return rule;
    }

我不需要对视图进行任何更改,但确实对模型的属性进行了更改:

    [RequiredIfNot("Id", 0, ErrorMessage = "Please select a Source")]
    public string TemplateGTSource { get; set; }

    public string TemplateGTMedium
    {
        get
        {
            return "Email";
        }
    }

    [RequiredIfNot("Id", 0, ErrorMessage = "Please enter a Campaign")]
    public string TemplateGTCampaign { get; set; }

    [RequiredIfNot("Id", 0, ErrorMessage = "Please enter a Term")]
    public string TemplateGTTerm { get; set; }

希望这会有所帮助!

【讨论】:

    【解决方案3】:

    我让它在 ASP.NET MVC 5 上工作

    我看到很多人对此代码感兴趣并为此感到痛苦,我第一次知道它确实令人困惑和破坏。

    注意事项

    • 在服务器端和客户端都使用 MVC 5 :D
    • 我根本没有安装“ExpressiveAnnotations”库。
    • 我正在了解“@Dan Hunex”的原始代码,请在上方找到他

    解决此错误的提示

    "System.Web.Mvc.RequiredAttributeAdapter 类型必须有一个公共构造函数,它接受 System.Web.Mvc.ModelMetadata、System.Web.Mvc.ControllerContext 和 ExpressiveAnnotations.Attributes.RequiredIfAttribute 类型三个参数参数名称:adapterType "

    提示 #1: 确保您继承自“ValidationAttribute”而不是“RequiredAttribute

     public class RequiredIfAttribute : ValidationAttribute, IClientValidatable { ...}
    

    提示 #2: 或者从 'Global.asax' 中删除这一整行,在较新版本的代码中根本不需要它(在由@Dan_Hunex),是的,这行在旧版本中是必须的.​​..

    DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute), typeof(RequiredAttributeAdapter));
    

    使 Javascript 代码部分工作的提示

    1- 将代码放入一个新的 js 文件中(例如:requiredIfValidator.js)

    2- 扭曲 $(document).ready(function(){........}); 中的代码;

    3- 在包含 JQuery 验证库之后包含我们的 js 文件,所以现在看起来像这样:

    @Scripts.Render("~/bundles/jqueryval")
    <script src="~/Content/JS/requiredIfValidator.js"></script>
    

    4- 编辑 C# 代码

    来自

    rule.ValidationParameters["dependentproperty"] = (context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(PropertyName);
    

    rule.ValidationParameters["dependentproperty"] = PropertyName;
    

    if (dependentValue.ToString() == DesiredValue.ToString())
    

    if (dependentValue != null && dependentValue.ToString() == DesiredValue.ToString())
    

    我的整个代码启动并运行

    Global.asax

    这里没什么好补充的,保持干净

    requiredIfValidator.js

    在 ~/content 或 ~/scripts 文件夹中创建此文件

        $.validator.unobtrusive.adapters.add('requiredif', ['dependentproperty', 'desiredvalue'], function (options)
    {
        options.rules['requiredif'] = options.params;
        options.messages['requiredif'] = options.message;
    });
    
    
    $(document).ready(function ()
    {
    
        $.validator.addMethod('requiredif', function (value, element, parameters) {
            var desiredvalue = parameters.desiredvalue;
            desiredvalue = (desiredvalue == null ? '' : desiredvalue).toString();
            var controlType = $("input[id$='" + parameters.dependentproperty + "']").attr("type");
            var actualvalue = {}
            if (controlType == "checkbox" || controlType == "radio") {
                var control = $("input[id$='" + parameters.dependentproperty + "']:checked");
                actualvalue = control.val();
            } else {
                actualvalue = $("#" + parameters.dependentproperty).val();
            }
            if ($.trim(desiredvalue).toLowerCase() === $.trim(actualvalue).toLocaleLowerCase()) {
                var isValid = $.validator.methods.required.call(this, value, element, parameters);
                return isValid;
            }
            return true;
        });
    });
    

    _Layout.cshtml 或视图

    @Scripts.Render("~/bundles/jqueryval")
    <script src="~/Content/JS/requiredIfValidator.js"></script>
    

    RequiredIfAttribute.cs 类

    在项目中的某个位置创建它,例如在 ~/models/customValidation/

        using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Web.Mvc;
    
    namespace Your_Project_Name.Models.CustomValidation
    {
        public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
        {
            private String PropertyName { get; set; }
            private Object DesiredValue { get; set; }
            private readonly RequiredAttribute _innerAttribute;
    
            public RequiredIfAttribute(String propertyName, Object desiredvalue)
            {
                PropertyName = propertyName;
                DesiredValue = desiredvalue;
                _innerAttribute = new RequiredAttribute();
            }
    
            protected override ValidationResult IsValid(object value, ValidationContext context)
            {
                var dependentValue = context.ObjectInstance.GetType().GetProperty(PropertyName).GetValue(context.ObjectInstance, null);
    
                if (dependentValue != null && dependentValue.ToString() == DesiredValue.ToString())
                {
                    if (!_innerAttribute.IsValid(value))
                    {
                        return new ValidationResult(FormatErrorMessage(context.DisplayName), new[] { context.MemberName });
                    }
                }
                return ValidationResult.Success;
            }
    
            public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
            {
                var rule = new ModelClientValidationRule
                {
                    ErrorMessage = ErrorMessageString,
                    ValidationType = "requiredif",
                };
                rule.ValidationParameters["dependentproperty"] = PropertyName;
                rule.ValidationParameters["desiredvalue"] = DesiredValue is bool ? DesiredValue.ToString().ToLower() : DesiredValue;
    
                yield return rule;
            }
        }
    }
    

    模型

        using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.Web;
    using Your_Project_Name.Models.CustomValidation;
    
    namespace Your_Project_Name.Models.ViewModels
    {
    
        public class CreateOpenActivity
        {
            public Nullable<int> ORG_BY_CD { get; set; }
    
            [RequiredIf("ORG_BY_CD", "5", ErrorMessage = "Coordinator ID is required")] // This means: IF 'ORG_BY_CD' is equal 5 (for the example)  > make 'COR_CI_ID_NUM' required and apply its all validation / data annotations
            [RegularExpression("[0-9]+", ErrorMessage = "Enter Numbers Only")]
            [MaxLength(9, ErrorMessage = "Enter a valid ID Number")]
            [MinLength(9, ErrorMessage = "Enter a valid ID Number")]
            public string COR_CI_ID_NUM { get; set; }
        }
    }
    

    观点

    其实这里没什么要注意的……

        @model Your_Project_Name.Models.ViewModels.CreateOpenActivity
    @{
        ViewBag.Title = "Testing";
    }
    
    @using (Html.BeginForm()) 
    {
        @Html.AntiForgeryToken()
    
        <div class="form-horizontal">
            <h4>CreateOpenActivity</h4>
            <hr />
            @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    
            <div class="form-group">
                @Html.LabelFor(model => model.ORG_BY_CD, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.ORG_BY_CD, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.ORG_BY_CD, "", new { @class = "text-danger" })
                </div>
            </div>
    
            <div class="form-group">
                @Html.LabelFor(model => model.COR_CI_ID_NUM, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.COR_CI_ID_NUM, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.COR_CI_ID_NUM, "", new { @class = "text-danger" })
                </div>
            </div>
    
            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <input type="submit" value="Create" class="btn btn-default" />
                </div>
            </div>
        </div>
    }
    

    我稍后可能会为此上传一个项目示例...

    希望对您有所帮助

    谢谢

    【讨论】:

    • 我遇到了一个问题,客户端错误消息返回为“字段 [object Object] 无效。”如果遇到相同情况,请将ErrorMessage = ErrorMessageString, 替换为ErrorMessage = ErrorMessage ?? string.Format(ErrorMessageString, metadata.DisplayName),
    • @Adel Mourad 我必须在 ASP.NET MVC Core 3.1 中实现类似的验证。你知道如何在 asp.net MVC Core 中实现这段代码吗?
    • @Simant 您可以使用 C# 和 JS 手动打开/关闭所需的验证。对于 C# 使用 ModelState.Remove(PropName") 在调用 ModelState.IsValid 之前从某些属性中删除验证 ....
    • @AdelMourad 感谢您提供完整的解决方案。我只是有一个评论或改进:最好用 string.Equals(dependentValue.ToString(), DesiredValue.ToString(), StringComparison.OrdinalIgnoreCase) 替换dependentValue.ToString() == DesiredValue.ToString() 或添加字符串比较模式作为参数
    【解决方案4】:

    这里与其他解决方案的主要区别在于,这个在服务器端重用RequiredAttribute中的逻辑,在客户端使用required的验证方法depends属性:

    public class RequiredIf : RequiredAttribute, IClientValidatable
    {
        public string OtherProperty { get; private set; }
        public object OtherPropertyValue { get; private set; }
    
        public RequiredIf(string otherProperty, object otherPropertyValue)
        {
            OtherProperty = otherProperty;
            OtherPropertyValue = otherPropertyValue;
        }
    
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            PropertyInfo otherPropertyInfo = validationContext.ObjectType.GetProperty(OtherProperty);
            if (otherPropertyInfo == null)
            {
                return new ValidationResult($"Unknown property {OtherProperty}");
            }
    
            object otherValue = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
            if (Equals(OtherPropertyValue, otherValue)) // if other property has the configured value
                return base.IsValid(value, validationContext);
    
            return null;
        }
    
        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            var rule = new ModelClientValidationRule();
            rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
            rule.ValidationType = "requiredif"; // data-val-requiredif
            rule.ValidationParameters.Add("other", OtherProperty); // data-val-requiredif-other
            rule.ValidationParameters.Add("otherval", OtherPropertyValue); // data-val-requiredif-otherval
    
            yield return rule;
        }
    }
    
    $.validator.unobtrusive.adapters.add("requiredif", ["other", "otherval"], function (options) {
        var value = {
            depends: function () {
                var element = $(options.form).find(":input[name='" + options.params.other + "']")[0];
                return element && $(element).val() == options.params.otherval;
            }
        }
        options.rules["required"] = value;
        options.messages["required"] = options.message;
    });
    

    【讨论】:

      【解决方案5】:

      如果您尝试使用“ModelState.Remove”或“ModelState["Prop"].Errors.Clear()”,“ModelState.IsValid”仍然返回 false。

      为什么不直接从模型中删除默认的“必需”注释并在控制器“发布”操作的“ModelState.IsValid”之前进行自定义验证?像这样:

      if (!String.IsNullOrEmpty(yourClass.Property1) && String.IsNullOrEmpty(yourClass.dependantProperty))            
                  ModelState.AddModelError("dependantProperty", "It´s necessary to select some 'dependant'.");
      

      【讨论】:

      • 我发现这个解决方案最简单。以这种方式引发的错​​误甚至与内置 [Required] 属性引发的错误一起显示。谢谢!
      【解决方案6】:

      我知道这个话题是不久前被问到的,但最近我遇到了类似的问题并找到了另一个,但在我看来是一个更完整的解决方案。我决定实现一种机制,该机制提供条件属性以根据其他属性值和它们之间的关系计算验证结果,这些属性值和它们之间的关系在逻辑表达式中定义。

      使用它,您可以通过以下方式实现您询问的结果:

      [RequiredIf("MyProperty2 == null && MyProperty3 == false")]
      public string MyProperty1 { get; set; }
      
      [RequiredIf("MyProperty1 == null && MyProperty3 == false")]
      public string MyProperty2 { get; set; }
      
      [AssertThat("MyProperty1 != null || MyProperty2 != null || MyProperty3 == true")]
      public bool MyProperty3 { get; set; }
      

      有关 ExpressiveAnnotations 库 can be found here 的更多信息。它应该简化许多声明式验证案例,而无需编写额外的案例特定属性或在控制器内使用命令式验证方式。

      【讨论】:

      • 非常非常感谢这个库。在可扩展性方面,它胜过所有其他人。
      • @H.Johnson:这个异常不太可能发生。我假设您的代码中存在问题(也许您的类型不明确 - 您确定在 Global.asax 中您已为来自适当名称空间的属性注册了适配器,即 ExpressiveAnnotations.Attributes,而不是任何其他的?)。为了帮助您,我需要更多信息,以上内容较少。最后看一下github上的sample project,试着调查一下区别在哪里。
      • @H.Johnson: ad 1) NuGet 应将 MvcUnobtrusiveValidatorProvider.dll 自动添加到您的引用中。如果由于某种原因它不存在 - 手动添加参考。 ad 2) 为了使客户端验证正常工作,尽管 ExpressiveAnnotations.dll 提到了 MvcUnobtrusiveValidatorProvider.dll 也需要在那里。更重要的是 expressive.annotations.validate.js 应该包含在 jquery 验证文件下面的 bundle 中,并添加到指定页面(参见示例项目)。
      • @JaroslawWaliszko:感谢您的帮助。其实你的消息鼓励了我,我又试了一次。我不知道问题出在哪里,但在最后一次尝试中,我设法“在服务器和客户端”解决了这个问题。这真是太好了、简单、最灵活,我搜索了很多网站。非常感谢您的好榜样和帮助。我投了赞成票;)
      • @H.Johnson:没问题,我很高兴它有帮助;]
      猜你喜欢
      • 2014-06-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-13
      • 1970-01-01
      • 1970-01-01
      • 2020-10-24
      • 1970-01-01
      相关资源
      最近更新 更多