MVC3 - 包含复杂类型列表的Viewmodel

Ins*_*hew 18 model-binding viewmodel checkboxfor asp.net-mvc-3

如果以前曾被问过,请道歉; 有一百万种方法来表达它,因此寻找答案已经证明是困难的.

我有一个具有以下属性的viewmodel:

public class AssignSoftwareLicenseViewModel
{
    public int LicenseId { get; set; }
    public ICollection<SelectableDeviceViewModel> Devices { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

SelectableDeviceViewModel的简化版本是这样的:

public class SelectableDeviceViewModel
{
    public int DeviceInstanceId { get; set; }
    public bool IsSelected { get; set; }
    public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

在我的视图中,我试图在输入表单中显示Devices属性的可编辑复选框列表.目前,我的View看起来像这样:

@using (Html.BeginForm())
{
    @Html.HiddenFor(x => Model.LicenseId)
    <table>
        <tr>
            <th>Name</th>
            <th></th>
        </tr>
        @foreach (SelectableDeviceViewModel device in Model.Devices)
        {
            @Html.HiddenFor(x => device.DeviceInstanceId)
            <tr>
                <td>@Html.CheckBoxFor(x => device.IsSelected)</td>
                <td>@device.Name</td>
            </tr>
        }
    </table>

    <input type="submit" value="Assign" />
}
Run Code Online (Sandbox Code Playgroud)

问题是,当模型回发到控制器时,Devices为null.

我的假设是发生了这种情况,因为即使我正在编辑其内容,也不会在表单中明确包含Devices属性.我尝试用HiddenFor包含它,但这只是导致模型有一个空列表而不是null.

知道我在这里做错了吗?

Dar*_*rov 30

我的假设是发生了这种情况,因为即使我正在编辑其内容,也不会在表单中明确包含Devices属性.

不,你的假设是错的.这不正确绑定的原因是因为您的输入字段没有正确的名称.例如,他们被称为name="IsSelected"而不是name="Devices[0].IsSelected".看看需要用于绑定到集合的正确的有线格式:http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx

但为什么会这样呢?

这是因为foreach您在视图中使用的循环.您x => device.IsSelected将复选框用作lambda表达式,但这根本不考虑Devices属性(通过查看生成的网页源代码可以看到).

所以我该怎么做?

我个人建议你使用编辑器模板,因为他们尊重复杂属性的导航上下文并生成正确的输入名称.因此,foreach在视图中删除整个循环,并用一行代码替换它:

@Html.EditorFor(x => x.Devices)
Run Code Online (Sandbox Code Playgroud)

现在定义一个自定义编辑器模板,它将由ASP.NET MVC为Devices集合的每个元素自动呈现.警告:此模板的位置和名称非常重要,因为它符合惯例~/Views/Shared/EditorTemplates/SelectableDeviceViewModel.cshtml:

@model SelectableDeviceViewModel
@Html.HiddenFor(x => x.DeviceInstanceId)
<tr>
    <td>@Html.CheckBoxFor(x => x.IsSelected)</td>
    <td>@Html.DisplayFor(x => x.Name)</td>
</tr>
Run Code Online (Sandbox Code Playgroud)

另一种方法(我不建议这样做)是将ICollection视图模型中的当前值更改为索引集合(例如IList<T>数组或数组T[]):

public class AssignSoftwareLicenseViewModel
{
    public int LicenseId { get; set; }
    public IList<SelectableDeviceViewModel> Devices { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

而不是foreach使用for循环:

@for (var i = 0; i < Model.Devices.Count; i++)
{
    @Html.HiddenFor(x => x.Devices[i].DeviceInstanceId)
    <tr>
        <td>@Html.CheckBoxFor(x => x.Devices[i].IsSelected)</td>
        <td>@Html.DisplayFor(x => x.Devices[i].Name</td>
    </tr>
}
Run Code Online (Sandbox Code Playgroud)