我正在尝试使用数据注释向模型中不能为空的列表添加验证。我已经尝试了自定义属性的几种实现,包括here和here.
我的看法:
<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>
型号:
public class ClientModel
{
public int ID { get; set; }
[Required] // Does nothing
public List<int> LocationIDs { get; set; }
}
控制器:
[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);
}
我尝试过的(功能相同的)属性之一:
[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();
}
}
目前,即使没有选择任何内容,检查 null 或空列表也会通过验证。在这种情况下,包含第一个选项的长度为 1 的列表被绑定。
我已经确认控制器实际上发送了一个长度为 1 的
List
。但是,我不确定如何更改此行为。我仍然认为这可能是下面块引用中描述的内容。
我认为我的问题可能在这个答案的编辑中描述,但我不确定如何解决它。
摘录如下:
您还必须注意在视图中绑定列表的方式。 例如,如果您将 List 绑定到这样的视图:
<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" />
MVC 模型绑定器将始终在您的模型中放入 5 个元素 列表,所有 String.Empty。如果这是您的视图的工作方式,则您的属性 需要变得更复杂一点,比如使用反射来拉 通用类型参数并将每个列表元素与 默认(T)或其他东西。
您可以尝试使用 Type 构造验证器,并验证列表中的任何项是否与您的类型的默认值不同。 更改您在此处提到的示例:
public class CannotBeEmptyAttribute : ValidationAttribute
{
private const string defaultError = "'{0}' must have at least one element.";
public Type ListType { get; private set; }
protected CannotBeEmptyAttribute(Type listType) : base(defaultError)
{
this.ListType = listType;
}
public override bool IsValid(object value)
{
object defaultValue = ListType.IsValueType ? Activator.CreateInstance(ListType) : null;
IEnumerable list = value as IEnumerable;
if (list != null)
{
foreach (var item in list)
{
if(item != defaultValue)
{
return true;
}
}
}
return false;
}
public override string FormatErrorMessage(string name)
{
return String.Format(this.ErrorMessageString, name);
}
}
你的
CannotBeEmptyAttribute
很好。我使用了完全相同的代码,并且效果很好。确保您也更改了视图模型以使用它(而不是 Required
在这种情况下它不会执行您想要的操作。
您的自定义属性不会为您提供客户端验证,除非您实施它。这意味着即使表单无效,它也会被发布,但
if (ModelState.IsValid)
会捕捉到它。您是否使用调试器查看 IsValid
是否为 false
?
“手动”创建输入对于服务器端验证完全没问题,但客户端将无法工作,因为缺少必要的
data-
属性。
这是一个最小版本的表单,带有手动创建的多选输入,适用于
CannotBeEmptyAttribute
:
@using (Html.BeginForm("TestPost", "Home"))
{
<select multiple="multiple" name="TestList">
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
@Html.ValidationMessage("TestList")
@Html.ValidationSummary()
<input type="submit" value="Save"/>
}
还分配一个
MinLength
对我有用:
[Required, MinLength(1, ErrorMessage = "Atleast one LocationId must be added")]
public List<int> LocationIDs { get; set; }
我可以获得零选择以实际绑定为
null
的唯一方法是使用Html.ListBoxFor
(我最初无法弄清楚 - 它应该从一开始就这样做):
@Html.ListBoxFor(model => model.SelectedLocations, Model.AllLocations, new { @class = "select2 form-control" })
我还没有得到客户端验证来工作,但我会把它作为另一个问题发布。