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

发布于 2024-11-27 09:46:07 字数 1004 浏览 3 评论 0原文

如何在同一字段上多次使用相同的自定义验证属性,或者简单地启用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 时,它​​给出了一个错误:

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

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

How can I use Same Custom Validation Attribute Multiple Times on Same Field or simply enable AllowMultiple=true, for both server side and client side validation??

I have a following Custom Validation Attribute:

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

Where in dependentProperties I can specify multiple dependant properties seperated by comma, in dependentValues I can specify for which values of dependant properties validation should process and finally in requiredValue I can specify expected value for the field to be validated.

In my model there are two properties LandMark, PinCode and I want to use validation as follows:

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

The values here are just for example, as per it seems I can add the attribute multiple times and don't get any compile error, I have implemented TypeID in attribute and it works well from serverside if I remove client validation from it. But when I am implementing IClientValidatable on the attribute, it gives me an error:

"Validation type names in unobtrusive client validation rules must be unique."

Any help how can I solve it??

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

我爱人 2024-12-04 09:46:07

问题

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

  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:

(只要ClientValidationEnabled &
UnobtrusiveJavaScriptEnabled 已启用)

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

所有操作都如下所示:

<一href="https://i.sstatic.net/ctjB0.png" rel="nofollow noreferrer">IClientValidatable< /a>

解决方案

那么,我们学到了什么?好吧,如果我们希望同一规则在同一元素上多次出现,则适配器必须多次查看每个元素的确切规则集,而无法区分多个集中的每个实例。此外,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; }

进一步阅读

The Problem

Validation Attributes have two environments they can validate against:

  1. Server
  2. Client

Server Validation - Multiple Attributes Easy

If you have any attribute with:

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

And have put it on your class property like this:

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

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

Then anytime the Server goes to validate an object (ex. ModelState.IsValid), it will check every ValidationAttribute on each property and call .IsValid() to determine validity. This will work fine, even if AttributeUsage.AllowMultiple is set to true.

Client Validation - HTML Attribute Bottleneck

If you enable client side by implementing IClientValidatable like this:

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

Then ASP.NET will emit the following HTML when generated:

(As long as ClientValidationEnabled &
UnobtrusiveJavaScriptEnabled are enabled)

<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" >

Data Attributes are the only vehicle we have for dumping rules into the client side validation engine which will search for any attributes on the page via a built in or custom adapter. And once part of the set of client side rules, it'll be able to determine the validity of each parsed rule with a built in or custom method.

So we can call jQuery Validate Unobtrusive to look for and parse these attributes by adding a custom adapter which will add a validation rule to the engine:

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

We can then tell that rule how function and determine validity by adding a custom method like this which will add a custom way to evaluate requiredif rules (as opposed to date rules or regex rules) which will rely on the parameters we loaded earlier through the adapter:

// 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;
}, '');

Which all operates like this:

IClientValidatable

Solution

So, what have we learned? Well, if we want the same rule to appear multiple times on the same element, the adapter would have to see the exact set of rules multiple times per element, with no way to differentiate between each instance within multiple sets. Further, ASP.NET won't render the same attribute name multiple times since it's not valid html.

So, we either need to:

  1. Collapse all the client side rules into a single mega attribute with all the info
  2. Rename attributes with each instance number and then find a way to parse them in sets.

I'll explore Option One (emitting a single client side attribute), which you could do a couple ways:

  1. Create a single Attribute that takes in multiple elements to validate on the server client
  2. Keep multiple distinct server side attributes and then merge all attributes via reflection before emitting to the client

In either case you will have to re-write the client side logic (adapter/method) to take an array of values, instead of a single value at a time.

To we'll build/transmit a JSON serialized object that looks like this:

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

Scripts/ValidateRequiredIfAny.js

Here's how we'll handle that in client side adapter / method:

// 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

On the server side, we'll validate attributes like normal, but when we got to build the client side attributes, we'll look for all attributes and build one mega attribute

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

Then we can bind that to our model by applying multiple attributes simultaneously:

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

Further Reading

黄昏下泛黄的笔记 2024-12-04 09:46:07

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

Finally here I found the answer my-self.
Look at following article for solution
http://www.codeproject.com/KB/validation/MultipleDataAnnotations.aspx

漫漫岁月 2024-12-04 09:46:07

接受的答案中的链接(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);
});

The link in the accepted answer (http://www.codeproject.com/KB/validation/MultipleDataAnnotations.aspx) is buggy, and someone else has written an errata here which I would recommend reading first. The answer above does not handle inheritance.
I believe this alternate solution has some advantages (including support of inheritance), but remains far from perfect code - improvements appreciated.

this C# uses Json.NET and Stuart 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);
        }
    }
}

the Global.asax contains the code

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

and the JavaScript (within a custom validators function)

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

An example of its use is below:
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);
});
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文