I'm trying to use Data Annotations to add validation to a List in my model that cannot be empty. I've tried several implementations of a custom attribute, including ones here and here.
My view:
<div class="form-group">
@* Model has a list of ints, LocationIDs *@
@Html.LabelFor(model => model.LocationIDs, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
<select class="select2 form-control" multiple id="LocationIDs" name="LocationIDs">
@* Adds every possible option to select box *@
@foreach (LocationModel loc in db.Locations)
{
<option value="@loc.ID">@loc.Name</option>
}
</select>
@Html.ValidationMessageFor(model => model.LocationIDs, "", new { @class = "text-danger" })
</div>
</div>
Model:
public class ClientModel
{
public int ID { get; set; }
[Required] // Does nothing
public List<int> LocationIDs { get; set; }
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,LocationIDs")] ClientModel clientModel)
{
if (ModelState.IsValid)
{
db.Clients.Add(clientModel);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(clientModel);
}
One of the (functionally identical) attributes I've tried:
[AttributeUsage(AttributeTargets.Property)]
public sealed class CannotBeEmptyAttribute : RequiredAttribute
{
public override bool IsValid(object value)
{
var list = value as IEnumerable;
return list != null && list.GetEnumerator().MoveNext();
}
}
Currently, checking for a null or empty list passes validation, even if nothing is chosen. In this case, a list of length one, containing the first option, is bound.
I've confirmed that the controller is actually sent a List
of length one. I'm not sure how to change this behavior, however. I still think this may be what was described in the below block quote.
I think my problem may be described in this answer's edit, but I'm not sure how to solve it.
Excerpt below:
You'll also have to be careful how you bind your list in your view. For example, if you bind a List to a view like this:
<input name="ListName[0]" type="text" />
<input name="ListName[1]" type="text" />
<input name="ListName[2]" type="text" />
<input name="ListName[3]" type="text" />
<input name="ListName[4]" type="text" />
The MVC model binder will always put 5 elements in your list, all String.Empty. If this is how your View works, your attribute would need to get a bit more complex, such as using Reflection to pull the generic type parameter and comparing each list element with default(T) or something.