【问题标题】:Custom Validation Attribute Multiple Times on same field在同一字段上多次自定义验证属性
【发布时间】:2011-08-02 14:18:21
【问题描述】:

如何在同一字段上多次使用相同的自定义验证属性,或者只是为服务器端和客户端验证启用 AllowMultiple=true??

我有以下自定义验证属性:

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, 
        AllowMultiple = true, Inherited = true)]
public class RequiredIfAttribute : ValidationAttribute,IClientValidatable
{
    public RequiredIfAttribute(string dependentProperties, 
     string dependentValues = "",
     string requiredValue = "val")
     {
     }
}

在dependentProperties 中,我可以指定多个以逗号分隔的依赖属性,在dependentValues 中,我可以指定应处理的依赖属性验证的哪些值,最后在 requiredValue 中,我可以指定要验证的字段的预期值。

在我的模型中有两个属性 LandMark、PinCode 和我想使用的验证如下:

public string LandMark { get; set; }
[RequiredIf("LandMark","XYZ","500500")]
[RequiredIf("LandMark", "ABC", "500505")]
public string PinCode { get; set; }

这里的值只是举例,似乎我可以多次添加属性并且没有得到任何编译错误,我已经在属性中实现了 TypeID,如果我从中删除客户端验证,它在服务器端运行良好.但是当我在属性上实现 IClientValidatable 时,它​​给了我一个错误:

“不显眼的客户端验证规则中的验证类型名称必须是唯一的。”

任何帮助我该如何解决它??

【问题讨论】:

  • 你遇到了什么问题?
  • 我有确切的需要,我无法避免在同一字段上多次使用自定义属性,我的自定义属性实现了 IClientValidatable,我在属性上启用了 AllowMultiple=true.. 现在当我使用该属性时多次我得到错误 ValidationType name in unobtrusive validation must be unique....
  • 请更新您的问题,也许添加您的属性代码并解释 orhers 如何复制您的问题

标签: c# asp.net-mvc-3 jquery-validate data-annotations


【解决方案1】:

问题

验证属性有两个可以验证的环境:

  1. 服务器
  2. 客户

服务器验证 - 简单的多属性

如果你有任何属性:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class RequiredIfAttribute : ValidationAttribute

并把它放在你的类属性上,如下所示:

public class Client
{
    public short ResidesWithCd { get; set; };

    [RequiredIf(nameof(ResidesWithCd), new[] { 99 }, "Resides with other is required.")]
    public string ResidesWithOther { get; set; }
}

然后,只要 服务器 转到validate an object(例如ModelState.IsValid),它就会检查每个属性上的每个ValidationAttribute 并调用.IsValid() 以确定有效性。即使 @987654324@.@987654325@ 设置为 true,这也可以正常工作。

客户端验证 - HTML 属性瓶颈

如果您通过像这样实现IClientValidatable 来启用客户端:

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)    
{
    var modelClientValidationRule = new ModelClientValidationRule
    {
        ValidationType = "requiredif",
        ErrorMessage = ErrorMessageString
    };
    modelClientValidationRule.ValidationParameters.Add("target", prop.PropName);
    modelClientValidationRule.ValidationParameters.Add("values", prop.CompValues);

    return new List<ModelClientValidationRule> { modelClientValidationRule };
}

然后 ASP.NET 将在生成时发出以下 HTML:

(只要@987654327@& @987654328@ 已启用)

<input class="form-control" type="text" value=""
       id="Client_CommunicationModificationDescription" 
       name="Client.CommunicationModificationDescription" 
       data-val="true"
       data-val-requiredif="Communication Modification Description is required."
       data-val-requiredif-target="CommunicationModificationCd"
       data-val-requiredif-values="99" >

数据属性是我们将规则转储到客户端验证引擎的唯一工具,该引擎将通过内置或自定义的适配器搜索页面上的任何属性。一旦成为客户端规则集的一部分,它将能够使用内置或自定义的方法来确定每个已解析规则的有效性。

所以我们可以调用 jQuery Validate Unobtrusive 通过添加自定义适配器来查找和解析这些属性,这将向引擎添加验证规则:

// hook up to client side validation
$.validator.unobtrusive.adapters.add('requiredif', ['target', 'values'], function (options) {
    options.rules["requiredif"] = {
        id: '#' + options.params.target,
        values: JSON.parse(options.params.values)
    };
    options.messages['requiredif'] = options.message;
});

然后我们可以通过添加这样的自定义方法来告诉该规则如何运行并确定有效性,这将添加一种自定义方式来评估 requiredif 规则(与日期规则或正则表达式规则相反),这将依赖于我们的参数之前通过适配器加载:

// test validity
$.validator.addMethod('requiredif', function (value, element, params) {
    var targetHasCondValue = targetElHasValue(params.id, params.value);
    var requiredAndNoValue = targetHasCondValue && !value; // true -> :(
    var passesValidation = !requiredAndNoValue;            // true -> :)
    return passesValidation;
}, '');

所有操作都是这样的:

解决方案

那么,我们学到了什么?好吧,如果我们希望相同的规则在同一个元素上多次出现,适配器将不得不多次查看每个元素的确切规则集,而无法区分多个集中的每个实例。此外,ASP.NET 不会多次呈现相同的属性名称,因为它不是有效的 html。

所以,我们要么需要:

  1. 将所有客户端规则合并到一个包含所有信息的 mega 属性中
  2. 用每个实例编号重命名属性,然后找到一种方法来解析它们。

我将探索选项一(发出单个客户端属性),您可以通过以下几种方式实现:

  1. 创建一个单一的属性,该属性接受多个元素以在服务器客户端上进行验证
  2. 保留多个不同的服务器端属性,然后通过反射合并所有属性,然后再发送到客户端

在任何一种情况下,您都必须重新编写客户端逻辑(适配器/方法)以获取一组值,而不是一次获取一个值。

我们将构建/传输一个 JSON 序列化对象,如下所示:

var props = [
  {
    PropName: "RoleCd",
    CompValues: ["2","3","4","5"]
  },
  {
    PropName: "IsPatient",
    CompValues: ["true"]
  }
]

Scripts/ValidateRequiredIfAny.js

以下是我们将如何在客户端适配器/方法中处理它:

// hook up to client side validation
$.validator.unobtrusive.adapters.add("requiredifany", ["props"], function (options) {
    options.rules["requiredifany"] = { props: options.params.props };
    options.messages["requiredifany"] = options.message;
});

// test validity
$.validator.addMethod("requiredifany", function (value, element, params) {
    var reqIfProps = JSON.parse(params.props);
    var anytargetHasValue = false;

    $.each(reqIfProps, function (index, item) {
        var targetSel = "#" + buildTargetId(element, item.PropName);
        var $targetEl = $(targetSel);
        var targetHasValue = elHasValue($targetEl, item.CompValues);
       
        if (targetHasValue) {
            anytargetHasValue = true;
            return ;
        }
    });

    var valueRequired = anytargetHasValue;
    var requiredAndNoValue = valueRequired && !value; // true -> :(
    var passesValidation = !requiredAndNoValue;       // true -> :)
    return passesValidation;

}, "");

// UTILITY METHODS

function buildTargetId(currentElement, targetPropName) {
    // https://stackoverflow.com/a/39725539/1366033
    // we are only provided the name of the target property
    // we need to build it's ID in the DOM based on a couple assumptions
    // derive the stacking context and depth based on the current element's ID/name
    // append the target property's name to that context

                                                // currentElement.name i.e. Details[0].DosesRequested
    var curId = currentElement.id;              // get full id         i.e. Details_0__DosesRequested
    var context = curId.replace(/[^_]+$/, "");  // remove last prop    i.e. Details_0__
    var targetId = context + targetPropName;    // build target ID     i.e. Details_0__OrderIncrement

    // fail noisily
    if ($("#" + targetId).length === 0)
        console.error(
            "Could not find id '" + targetId +
            "' when looking for '" + targetPropName +
            "'  on originating element '" + curId + "'");

    return targetId;
}

function elHasValue($el, values) {
    var isCheckBox = $el.is(":checkbox,:radio");
    var isChecked = $el.is(":checked");
    var inputValue = $el.val();
    var valueInArray = $.inArray(String(inputValue), values) > -1;

    var hasValue = (!isCheckBox || isChecked) && valueInArray;

    return hasValue;
};

Models/RequiredIfAttribute.cs

在服务器端,我们将像平常一样验证属性,但是当我们要构建客户端属性时,我们将查找所有属性并构建一个超级属性

using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Web.Helpers;
using System.Web.Mvc;

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{

    public PropertyNameValues TargetProp { get; set; }

    public RequiredIfAttribute(string compPropName, string[] compPropValues, string msg) : base(msg)
    {
        this.TargetProp = new PropertyNameValues()
        {
            PropName = compPropName,
            CompValues = compPropValues
        };
    }
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        PropertyInfo compareProp = validationContext.ObjectType.GetProperty(TargetProp.PropName);
        var compPropVal = compareProp.GetValue(validationContext.ObjectInstance, null);
        string compPropValAsString = compPropVal?.ToString().ToLower() ?? "";
        var matches = TargetProp.CompValues.Where(v => v == compPropValAsString);
        bool needsValue = matches.Any();

        if (needsValue)
        {
            if (value == null || value.ToString() == "" || value.ToString() == "0")
            {
                return new ValidationResult(FormatErrorMessage(null));
            }
        }

        return ValidationResult.Success;
    }


    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        // at this point, who cares that we're on this particular instance - find all instances
        PropertyInfo curProp = metadata.ContainerType.GetProperty(metadata.PropertyName);
        RequiredIfAttribute[] allReqIfAttr = curProp.GetCustomAttributes<RequiredIfAttribute>().ToArray();

        // emit validation attributes from all simultaneously, otherwise each will overwrite the last
        PropertyNameValues[] allReqIfInfo = allReqIfAttr.Select(x => x.TargetProp).ToArray();
        string allReqJson = Json.Encode(allReqIfInfo);

        var modelClientValidationRule = new ModelClientValidationRule
        {
            ValidationType = "requiredifany",
            ErrorMessage = ErrorMessageString
        };

        // add name for jQuery parameters for the adapter, must be LOWERCASE!
        modelClientValidationRule.ValidationParameters.Add("props", allReqJson);

        return new List<ModelClientValidationRule> { modelClientValidationRule };
    }
}

public class PropertyNameValues
{
    public string PropName { get; set; }
    public string[] CompValues { get; set; }
}

然后我们可以通过同时应用多个属性将其绑定到我们的模型:

[RequiredIf(nameof(RelationshipCd), new[] { 1,2,3,4,5 }, "Mailing Address is required.")]
[RequiredIf(nameof(IsPatient), new[] { "true" },"Mailing Address is required.")]
public string MailingAddressLine1 { get; set; }

进一步阅读

【讨论】:

  • 首先,很好的答案!比大多数解释性强得多。您的代码似乎假定每个属性实例的错误消息必须相同。在您的特定示例中有效,但更常见的情况可能是每个实例显示不同的消息。
【解决方案2】:

终于在这里我自己找到了答案。 看下面的文章解决 http://www.codeproject.com/KB/validation/MultipleDataAnnotations.aspx

【讨论】:

    【解决方案3】:

    已接受答案 (http://www.codeproject.com/KB/validation/MultipleDataAnnotations.aspx) 中的链接有问题,其他人写了勘误表 here,我建议您先阅读。上面的答案不处理继承。 我相信这个替代解决方案有一些优势(包括支持继承),但距离完美的代码还很远 - 感谢改进。

    这个 C# 使用 Json.NETStuart Leeks HTML Attribute provider

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.Web.Mvc;
    using Newtonsoft.Json;
    
    namespace DabTrial.Infrastructure.Validation
    {
        [AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
        public abstract class MultipleValidationAttribute : ValidationAttribute, IMetadataAware
        {
            private class Validation
            {
                public ICollection<string> ErrorMessage { get; set; }
                public IDictionary<string, ICollection<object>> Attributes { get; set; }
            }
            private object _typeId = new object();
            public const string attributeName = "multipleValidations";
            public MultipleValidationAttribute()
            {
            }
            public override object TypeId
            {
                get
                {
                    return this._typeId;
                }
            }
            public void OnMetadataCreated(ModelMetadata metadata)
            {
                Dictionary<string, Validation> allMultis;
                if (metadata.AdditionalValues.ContainsKey(attributeName))
                {
                    allMultis = (Dictionary<string, Validation>)metadata.AdditionalValues[attributeName];
                }
                else
                {
                    allMultis = new Dictionary<string, Validation>();
                    metadata.AdditionalValues.Add(attributeName, allMultis);
                }
                foreach (var result in GetClientValidationRules(metadata))
                {
                    if (allMultis.ContainsKey(result.ValidationType))
                    {
                        var thisMulti = allMultis[result.ValidationType];
                        thisMulti.ErrorMessage.Add(result.ErrorMessage);
                        foreach (var attr in result.ValidationParameters)
                        {
                            thisMulti.Attributes[attr.Key].Add(attr.Value);
                        }
                    }
                    else
                    {
                        var thisMulti = new Validation
                        {
                            ErrorMessage = new List<string>(),
                            Attributes = new Dictionary<string, ICollection<object>>()
                        };
                        allMultis.Add(result.ValidationType, thisMulti);
                        thisMulti.ErrorMessage.Add(result.ErrorMessage);
                        foreach (var attr in result.ValidationParameters)
                        {
                            var newList = new List<object>();
                            newList.Add(attr.Value);
                            thisMulti.Attributes.Add(attr.Key, newList);
                        }
                    }
                }
            }
    
            public static IEnumerable<KeyValuePair<string, object>> GetAttributes(ModelMetadata metadata)
            {
                if (!metadata.AdditionalValues.ContainsKey(attributeName))
                {
                    return null;
                }
                var returnVar = new List<KeyValuePair<string, object>>();
                returnVar.Add(new KeyValuePair<string,object>("data-val", true));
                var allMultis = (Dictionary<string, Validation>)metadata.AdditionalValues[attributeName];
                foreach (var multi in allMultis)
                {
                    string valName = "data-val-" + multi.Key;
                    returnVar.Add(new KeyValuePair<string,object>(valName, JsonConvert.SerializeObject(multi.Value.ErrorMessage)));
                    returnVar.AddRange(multi.Value.Attributes.Select(a=>new KeyValuePair<string,object>(valName + '-' + a.Key, JsonConvert.SerializeObject(a.Value))));
                }
                return returnVar;
            }
            public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata,
                                                                                   ControllerContext context)
            {
                throw new NotImplementedException("This function must be overriden");
            }
            public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata)
            {
                return GetClientValidationRules(metadata, null);
            }
        }
    }
    

    Global.asax 包含代码

    HtmlAttributeProvider.Register((metadata) =>
    {
        return MultipleValidationAttribute.GetAttributes(metadata);
    });
    

    和 JavaScript(在自定义验证器函数中)

    function setMultiValidationValues(options, ruleName, values) {
        var i = 0, thisRule;
        for (; i < values.length; i++) {
            thisRule = (i == 0) ? ruleName : ruleName + i;
            options.messages[thisRule] = values[i].message;
            delete values[i].message;
            options.rules[thisRule] = values[i];
            if (ruleName !== thisRule) {
                (function addValidatorMethod() {
                    var counter = 0;
                    if (!$.validator.methods[ruleName]) {
                        if (++counter > 10) { throw new ReferenceError(ruleName + " is not defined"); }
                        setTimeout(addValidatorMethod, 100);
                        return;
                    }
                    if (!$.validator.methods[thisRule]) { $.validator.addMethod(thisRule, $.validator.methods[ruleName]); }
                })();
            }
        }
    }
    function transformValidationValues(options) {
        var rules = $.parseJSON(options.message),
            propNames = [], p, utilObj,i = 0,j, returnVar=[];
        for (p in options.params) {
            if (options.params.hasOwnProperty(p)) {
                utilObj = {};
                utilObj.key = p;
                utilObj.vals = $.parseJSON(options.params[p]);
                propNames.push(utilObj);
            }
        }
        for (; i < rules.length; i++) {
            utilObj = {};
            utilObj.message = rules[i];
            for (j=0; j < propNames.length; j++) {
                utilObj[propNames[j].key] = propNames[j].vals[i];
            }
            returnVar.push(utilObj);
        }
        return returnVar;
    }
    

    它的使用示例如下: C#

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Text.RegularExpressions;
    using System.Web.Mvc;
    
    namespace DabTrial.Infrastructure.Validation
    {
        [AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
        public class RegexCountAttribute : MultipleValidationAttribute
        {
            # region members
            private string _defaultErrorMessageFormatString;
            protected readonly string _regexStr;
            protected readonly RegexOptions _regexOpt;
            private int _minimumCount=0;
            private int _maximumCount=int.MaxValue;
            #endregion
            #region properties
            public int MinimumCount 
            {
                get { return _minimumCount; } 
                set 
                {
                    if (value < 0) { throw new ArgumentOutOfRangeException(); }
                    _minimumCount = value; 
                } 
            }
            public int MaximumCount
            {
                get { return _maximumCount; }
                set 
                {
                    if (value < 0) { throw new ArgumentOutOfRangeException(); }
                    _maximumCount = value; 
                }
            }
            private string DefaultErrorMessageFormatString
            {
                get
                {
                    if (_defaultErrorMessageFormatString == null)
                    {
                        _defaultErrorMessageFormatString = string.Format(
                            "{{0}} requires a {0}{1}{2} match(es) to regex {3}", 
                            MinimumCount>0?"minimum of "+ MinimumCount:"",
                            MinimumCount > 0 && MaximumCount< int.MaxValue? " and " : "",
                            MaximumCount<int.MaxValue?"maximum of "+ MaximumCount:"",
                            _regexStr);
                    }
                    return _defaultErrorMessageFormatString;
                }
                set
                {
                    _defaultErrorMessageFormatString = value;
                }
    
            }
            #endregion
            #region instantiation
            public RegexCountAttribute(string regEx, string defaultErrorMessageFormatString = null, RegexOptions regexOpt = RegexOptions.None)
            {
    #if debug
                if (minimumCount < 0) { throw new ArgumentException("the minimum value must be non-negative"); }
    #endif
                _regexStr = regEx;
                DefaultErrorMessageFormatString = defaultErrorMessageFormatString;
                _regexOpt = regexOpt;
            }
            #endregion
            #region methods
    
            protected override ValidationResult IsValid(object value,
                                                        ValidationContext validationContext)
            {
                var instr = (string)value;
                int matchCount = 0;
                if (MinimumCount > 0 && instr != null)
                {
                    Match match = new Regex(_regexStr,_regexOpt).Match(instr);
                    while (match.Success && ++matchCount < MinimumCount)
                    {
                       match = match.NextMatch();
                    }
                    if (MaximumCount != int.MaxValue)
                    {
                        while (match.Success && ++matchCount <= MaximumCount)
                        {
                            match = match.NextMatch();
                        }
                    }
                }
                if (matchCount >= MinimumCount && matchCount <=MaximumCount)
                {
                    return ValidationResult.Success;
                }
                string errorMessage = GetErrorMessage(validationContext.DisplayName);
                return new ValidationResult(errorMessage);
            }
            protected string GetErrorMessage(string displayName)
            {
                return ErrorMessage ?? string.Format(DefaultErrorMessageFormatString,
                    displayName,
                    MinimumCount);
            }
            private bool HasFlag(RegexOptions options, RegexOptions flag)
            {
                return ((options & flag) == flag);
            }
            private string RegexpModifier
            {
                get 
                {
                    string options = string.Empty;
                    if (HasFlag(_regexOpt, RegexOptions.IgnoreCase)) { options += 'i'; }
                    if (HasFlag(_regexOpt, RegexOptions.Multiline)) { options += 'm'; }
                    return options;
                }
            }
            public override IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata)
            {
                var returnVal = new ModelClientValidationRule {
                    ErrorMessage = GetErrorMessage(metadata.DisplayName),
                    ValidationType = "regexcount",
                };
                returnVal.ValidationParameters.Add("min",MinimumCount);
                returnVal.ValidationParameters.Add("max",MaximumCount);
                returnVal.ValidationParameters.Add("regex",_regexStr);
                returnVal.ValidationParameters.Add("regexopt", RegexpModifier);
                yield return returnVal;
            }
            #endregion
        }
        public class MinNonAlphanum : RegexCountAttribute
        {
            public MinNonAlphanum(int minimum) : base("[^0-9a-zA-Z]", GetDefaultErrorMessageFormatString(minimum)) 
            {
                this.MinimumCount = minimum;
            }
            private static string GetDefaultErrorMessageFormatString(int min)
            {
                if (min == 1)
                {
                    return "{0} requires a minimum of {1} character NOT be a letter OR number";
                }
                return "{0} requires a minimum of {1} characters NOT be a letter OR number";
            }
        }
        public class MinDigits : RegexCountAttribute
        {
            public MinDigits(int minimum) : base(@"\d", GetDefaultErrorMessageFormatString(minimum)) 
            {
                this.MinimumCount = minimum;
            }
            private static string GetDefaultErrorMessageFormatString(int min)
            {
                if (min == 1)
                {
                    return "{0} requires a minimum of {1} character is a number";
                }
                return "{0} requires a minimum of {1} characters are numbers";
            }
        }
    }
    

    JavaScript:

    $.validator.addMethod("regexcount", function (value, element, params) {
        var matches = (value.match(params.regex)||[]).length
        return  matches >= params.min && matches <= params.max;
    });
    $.validator.unobtrusive.adapters.add("regexcount", ["min", "max", "regex", "regexopt"], function (options) {
        var args = transformValidationValues(options), i=0;
        for (; i < args.length; i++) {
            args[i].regex = new RegExp(args[i].regex, args[i].regexopt);
            delete args[i].regexopt;
        }
        setMultiValidationValues(options, "regexcount", args);
    });
    

    【讨论】:

      猜你喜欢
      • 2016-12-10
      • 1970-01-01
      • 2013-01-17
      • 2019-07-10
      • 2019-09-06
      • 1970-01-01
      • 2017-06-21
      • 2012-11-30
      • 1970-01-01
      相关资源
      最近更新 更多