Validate dropdown item does not occur more than once in a list of dropdown fields with the same id

↘锁芯ラ 提交于 2019-12-31 02:21:10

问题


I have a form where the user can add as many rows as needed. Each time they are expected to select a different option from the dropdown list provided. At the moment there is no validation to stop them selecting the same value multiple times.

Each row is a "ResourceCount". The ViewModel has an IList of ResourceCountViewModel, so each row is being added as an item to the list.

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.

}

回答1:


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.



来源:https://stackoverflow.com/questions/48321261/validate-dropdown-item-does-not-occur-more-than-once-in-a-list-of-dropdown-field

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!