在具有相同ID的下拉字段列表中,验证下拉项不会出现多次 [英] Validate dropdown item does not occur more than once in a list of dropdown fields with the same id

查看:68
本文介绍了在具有相同ID的下拉字段列表中,验证下拉项不会出现多次的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个表单,用户可以根据需要添加任意多的行.每次期望他们从提供的下拉列表中选择一个不同的选项.目前,没有验证可阻止他们多次选择相同的值.

每行都是一个"ResourceCount". ViewModel具有一个ResourceCountViewModel的IList,因此每行都作为一项添加到列表中.

ResourceCount视图模型由一个用于存储所选下拉值的"id"和一个用于数字字段的数量"组成.

在这种情况下,我无法想到一种使用比较"注释的方法. 我该如何对此进行适当的验证?

接受的答案中有很多好的建议.我在控制器后操作中做了一个简单的检查:

if (resourceCounts.Count != resourceCounts.Where(d => d.Id!= 0)
                                          .Select(x => x.Id)
                                          .Distinct()
                                          .Count())
{

ModelState.AddModelError(string.Empty, "You can't select the same option more than once");
//etc.

}

解决方案

使用ValidationAttribute(内置的或自定义的)和MVC的客户端验证完全不可能做到这一点.

验证属性应用于模型(您的类)的属性,并且检查验证的上下文仅针对该实例-它不知道集合中模型的其他实例,因此无法比较其他实例实例.

相反,如果将ValidationAttribute应用于集合(例如,List<T>属性),则不打扰的验证将不起作用,因为将规则添加到$.validator所必需的data-val-*属性只能在包含以下内容的情况下生成:集合属性的输入(而不是集合中每个对象的每个属性),这意味着ModelBinding将失败.

您将需要编写自己的控制器代码和脚本来实现自定义验证.

在客户端,您可以处理<select>.change()事件,并检查是否有以前的选择匹配,如果匹配,则显示错误消息.您尚未显示模型或视图,但基于以下html(对于集合中的每个对象重复显示

<select class="select" .....>
    <option value="">Please select</option>
    <option value="1">On-call</option>
    ....
<select>
<div class="error"></div> // see notes below if you using ValidationMessageFor()

那么脚本应该是

var errors = $('.error');
var selects = $('.select');
$('.select').change(function() {
  var selected = $.map(selects, function(s, i) {
    return $(s).val();
  })
  $.each(selects, function(i, s) {
    var error = $(this).next('.error');
    var val = $(this).val();
    var index = i;
    if ($(this).val()) {
      var others = selected.filter(function(s, i) {
        if (i != index) {
          return s;
        }
      });
      if (others.indexOf(val) > -1) {
        error.text('Please make a unique selection');
      } else {
        error.text('');
      }
    } else {
      error.text('');
    }

  })
})

请参考此小提琴作为有效示例.

或者,您可以在每个<select>中隐藏/显示(或禁用)选项,以防止用户首先进行无效选择,但是如果您动态添加/删除项目和/或查看视图,则情况变得更加复杂正在编辑该属性已具有选定值的现有数据(我将留给您询问一个新问题,以显示是否要执行该操作).

在服务器端,您可以检查重复的值,如果是,则添加ModelState错误并返回视图,例如

var selected = new List<int>();
for (int i = 0 i < model.Count; i++)
{
    if (selected.Contains(model[i].YourProperty))
    {
        ModelState.AddModelError("", "Please make a unique selection");
        break;
    }
    else
    {
        selected.Add(model[i].YourProperty);
    }
}
if (!ModelState.IsValid)
{
    return View(model);
}
....

或使用linq

if (model.Select(x => x.YourProperty).GroupBy(x => x).Any(g => g.Count() > 1))
{
    ModelState.AddModelError("", "Please make a unique selection");
}

,然后将其显示在视图@Html.ValidationSummary()中.

如果您在视图中为每个下拉列表使用@Html.ValidationMessageFor(m => m[i].YourProperty),则可以将上述循环修改为

if (selected.Contains(model[i].YourProperty))
{
    var propertyName = string.Format("[{0}].yourPropertyName", i);
    ModelState.AddModelError(propertyName, "Please make a unique selection");
    break;
}

并修改脚本以添加/删除由ValidationMessageFor()生成的<span>元素的消息(即代替上面显示的<div class="error">元素)

作为附带说明,如果您想了解有关验证属性如何与客户端验证结合使用的更多信息,建议阅读

The ResourceCount view model consists of an "id" to store the dropdown value selected and a "quantity" for the number field.

I can't think of a way to use the Compare annotation in this scenario. How can I implement appropriate validation for this?

EDIT: There is lots of good advice in the accepted answer. I went with a simple check in the controller post action:

if (resourceCounts.Count != resourceCounts.Where(d => d.Id!= 0)
                                          .Select(x => x.Id)
                                          .Distinct()
                                          .Count())
{

ModelState.AddModelError(string.Empty, "You can't select the same option more than once");
//etc.

}

解决方案

This is simply not possible using a ValidationAttribute (either in-built or custom ones) and MVC's unobtrusive client side validation.

Validation attributes are applied to properties of a model (your class), and the context in which validation is checked is for that instance only - it has no knowledge of other instances of the model within the collection, therefore it cannot compare other instances.

Conversely if you apply a ValidationAttribute to a collection (e.g. List<T> property), unobtrusive validation will not work because the data-val-* attributes necessary to add rules to the $.validator could only be generated if you include an input for the collection property (as opposed to each property of each object in the collection) which means ModelBinding would fail.

You will need to write your own controller code and scripts to achieve your custom validation.

On the client side, you could handle the .change() event of the <select> and check if any previous selections match, and if so display an error message. You have not shown your model, or the view, but based on the following html (repeated for each object in the collection

<select class="select" .....>
    <option value="">Please select</option>
    <option value="1">On-call</option>
    ....
<select>
<div class="error"></div> // see notes below if you using ValidationMessageFor()

then the script would be

var errors = $('.error');
var selects = $('.select');
$('.select').change(function() {
  var selected = $.map(selects, function(s, i) {
    return $(s).val();
  })
  $.each(selects, function(i, s) {
    var error = $(this).next('.error');
    var val = $(this).val();
    var index = i;
    if ($(this).val()) {
      var others = selected.filter(function(s, i) {
        if (i != index) {
          return s;
        }
      });
      if (others.indexOf(val) > -1) {
        error.text('Please make a unique selection');
      } else {
        error.text('');
      }
    } else {
      error.text('');
    }

  })
})

Refer this fiddle for a working example.

Alternatively you could hide/show (or disable) options in each <select> to prevent the user making invalid selections in the first place, but that becomes more complex if your dynamically adding/deleting items, and/or when your view is editing existing data where the property already has a selected value (I'll leave that to you to ask a new question showing your attempt if you want to implement that).

On the server side, you can check for duplicate values, and if so, add a ModelState error and return the view, for example

var selected = new List<int>();
for (int i = 0 i < model.Count; i++)
{
    if (selected.Contains(model[i].YourProperty))
    {
        ModelState.AddModelError("", "Please make a unique selection");
        break;
    }
    else
    {
        selected.Add(model[i].YourProperty);
    }
}
if (!ModelState.IsValid)
{
    return View(model);
}
....

or using linq

if (model.Select(x => x.YourProperty).GroupBy(x => x).Any(g => g.Count() > 1))
{
    ModelState.AddModelError("", "Please make a unique selection");
}

which would then be displayed in the views @Html.ValidationSummary().

If your using @Html.ValidationMessageFor(m => m[i].YourProperty) in your view for each dropdownlist, then the above loop can be modified to

if (selected.Contains(model[i].YourProperty))
{
    var propertyName = string.Format("[{0}].yourPropertyName", i);
    ModelState.AddModelError(propertyName, "Please make a unique selection");
    break;
}

and modify the script to add/remove the message for the <span> element generated by ValidationMessageFor() (i.e instead of the <div class="error"> element as shown above)

As a side note, if you want to learn more about how validation attributes in conjunction with client side validation work, I recommend reading The Complete Guide To Validation In ASP.NET MVC 3 - Part 2.

这篇关于在具有相同ID的下拉字段列表中,验证下拉项不会出现多次的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆