繁体   English   中英

自定义验证属性在同一字段上多次

[英]Custom Validation Attribute Multiple Times on same field

如何在同一字段上多次使用相同的自定义验证属性,或者只是为服务器端和客户端验证启用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时,它给了我一个错误:

“不引人注目的客户端验证规则中的验证类型名称必须是唯一的。”

任何帮助我该如何解决?

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

接受的答案中的链接( http://www.codeproject.com/KB/validation/MultipleDataAnnotations.aspx )是错误的,其他人在这里写了一个勘误表,我建议先阅读。 上面的答案不处理继承。 我相信这种替代解决方案具有一些优势(包括继承的支持),但仍然远非完美的代码 - 改进升值。

这个C#使用Json.NETStuart Leeks HTML属性提供程序

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);
});

问题

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

  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; }
}

然后随时服务器验证对象 (例如: ModelState.IsValid ,它会检查每个ValidationAttribute每个属性并调用.IsValid()来确定有效性。这将正常工作,即使AttributeUsage . AllowMultiple设置为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:
(只要启用了ClientValidationEnabledUnobtrusiveJavaScriptEnabled

<input class="form-control" type="text" value=""
       id="Client_CommunicationModificationDescription" 
       name="Client.CommunicationModificationDescription" 
       

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

因此,我们可以通过添加自定义适配器来查找和解析这些属性,以便为引擎添加验证规则: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;
}, '');

所有这些都是这样的:

IClientValidatable

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

所以,我们要么需要:

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

我将探索Option One (发出单个客户端属性),你可以做几个方面:

  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

在服务器端,我们将验证正常的属性,但是当我们构建客户端属性时,我们将查找所有属性并构建一个mega属性

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; }

进一步阅读

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM