MVC3 验证 - 需要组中的一个

发布于 2024-12-02 10:00:43 字数 831 浏览 2 评论 0原文

给定以下视图模型:

public class SomeViewModel
{
  public bool IsA { get; set; }
  public bool IsB { get; set; }
  public bool IsC { get; set; } 
  //... other properties
}

我希望创建一个自定义属性来验证至少一个可用属性是否为真。我设想能够将属性附加到属性并分配组名称,如下所示:

public class SomeViewModel
{
  [RequireAtLeastOneOfGroup("Group1")]
  public bool IsA { get; set; }

  [RequireAtLeastOneOfGroup("Group1")]
  public bool IsB { get; set; }

  [RequireAtLeastOneOfGroup("Group1")]
  public bool IsC { get; set; } 

  //... other properties

  [RequireAtLeastOneOfGroup("Group2")]
  public bool IsY { get; set; }

  [RequireAtLeastOneOfGroup("Group2")]
  public bool IsZ { get; set; }
}

我想在表单提交之前在客户端进行验证,因为表单中的值会发生变化,这就是为什么我更喜欢避免类级别如果可能的话属性。

这将需要服务器端和客户端验证来查找具有作为自定义属性的参数传入的相同组名称值的所有属性。这可能吗?非常感谢任何指导。

Given the following viewmodel:

public class SomeViewModel
{
  public bool IsA { get; set; }
  public bool IsB { get; set; }
  public bool IsC { get; set; } 
  //... other properties
}

I wish to create a custom attribute that validates at least one of the available properties are true. I envision being able to attach an attribute to a property and assign a group name like so:

public class SomeViewModel
{
  [RequireAtLeastOneOfGroup("Group1")]
  public bool IsA { get; set; }

  [RequireAtLeastOneOfGroup("Group1")]
  public bool IsB { get; set; }

  [RequireAtLeastOneOfGroup("Group1")]
  public bool IsC { get; set; } 

  //... other properties

  [RequireAtLeastOneOfGroup("Group2")]
  public bool IsY { get; set; }

  [RequireAtLeastOneOfGroup("Group2")]
  public bool IsZ { get; set; }
}

I would like to validate on the client-side prior to form submission as values in the form change which is why I prefer to avoid a class-level attribute if possible.

This would require both the server-side and client-side validation to locate all properties having identical group name values passed in as the parameter for the custom attribute. Is this possible? Any guidance is much appreciated.

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

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

发布评论

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

评论(4

━╋う一瞬間旳綻放 2024-12-09 10:00:43

这是一种继续进行的方法(还有其他方法,我只是举例说明一种与您的视图模型相匹配的方法):

[AttributeUsage(AttributeTargets.Property)]
public class RequireAtLeastOneOfGroupAttribute: ValidationAttribute, IClientValidatable
{
    public RequireAtLeastOneOfGroupAttribute(string groupName)
    {
        ErrorMessage = string.Format("You must select at least one value from group \"{0}\"", groupName);
        GroupName = groupName;
    }

    public string GroupName { get; private set; }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        foreach (var property in GetGroupProperties(validationContext.ObjectType))
        {
            var propertyValue = (bool)property.GetValue(validationContext.ObjectInstance, null);
            if (propertyValue)
            {
                // at least one property is true in this group => the model is valid
                return null;
            }
        }
        return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
    }

    private IEnumerable<PropertyInfo> GetGroupProperties(Type type)
    {
        return
            from property in type.GetProperties()
            where property.PropertyType == typeof(bool)
            let attributes = property.GetCustomAttributes(typeof(RequireAtLeastOneOfGroupAttribute), false).OfType<RequireAtLeastOneOfGroupAttribute>()
            where attributes.Count() > 0
            from attribute in attributes
            where attribute.GroupName == GroupName
            select property;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var groupProperties = GetGroupProperties(metadata.ContainerType).Select(p => p.Name);
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = this.ErrorMessage
        };
        rule.ValidationType = string.Format("group", GroupName.ToLower());
        rule.ValidationParameters["propertynames"] = string.Join(",", groupProperties);
        yield return rule;
    }
}

现在,让我们定义一个控制器:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new SomeViewModel();
        return View(model);        
    }

    [HttpPost]
    public ActionResult Index(SomeViewModel model)
    {
        return View(model);
    }
}

和一个视图:

@model SomeViewModel

<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>

@using (Html.BeginForm())
{
    @Html.EditorFor(x => x.IsA)
    @Html.ValidationMessageFor(x => x.IsA)
    <br/>
    @Html.EditorFor(x => x.IsB)<br/>
    @Html.EditorFor(x => x.IsC)<br/>

    @Html.EditorFor(x => x.IsY)
    @Html.ValidationMessageFor(x => x.IsY)
    <br/>
    @Html.EditorFor(x => x.IsZ)<br/>
    <input type="submit" value="OK" />
}

剩下的最后一部分是为客户端注册适配器侧面验证:

jQuery.validator.unobtrusive.adapters.add(
    'group', 
    [ 'propertynames' ],
    function (options) {
        options.rules['group'] = options.params;
        options.messages['group'] = options.message;
    }
);

jQuery.validator.addMethod('group', function (value, element, params) {
    var properties = params.propertynames.split(',');
    var isValid = false;
    for (var i = 0; i < properties.length; i++) {
        var property = properties[i];
        if ($('#' + property).is(':checked')) {
            isValid = true;
            break;
        }
    }
    return isValid;
}, '');

根据您的具体要求,可能会调整代码。

Here's one way to proceed (there are other ways, I am just illustrating one that would match your view model as is):

[AttributeUsage(AttributeTargets.Property)]
public class RequireAtLeastOneOfGroupAttribute: ValidationAttribute, IClientValidatable
{
    public RequireAtLeastOneOfGroupAttribute(string groupName)
    {
        ErrorMessage = string.Format("You must select at least one value from group \"{0}\"", groupName);
        GroupName = groupName;
    }

    public string GroupName { get; private set; }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        foreach (var property in GetGroupProperties(validationContext.ObjectType))
        {
            var propertyValue = (bool)property.GetValue(validationContext.ObjectInstance, null);
            if (propertyValue)
            {
                // at least one property is true in this group => the model is valid
                return null;
            }
        }
        return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
    }

    private IEnumerable<PropertyInfo> GetGroupProperties(Type type)
    {
        return
            from property in type.GetProperties()
            where property.PropertyType == typeof(bool)
            let attributes = property.GetCustomAttributes(typeof(RequireAtLeastOneOfGroupAttribute), false).OfType<RequireAtLeastOneOfGroupAttribute>()
            where attributes.Count() > 0
            from attribute in attributes
            where attribute.GroupName == GroupName
            select property;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var groupProperties = GetGroupProperties(metadata.ContainerType).Select(p => p.Name);
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = this.ErrorMessage
        };
        rule.ValidationType = string.Format("group", GroupName.ToLower());
        rule.ValidationParameters["propertynames"] = string.Join(",", groupProperties);
        yield return rule;
    }
}

Now, let's define a controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new SomeViewModel();
        return View(model);        
    }

    [HttpPost]
    public ActionResult Index(SomeViewModel model)
    {
        return View(model);
    }
}

and a view:

@model SomeViewModel

<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>

@using (Html.BeginForm())
{
    @Html.EditorFor(x => x.IsA)
    @Html.ValidationMessageFor(x => x.IsA)
    <br/>
    @Html.EditorFor(x => x.IsB)<br/>
    @Html.EditorFor(x => x.IsC)<br/>

    @Html.EditorFor(x => x.IsY)
    @Html.ValidationMessageFor(x => x.IsY)
    <br/>
    @Html.EditorFor(x => x.IsZ)<br/>
    <input type="submit" value="OK" />
}

The last part that's left would be to register adapters for the client side validation:

jQuery.validator.unobtrusive.adapters.add(
    'group', 
    [ 'propertynames' ],
    function (options) {
        options.rules['group'] = options.params;
        options.messages['group'] = options.message;
    }
);

jQuery.validator.addMethod('group', function (value, element, params) {
    var properties = params.propertynames.split(',');
    var isValid = false;
    for (var i = 0; i < properties.length; i++) {
        var property = properties[i];
        if ($('#' + property).is(':checked')) {
            isValid = true;
            break;
        }
    }
    return isValid;
}, '');

Based on your specific requirements the code might be adapted.

浊酒尽余欢 2024-12-09 10:00:43

使用 jquery-validation 团队的 require_from_group

jQuery-validation 项目在 src 文件夹中有一个名为 additional 的子文件夹>。
您可以在此处查看。

在该文件夹中,我们有许多不常见的附加验证方法,这就是默认情况下不添加它们的原因。

正如您在该文件夹中看到的,它存在如此多的方法,您需要通过选择您实际需要的验证方法来选择。

根据您的问题,您需要的验证方法被命名为来自附加文件夹的 require_from_group
只需下载位于此处的关联文件并将其放入您的 Scripts 应用程序文件夹中。

该方法的文档对此进行了解释:

让您说“必须填充至少 X 个与选择器 Y 匹配的输入。”

最终结果是这些输入都不是:

...除非至少其中之一已填写,否则将验证。

零件编号:{require_from_group: [1,".productinfo"]},
描述:{require_from_group: [1,".productinfo"]}

选项[0]:组中必须填写的字段数
options2:定义组的 CSS 选择器有条件必填字段

为什么需要选择此实现:

此验证方法是通用的,适用于每个输入(文本、复选框、单选框等)、文本区域和选择此方法还允许您指定需要填充的最小输入数量,例如

partnumber:     {require_from_group: [2,".productinfo"]},
category:       {require_from_group: [2,".productinfo"]},
description:    {require_from_group: [2,".productinfo"]}

我创建了两个类 RequireFromGroupAttributeRequireFromGroupFieldAttribute 这将帮助您在服务器端和客户端验证上

RequireFromGroupAttribute 类定义

RequireFromGroupAttribute 仅派生自 Attribute。该类仅用于配置,例如设置验证需要填写的字段数量。您需要向此类提供 CSS 选择器类,验证方法将使用该类来获取同一组中的所有元素。由于必填字段的默认数量为 1,因此,如果指定组中的最低要求大于默认数量,则此属性仅用于装饰您的模型。

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class RequireFromGroupAttribute : Attribute
{
    public const short DefaultNumber = 1;

    public string Selector { get; set; }

    public short Number { get; set; }

    public RequireFromGroupAttribute(string selector)
    {
        this.Selector = selector;
        this.Number = DefaultNumber;
    }

    public static short GetNumberOfRequiredFields(Type type, string selector)
    {
        var requiredFromGroupAttribute = type.GetCustomAttributes<RequireFromGroupAttribute>().SingleOrDefault(a => a.Selector == selector);
        return requiredFromGroupAttribute?.Number ?? DefaultNumber;
    }
}

RequireFromGroupFieldAttribute 类定义

RequireFromGroupFieldAttribute 派生自 ValidationAttribute 并实现 IClientValidatable。您需要在模型中参与组验证的每个属性上使用此类。您必须传递 css 选择器类。

[AttributeUsage(AttributeTargets.Property)]
public class RequireFromGroupFieldAttribute : ValidationAttribute, IClientValidatable
{
    public string Selector { get; }

    public bool IncludeOthersFieldName { get; set; }

    public RequireFromGroupFieldAttribute(string selector)
        : base("Please fill at least {0} of these fields")
    {
        this.Selector = selector;
        this.IncludeOthersFieldName = true;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var properties = this.GetInvolvedProperties(validationContext.ObjectType); ;
        var numberOfRequiredFields = RequireFromGroupAttribute.GetNumberOfRequiredFields(validationContext.ObjectType, this.Selector);

        var values = new List<object> { value };
        var otherPropertiesValues = properties.Where(p => p.Key.Name != validationContext.MemberName)
                                              .Select(p => p.Key.GetValue(validationContext.ObjectInstance));
        values.AddRange(otherPropertiesValues);

        if (values.Count(s => !string.IsNullOrWhiteSpace(Convert.ToString(s))) >= numberOfRequiredFields)
        {
            return ValidationResult.Success;
        }

        return new ValidationResult(this.GetErrorMessage(numberOfRequiredFields, properties.Values), new List<string> { validationContext.MemberName });
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var properties = this.GetInvolvedProperties(metadata.ContainerType);
        var numberOfRequiredFields = RequireFromGroupAttribute.GetNumberOfRequiredFields(metadata.ContainerType, this.Selector);
        var rule = new ModelClientValidationRule
        {
            ValidationType = "requirefromgroup",
            ErrorMessage = this.GetErrorMessage(numberOfRequiredFields, properties.Values)
        };
        rule.ValidationParameters.Add("number", numberOfRequiredFields);
        rule.ValidationParameters.Add("selector", this.Selector);

        yield return rule;
    }

    private Dictionary<PropertyInfo, string> GetInvolvedProperties(Type type)
    {
        return type.GetProperties()
                   .Where(p => p.IsDefined(typeof(RequireFromGroupFieldAttribute)) &&
                               p.GetCustomAttribute<RequireFromGroupFieldAttribute>().Selector == this.Selector)
                   .ToDictionary(p => p, p => p.IsDefined(typeof(DisplayAttribute)) ? p.GetCustomAttribute<DisplayAttribute>().Name : p.Name);
    }

    private string GetErrorMessage(int numberOfRequiredFields, IEnumerable<string> properties)
    {
        var errorMessage = string.Format(this.ErrorMessageString, numberOfRequiredFields);
        if (this.IncludeOthersFieldName)
        {
            errorMessage += ": " + string.Join(", ", properties);
        }

        return errorMessage;
    }
}

如何在视图模型中使用它?

在您的模型中,以下是如何使用它:

public class SomeViewModel
{
    internal const string GroupOne = "Group1";
    internal const string GroupTwo = "Group2";

    [RequireFromGroupField(GroupOne)]
    public bool IsA { get; set; }

    [RequireFromGroupField(GroupOne)]
    public bool IsB { get; set; }

    [RequireFromGroupField(GroupOne)]
    public bool IsC { get; set; }

    //... other properties

    [RequireFromGroupField(GroupTwo)]
    public bool IsY { get; set; }

    [RequireFromGroupField(GroupTwo)]
    public bool IsZ { get; set; }
}

默认情况下,您不需要使用 RequireFromGroupAttribute 装饰您的模型,因为默认的必填字段数量为 1。但是如果您想要多个必填字段与 1 不同,您可以执行以下操作:

[RequireFromGroup(GroupOne, Number = 2)]
public class SomeViewModel
{
    //...
}

如何在视图代码中使用它?

@model SomeViewModel

<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/require_from_group.js")" type="text/javascript"></script>

@using (Html.BeginForm())
{
    @Html.CheckBoxFor(x => x.IsA, new { @class="Group1"})<span>A</span>
    @Html.ValidationMessageFor(x => x.IsA)
    <br />
    @Html.CheckBoxFor(x => x.IsB, new { @class = "Group1" }) <span>B</span><br />
    @Html.CheckBoxFor(x => x.IsC, new { @class = "Group1" }) <span>C</span><br />

    @Html.CheckBoxFor(x => x.IsY, new { @class = "Group2" }) <span>Y</span>
    @Html.ValidationMessageFor(x => x.IsY)
    <br />
    @Html.CheckBoxFor(x => x.IsZ, new { @class = "Group2" })<span>Z</span><br />
    <input type="submit" value="OK" />
}

请注意,您在使用 RequireFromGroupField 属性时指定的组选择器将在您的视图中使用,方法是将其指定为组中涉及的每个输入中的类。

这就是服务器端验证的全部内容。

我们来谈谈客户端验证。

如果您检查 RequireFromGroupFieldAttribute 类中的 GetClientValidationRules 实现,您将看到我使用的是字符串 requirefromgroup 而不是 require_from_group作为 ValidationType 属性的方法名称。这是因为 ASP.Net MVC 只允许验证类型的名称包含字母数字字符,并且不能以数字开头。因此,您需要添加以下 javascript:

$.validator.unobtrusive.adapters.add("requirefromgroup", ["number", "selector"], function (options) {
    options.rules["require_from_group"] = [options.params.number, options.params.selector];
    options.messages["require_from_group"] = options.message;
});

javascript 部分非常简单,因为在适配器函数的实现中,我们只需将验证委托给正确的 require_from_group 方法。

因为它适用于每种类型的 inputtextareaselect 元素,所以我可能认为这种方式更通用。

希望有帮助!

Use of require_from_group from jquery-validation team:

jQuery-validation project has a sub-folder in src folder called additional.
You can check it here.

In that folder we have a lot of additional validation methods that are not common that is why they're not added by default.

As you see in that folder it exists so many methods that you need to choose by picking which validation method you actually need.

Based on your question, the validation method you need is named require_from_group from additional folder.
Just download this associated file which is located here and put it into your Scripts application folder.

The documentation of this method explains this:

Lets you say "at least X inputs that match selector Y must be filled."

The end result is that neither of these inputs:

...will validate unless at least one of them is filled.

partnumber: {require_from_group: [1,".productinfo"]},
description: {require_from_group: [1,".productinfo"]}

options[0]: number of fields that must be filled in the group
options2: CSS selector that defines the group of conditionally required fields

Why you need to choose this implementation :

This validation method is generic and works for every input (text, checkbox, radio etc), textarea and select. This method also let you specify the minimum number of required inputs that need to be filled e.g

partnumber:     {require_from_group: [2,".productinfo"]},
category:       {require_from_group: [2,".productinfo"]},
description:    {require_from_group: [2,".productinfo"]}

I created two classes RequireFromGroupAttribute and RequireFromGroupFieldAttribute that will help you on both server-side and client-side validations

RequireFromGroupAttribute class definition

RequireFromGroupAttribute only derives from Attribute. The class is use just for configuration e.g. setting the number of fields that need to be filled for the validation. You need to provide to this class the CSS selector class that will be used by the validation method to get all elements on the same group. Because the default number of required fields is 1 then this attribute is only used to decorate your model if the minimum requirement in the spcefied group is greater than the default number.

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class RequireFromGroupAttribute : Attribute
{
    public const short DefaultNumber = 1;

    public string Selector { get; set; }

    public short Number { get; set; }

    public RequireFromGroupAttribute(string selector)
    {
        this.Selector = selector;
        this.Number = DefaultNumber;
    }

    public static short GetNumberOfRequiredFields(Type type, string selector)
    {
        var requiredFromGroupAttribute = type.GetCustomAttributes<RequireFromGroupAttribute>().SingleOrDefault(a => a.Selector == selector);
        return requiredFromGroupAttribute?.Number ?? DefaultNumber;
    }
}

RequireFromGroupFieldAttribute class definition

RequireFromGroupFieldAttribute which derives from ValidationAttribute and implements IClientValidatable. You need to use this class on each property in your model that participates to your group validation. You must pass the css selector class.

[AttributeUsage(AttributeTargets.Property)]
public class RequireFromGroupFieldAttribute : ValidationAttribute, IClientValidatable
{
    public string Selector { get; }

    public bool IncludeOthersFieldName { get; set; }

    public RequireFromGroupFieldAttribute(string selector)
        : base("Please fill at least {0} of these fields")
    {
        this.Selector = selector;
        this.IncludeOthersFieldName = true;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var properties = this.GetInvolvedProperties(validationContext.ObjectType); ;
        var numberOfRequiredFields = RequireFromGroupAttribute.GetNumberOfRequiredFields(validationContext.ObjectType, this.Selector);

        var values = new List<object> { value };
        var otherPropertiesValues = properties.Where(p => p.Key.Name != validationContext.MemberName)
                                              .Select(p => p.Key.GetValue(validationContext.ObjectInstance));
        values.AddRange(otherPropertiesValues);

        if (values.Count(s => !string.IsNullOrWhiteSpace(Convert.ToString(s))) >= numberOfRequiredFields)
        {
            return ValidationResult.Success;
        }

        return new ValidationResult(this.GetErrorMessage(numberOfRequiredFields, properties.Values), new List<string> { validationContext.MemberName });
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var properties = this.GetInvolvedProperties(metadata.ContainerType);
        var numberOfRequiredFields = RequireFromGroupAttribute.GetNumberOfRequiredFields(metadata.ContainerType, this.Selector);
        var rule = new ModelClientValidationRule
        {
            ValidationType = "requirefromgroup",
            ErrorMessage = this.GetErrorMessage(numberOfRequiredFields, properties.Values)
        };
        rule.ValidationParameters.Add("number", numberOfRequiredFields);
        rule.ValidationParameters.Add("selector", this.Selector);

        yield return rule;
    }

    private Dictionary<PropertyInfo, string> GetInvolvedProperties(Type type)
    {
        return type.GetProperties()
                   .Where(p => p.IsDefined(typeof(RequireFromGroupFieldAttribute)) &&
                               p.GetCustomAttribute<RequireFromGroupFieldAttribute>().Selector == this.Selector)
                   .ToDictionary(p => p, p => p.IsDefined(typeof(DisplayAttribute)) ? p.GetCustomAttribute<DisplayAttribute>().Name : p.Name);
    }

    private string GetErrorMessage(int numberOfRequiredFields, IEnumerable<string> properties)
    {
        var errorMessage = string.Format(this.ErrorMessageString, numberOfRequiredFields);
        if (this.IncludeOthersFieldName)
        {
            errorMessage += ": " + string.Join(", ", properties);
        }

        return errorMessage;
    }
}

How to use it in your view model?

In your model here is how to use it :

public class SomeViewModel
{
    internal const string GroupOne = "Group1";
    internal const string GroupTwo = "Group2";

    [RequireFromGroupField(GroupOne)]
    public bool IsA { get; set; }

    [RequireFromGroupField(GroupOne)]
    public bool IsB { get; set; }

    [RequireFromGroupField(GroupOne)]
    public bool IsC { get; set; }

    //... other properties

    [RequireFromGroupField(GroupTwo)]
    public bool IsY { get; set; }

    [RequireFromGroupField(GroupTwo)]
    public bool IsZ { get; set; }
}

By default you don't need to decorate your model with RequireFromGroupAttribute because the default number of required fields is 1. But if you want a number of required fields to be different to 1 you can do the following :

[RequireFromGroup(GroupOne, Number = 2)]
public class SomeViewModel
{
    //...
}

How to use it in your view code?

@model SomeViewModel

<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/require_from_group.js")" type="text/javascript"></script>

@using (Html.BeginForm())
{
    @Html.CheckBoxFor(x => x.IsA, new { @class="Group1"})<span>A</span>
    @Html.ValidationMessageFor(x => x.IsA)
    <br />
    @Html.CheckBoxFor(x => x.IsB, new { @class = "Group1" }) <span>B</span><br />
    @Html.CheckBoxFor(x => x.IsC, new { @class = "Group1" }) <span>C</span><br />

    @Html.CheckBoxFor(x => x.IsY, new { @class = "Group2" }) <span>Y</span>
    @Html.ValidationMessageFor(x => x.IsY)
    <br />
    @Html.CheckBoxFor(x => x.IsZ, new { @class = "Group2" })<span>Z</span><br />
    <input type="submit" value="OK" />
}

Notice the group selector you specified when using RequireFromGroupField attribute is use in your view by specifing it as a class in each input involved in your groups.

That is all for the server side validation.

Let's talk about the client side validation.

If you check the GetClientValidationRules implementation in RequireFromGroupFieldAttribute class you will see I'm using the string requirefromgroup and not require_from_group as the name of method for the ValidationType property. That is because ASP.Net MVC only allows the name of the validation type to contain alphanumeric char and must not start with a number. So you need to add the following javascript :

$.validator.unobtrusive.adapters.add("requirefromgroup", ["number", "selector"], function (options) {
    options.rules["require_from_group"] = [options.params.number, options.params.selector];
    options.messages["require_from_group"] = options.message;
});

The javascript part is really simple because in the implementation of the adaptater function we just delegate the validation to the correct require_from_group method.

Because it works with every type of input, textarea and select elements, I may think this way is more generic.

Hope that helps!

仲春光 2024-12-09 10:00:43

我在我的应用程序中实现了 Darin 的出色答案,只是我将其添加为字符串而不是布尔值。这适用于姓名/公司或电话/电子邮件等内容。我很喜欢它,除了一点点挑剔。

我尝试在没有工作电话、移动电话、家庭电话或电子邮件的情况下提交表格。我在客户端收到四个单独的验证错误。这对我来说很好,因为它可以让用户确切地知道可以填写哪些字段来消除错误。

我输入了一个电子邮件地址。现在,电子邮件下的单一验证消失了,但电话号码下的三个验证仍然存在。这些也不再是错误。

因此,我重新分配了检查验证的 jQuery 方法来解决这个问题。代码如下。希望它能帮助某人。

jQuery.validator.prototype.check = function (element) {

   var elements = [];
   elements.push(element);
   var names;

   while (elements.length > 0) {
      element = elements.pop();
      element = this.validationTargetFor(this.clean(element));

      var rules = $(element).rules();

      if ((rules.group) && (rules.group.propertynames) && (!names)) {
         names = rules.group.propertynames.split(",");
         names.splice($.inArray(element.name, names), 1);

         var name;
         while (name = names.pop()) {
            elements.push($("#" + name));
         }
      }

      var dependencyMismatch = false;
      var val = this.elementValue(element);
      var result;

      for (var method in rules) {
         var rule = { method: method, parameters: rules[method] };
         try {

            result = $.validator.methods[method].call(this, val, element, rule.parameters);

            // if a method indicates that the field is optional and therefore valid,
            // don't mark it as valid when there are no other rules
            if (result === "dependency-mismatch") {
               dependencyMismatch = true;
               continue;
            }
            dependencyMismatch = false;

            if (result === "pending") {
               this.toHide = this.toHide.not(this.errorsFor(element));
               return;
            }

            if (!result) {
               this.formatAndAdd(element, rule);
               return false;
            }
         } catch (e) {
            if (this.settings.debug && window.console) {
               console.log("Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.", e);
            }
            throw e;
         }
      }
      if (dependencyMismatch) {
         return;
      }
      if (this.objectLength(rules)) {
         this.successList.push(element);
      }
   }

   return true;
};

I implemented Darin's awesome answer into my application, except I added it for strings and not boolean values. This was for stuff like name/company, or phone/email. I loved it except for one minor nitpick.

I tried to submit my form without a work phone, mobile phone, home phone, or email. I got four separate validation errors client side. This is fine by me because it lets the users know exactly what field(s) can be filled in to make the error go away.

I typed in an email address. Now the single validation under email went away, but the three remained under the phone numbers. These are also no longer errors anymore.

So, I reassigned the jQuery method that checks validation to account for this. Code below. Hope it helps someone.

jQuery.validator.prototype.check = function (element) {

   var elements = [];
   elements.push(element);
   var names;

   while (elements.length > 0) {
      element = elements.pop();
      element = this.validationTargetFor(this.clean(element));

      var rules = $(element).rules();

      if ((rules.group) && (rules.group.propertynames) && (!names)) {
         names = rules.group.propertynames.split(",");
         names.splice($.inArray(element.name, names), 1);

         var name;
         while (name = names.pop()) {
            elements.push($("#" + name));
         }
      }

      var dependencyMismatch = false;
      var val = this.elementValue(element);
      var result;

      for (var method in rules) {
         var rule = { method: method, parameters: rules[method] };
         try {

            result = $.validator.methods[method].call(this, val, element, rule.parameters);

            // if a method indicates that the field is optional and therefore valid,
            // don't mark it as valid when there are no other rules
            if (result === "dependency-mismatch") {
               dependencyMismatch = true;
               continue;
            }
            dependencyMismatch = false;

            if (result === "pending") {
               this.toHide = this.toHide.not(this.errorsFor(element));
               return;
            }

            if (!result) {
               this.formatAndAdd(element, rule);
               return false;
            }
         } catch (e) {
            if (this.settings.debug && window.console) {
               console.log("Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.", e);
            }
            throw e;
         }
      }
      if (dependencyMismatch) {
         return;
      }
      if (this.objectLength(rules)) {
         this.successList.push(element);
      }
   }

   return true;
};
九局 2024-12-09 10:00:43

我知道这是一个旧线程,但我刚刚遇到了同样的场景,找到了一些解决方案,并看到了一个解决马特上面问题的解决方案,所以我想我会分享给那些遇到这个答案的人。查看:MVC3 不显眼的输入验证组

I know this is an old thread but I just came across the same scenario and found a few solutions and saw one that solves Matt's question above so I thought I would share for those who come across this answer. Check out: MVC3 unobtrusive validation group of inputs

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文