18

抱歉,如果之前有人问过这个问题;有一百万种表达方式,因此事实证明很难找到答案。

我有一个具有以下属性的视图模型:

public class AssignSoftwareLicenseViewModel
{
    public int LicenseId { get; set; }
    public ICollection<SelectableDeviceViewModel> Devices { get; set; }
}

SelectableDeviceViewModel 的简化版本是这样的:

public class SelectableDeviceViewModel
{
    public int DeviceInstanceId { get; set; }
    public bool IsSelected { get; set; }
    public string Name { get; set; }
}

在我的视图中,我试图在输入表单中显示 Devices 属性的可编辑复选框列表。目前,我的视图如下所示:

@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" />
}

问题是,当模型被发送回控制器时,Devices 为空。

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

知道我在这里做错了什么吗?

4

2 回答 2

30

My assumption is that this is happening because even though I'm editing its contents, the Devices property is never explicitly included in the form.

No, your assumption is wrong. The reason this doesn't get bound properly is because your input fields doesn't have correct names. For example they are called name="IsSelected" instead of name="Devices[0].IsSelected". Take a look at the correct wire format that needs to be used to bind to collections: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx

But why this happens?

It happens because of the foreach loop that you used in your view. You used x => device.IsSelected as lambda expression for the checkbox but this doesn't take into account the Devices property at all (as you can see by looking at the generated source code of your web page).

So what should I do?

Personally I would recommend you using editor templates as they respect the navigational context of complex properties and generate correct input names. So get rid of the entire foreach loop in your view and replace it with a single line of code:

@Html.EditorFor(x => x.Devices)

and now define a custom editor template that will automatically be rendered by ASP.NET MVC for each element of the Devices collection. Warning: the location and name of this template are very important as this works by convention: ~/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>

Another approach (which I don't recommend) is to change your current ICollection in your view model to an indexed collection (such as an IList<T> or an array T[]):

public class AssignSoftwareLicenseViewModel
{
    public int LicenseId { get; set; }
    public IList<SelectableDeviceViewModel> Devices { get; set; }
}

and then instead of the foreach use a for loop:

@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>
}
于 2012-07-27T06:23:45.327 回答
0

EditorFor templates work and keep the code clean. You don't need the loops and the model is posted back correctly.

However, has anyone had problems with validation on complex viewmodels (nested EditorFor templates)? I'm using Kendo Validator and am running into all sorts of jquery errors.

于 2013-01-09T06:42:26.473 回答