【问题标题】:Can't force List<int> to be required不能强制要求 List<int>
【发布时间】:2017-07-19 21:42:20
【问题描述】:

我正在尝试使用数据注释将验证添加到我的模型中不能为空的列表中。我尝试了几种自定义属性的实现,包括herehere

我的看法:

<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 或空列表会通过验证,即使没有选择任何内容。在这种情况下,包含第一个选项的长度为 one 的列表被绑定。


我已经确认控制器实际上发送了一个长度为 1 的 List。但是,我不确定如何改变这种行为。我仍然认为这可能是下面块引用中描述的内容。


认为我的问题可能在this answer's edit 中有所描述,但我不确定如何解决。

摘录如下:

您还必须小心在视图中绑定列表的方式。 例如,如果您将 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 模型绑定器将始终在您的 列表,所有 String.Empty。如果这是您的视图的工作方式,那么您的属性 需要变得更复杂一些,例如使用反射拉 泛型类型参数并将每个列表元素与 default(T) 什么的。

【问题讨论】:

  • 您是要验证最终用户是否选择了 select 中的项目,还是要验证 model.LocationIDs 中是否有条目,或两者兼而有之?
  • 两者,我想。客户端是select,服务器端是model.LocationIDs。我对自动验证属性没有 100% 的透彻理解,但我知道数据注释可以防止保存无效值(必不可少),同时还会向最终用户提供错误消息。
  • @mjwills 我如何编辑我的问题以使其更清楚?
  • 我怀疑ValidationMessageFor 可能会帮助您处理前者(选择一个项目),但不能帮助您处理后者(其中有条目)。
  • 如果你想了解valdation,那么我建议你从The Complete Guide To Validation In ASP.NET MVC 3 - Part 2开始

标签: c# asp.net asp.net-mvc entity-framework


【解决方案1】:

您可以尝试使用 Type 构建验证器,并验证列表中的任何项是否与您的类型的默认值不同。 更改您提到的示例here

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);
    }
}

【讨论】:

    【解决方案2】:

    您的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"/>
    }
    

    【讨论】:

    • 除了扩展HtmlHelper之外,我只是看不到任何创建我想要的选择框的方法,但我对源代码的探索并没有帮助我创建任何更高级的东西比Html.SimpleText("words") 其中"words" 内置在某处的字符串中并返回。
    • 不,仍然不知道为什么。不过,它确实适用于一些种子值,我实际上在数据库初始化程序中将List 设置为null。当我自己发送表单时,IsValid 是真的。不过,我不知道如何检查发送到控制器的值。
    • 我能想到的只是它实际上是在发送一个包含第一项的列表(这就是最终被保存的内容)。但我不知道如何解决。选择一项是有效的。
    • 我已经确认控制器实际上发送了一个长度为 1 的 List。但是,我不确定如何改变这种行为。我仍然认为这可能是上面块引用中描述的内容。
    【解决方案3】:

    我可以获得零选择以实际绑定为null 的唯一方法是使用Html.ListBoxFor(我最初无法弄清楚 - 从一开始就应该这样做):

    @Html.ListBoxFor(model => model.SelectedLocations, Model.AllLocations, new { @class = "select2 form-control" })
    

    我还没有让客户端验证工作,但我会把它作为另一个问题发布。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-04
      • 2021-10-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多