C# ASP.NET MVC自定义多字段验证

C# ASP.NET MVC自定义多字段验证,c#,asp.net,asp.net-mvc,validation,data-annotations,C#,Asp.net,Asp.net Mvc,Validation,Data Annotations,我正在开发一个ASP.NET MVC 5.2.3自定义数据注释,以便在Visual Studio 2015中进行验证。它需要获取任意数量的字段,并确保如果一个字段有值,那么所有字段都必须有值;如果它们都是空的/空白的,就应该可以了 一些例子有助于: 但是,我不知道如何进行客户端验证,因为您需要验证的字段数量未知 如何使用IClientValidatable接口的GetClientValidationRules()方法将其传递给客户端 另外,如何将此新数据注释应用于视图模型上的特性?看起

我正在开发一个ASP.NET MVC 5.2.3自定义数据注释,以便在Visual Studio 2015中进行验证。它需要获取任意数量的字段,并确保如果一个字段有值,那么所有字段都必须有值;如果它们都是空的/空白的,就应该可以了

一些例子有助于:

但是,我不知道如何进行客户端验证,因为您需要验证的字段数量未知

如何使用
IClientValidatable
接口的
GetClientValidationRules()
方法将其传递给客户端

另外,如何将此新数据注释应用于视图模型上的特性?看起来像这样吗

[MultipleRequired("AppNumber", "UserId", /* more fields */), ErrorMessage = "Something..."]
[DisplayName("App #")]
public int AppNumber { get; set; }

[DisplayName("User ID")]
public int UserId { get; set; }
以下是使用
MultipleRequiredAttribute
自定义数据注释类所能得到的:

public class MultipleRequiredAttribute : ValidationAttribute, IClientValidatable
{
    private readonly string[] _fields;
    public MultipleRequiredAttribute(params string[] fields)
    {
        _fields = fields;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        // If any field has value, then all must have value
        var anyHasValue = _fields.Any(f => !string.IsNullOrEmpty(f));

        if (!anyHasValue) return null;

        foreach (var field in _fields)
        {
            var property = validationContext.ObjectType.GetProperty(field);
            if (property == null)
                return new ValidationResult($"Property '{field}' is undefined.");

            var fieldValue = property.GetValue(validationContext.ObjectInstance, null);

            if (string.IsNullOrEmpty(fieldValue?.ToString()))
                return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
        }

        return null;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        yield return new ModelClientValidationRule
        {
            ErrorMessage = ErrorMessage,
            ValidationType = "multiplerequired"
        };
    }
}
公共类多重必需属性:ValidationAttribute,IClientValidable
{
私有只读字符串[]字段;
公共MultipleRequiredAttribute(参数字符串[]字段)
{
_字段=字段;
}
受保护的重写ValidationResult有效(对象值,ValidationContext ValidationContext)
{
//如果任何字段都有值,则所有字段都必须有值
var anyHasValue=_fields.Any(f=>!string.IsNullOrEmpty(f));
如果(!anyHasValue)返回null;
foreach(变量字段在_字段中)
{
var property=validationContext.ObjectType.GetProperty(字段);
if(属性==null)
返回新的ValidationResult($“未定义属性{field}”);
var fieldValue=property.GetValue(validationContext.ObjectInstance,null);
if(string.IsNullOrEmpty(fieldValue?.ToString()))
返回新的ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
返回null;
}
公共IEnumerable GetClientValidationRules(ModelMetadata元数据、ControllerContext上下文)
{
生成返回新ModelClientValidationRule
{
ErrorMessage=ErrorMessage,
ValidationType=“需要多个”
};
}
}

谢谢。

为了获得客户端验证,您需要使用rules
ValidationParameters
属性的
.Add()
方法传递
ModelClientValidationRule
中的“其他属性”值,然后编写客户端脚本将规则添加到
$.validator

但首先,您的属性还需要解决一些其他问题。首先,仅当应用属性的属性值为
null
时,才应执行
foreach
循环。其次,如果其中一个“其他属性”不存在,则返回一个
ValidationResult
,这对用户来说是混乱和无意义的,您应该忽略它

属性代码应该是(注意我更改了属性的名称)


您可以在客户端上为jquery Validate js插件构建一个自定义函数,从读取开始。在
GetClientValidationRules()
方法中,添加一个
ModelClientValidationRule
,在这里可以传递(比如)以逗号分隔的属性名列表,即可以在客户端脚本中解析和使用的
字段
值(如果您有问题,请告诉我,我会添加一个答案,但在几个小时内没有机会)谢谢,@StephenMuecke!我的问题之一是如何将值传递给客户端。您询问状态如果有值,它们都必须有值,但您的代码未验证该值(如果是这种情况,您还需要将该属性应用于所有属性)此外,您的
返回新的ValidationResult($“属性{field}”未定义)。
实际上没有意义(在视图中显示该消息将毫无意义,并且会让用户感到困惑)-忽略它,或者检入构造函数并抛出一个异常。这是一个用于在DOM中查找关联元素的通用函数。您可能有一个包含复杂对象或集合的属性的模型,因此它可能以(比如)输入呈现(比如)
name=“Employees[0]。FirstName”id=“Employees\u 0\u FirstName”
name=“Employees[0].LastName“id=“Employees\u 0\u\u LastName”
。因此,如果提供了
FirstName
,则假定需要验证
LastName
。您可以传递
GetClientValidationRules()
method是另一个属性的名称,即
LastName
该方法首先检查DOM是否包含带有
id=“LastName”
的元素。对于简单对象,它将返回一个元素。但在这种情况下,它不会返回,因此函数的下一部分将获取当前元素的名称(即
name=“Employees[0].FirstName“
)并获取最后一个点左边的部分(
Employees[0]
),并附加到其他属性以生成
Employees[0]。LastName
。由于按
id
搜索比按
name
属性搜索快,
替换()
生成
Employees\u 0\u LastName
并搜索该元素。在大多数情况下,这会找到您想要的内容,但有些用户会覆盖
id
属性,因此不会找到任何内容,最后的检查是否基于基于
name
属性查找元素方法有一点,省略了一些与元素相关的代码,这些元素是复选框和单选按钮,需要以不同的方式处理,但我认为这不适用于您的情况“别名”只是为函数命名(它是my jquery插件的一部分,其中包括许多不由内置验证属性处理的验证函数),只是防止与插件发生任何可能的(尽管不太可能)冲突
public class RequiredIfAnyAttribute : ValidationAttribute, IClientValidatable
{
    private readonly string[] _otherProperties;
    private const string _DefaultErrorMessage = "The {0} field is required";

    public RequiredIfAnyAttribute(params string[] otherProperties)
    {
        if (otherProperties.Length == 0) // would not make sense
        {
            throw new ArgumentException("At least one other property name must be provided");
        }
        _otherProperties = otherProperties;
        ErrorMessage = _DefaultErrorMessage;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value == null) // no point checking if it has a value
        {
            foreach (string property in _otherProperties)
            {
                var propertyName = validationContext.ObjectType.GetProperty(property);
                if (propertyName == null)
                {
                    continue;
                }
                var propertyValue = propertyName.GetValue(validationContext.ObjectInstance, null);
                if (propertyValue != null)
                {
                    return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
                }
            }
        }
        return ValidationResult.Success;
    }
    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
        {
            ValidationType = "requiredifany",
            ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
        };
        / pass a comma separated list of the other propeties
        rule.ValidationParameters.Add("otherproperties", string.Join(",", _otherProperties));
        yield return rule;
    }
}
sandtrapValidation = {
    getDependentElement: function (validationElement, dependentProperty) {
        var dependentElement = $('#' + dependentProperty);
        if (dependentElement.length === 1) {
            return dependentElement;
        }
        var name = validationElement.name;
        var index = name.lastIndexOf(".") + 1;
        var id = (name.substr(0, index) + dependentProperty).replace(/[\.\[\]]/g, "_");
        dependentElement = $('#' + id);
        if (dependentElement.length === 1) {
            return dependentElement;
        }
        // Try using the name attribute
        name = (name.substr(0, index) + dependentProperty);
        dependentElement = $('[name="' + name + '"]');
        if (dependentElement.length > 0) {
            return dependentElement.first();
        }
        return null;
    }
}

$.validator.unobtrusive.adapters.add("requiredifany", ["otherproperties"], function (options) {
    var element = options.element;
    var otherNames = options.params.otherproperties.split(',');
    var otherProperties = [];
    $.each(otherNames, function (index, item) {
        otherProperties.push(sandtrapValidation.getDependentElement(element, item))
    });
    options.rules['requiredifany'] = {
        otherproperties: otherProperties
    };
    options.messages['requiredifany'] = options.message;
});

$.validator.addMethod("requiredifany", function (value, element, params) {
    if ($(element).val() != '') {
        // The element has a value so its OK
        return true;
    }
    var isValid = true;
    $.each(params.otherproperties, function (index, item) {
        if ($(this).val() != '') {
            isValid = false;
        }
    });
    return isValid;
});