提交表单时模型中的列表 <T> 未到达控制器

List<T> within the model not reaching controller when form is submitted

为什么当我提交表单时,控制器没有收到来自 Model.Items 的任何数据? 我正在使用 asp.net 核心 mvc 2.0

这是我的视图模型

public class SectionVM
{
    public long Id { get; set; }

    [Required]
    public string Name { get; set; }

    public string Description { get; set; }

    public ICollection<SectionItemCollector> Items { get; set; }
}

public class SectionItemCollector
{
    public bool IsSelected { get; set; }

    public long ItemId { get; set; }

    public string ItemName { get; set; }
}

在这里,我创建了一个新的视图模型,用数据填充它并将其发送到视图。视图接收视图模型,我目前能够毫无问题地访问视图模型中的所有数据。

// Controller Actions
// GET: MenuSections/Create
public async Task<IActionResult> Create()
{
    var viewModel = new SectionVM();
    var allItems = await _context.MenuItem.ToListAsync();
    var vmItems = new List<SectionItemCollector>();
    foreach (var i in allItems)
    {
        vmItems.Add(new SectionItemCollector() { IsSelected = false, ItemId = i.Id, ItemName = i.Name });
    }
    viewModel.Items = vmItems;
    return View(viewModel);
}

我的视图显示视图模型中包含的数据,用户可以选择select一些项目

// View
@model ROO_App.Models.SectionVM
...
<form asp-action="Create">
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>
    <div class="form-group">
        <label asp-for="Name" class="control-label"></label>
        <input asp-for="Name" class="form-control" />
        <span asp-validation-for="Name" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Description" class="control-label"></label>
        <input asp-for="Description" class="form-control" />
        <span asp-validation-for="Description" class="text-danger"></span>
    </div>
    <div class="form-group">
    @{
        foreach (var item in Model.Items)
        {
             <div class="row">
                 <div class="col-sm-1">  
                     <input asp-for="@item.IsSelected" type="checkbox" class="form-check-input" id="@item.ItemId"/> 
                 </div>
                 <div class="col-sm-4">@item.ItemName</div>    
             </div>
        }
    }

    </div>
    <div class="form-group">
        <input type="submit" value="Create" class="btn btn-default" />
    </div>
</form>

提交表单时 Items = null

// POST: MenuSections/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name,Description, Items")] SectionVM menuSection)
{
    ...
}

原因:

没有名为 "Items" 的变量被发送到服务器,因为表单不包含名为 "Items" 的输入。相反,复选框输入正在发送一个名为 "item.IsSelected" 的数组。

在您看来以下行:(显示重要部分)

<input asp-for="@item.IsSelected" 

正在构建一个 HTML 输入字段,如下所示:(显示重要部分)

<input type="checkbox" name="item.IsSelected" value="true">

使用 Google Chrome,在提交表单之前按 F12。查看 Network 选项卡 > Headers > Form data,您可以确认它为每个选中的项目提交了以下字段。

item.IsSelected:true

所以控制器没有收到名为 "Items" 的输入。它正在接收一个名为 "item.IsSelected" 的字符串数组,默认值为 "true".

正在寻求解决方案:

您可以通过向输入标签提供可选的 'name' 参数来覆盖默认名称。

<input asp-for="@item.IsSelected" name="Items" 

您还设置了 Id 属性。在此上下文中,Id 用于 HTML 并且不与表单一起提交。仅提交复选框的值。

id="@item.ItemId" /> 

如果您想将已检查的 ID 作为数组提交回,请将其设置为值,而不是 ID。

value="@item.ItemId" />            

如果您更喜欢使用 ItemName 作为值:

value="@item.ItemName" />

但是,HTML 复选框输入不允许您同时传递 ItemName 和 Id 值,因为它只传递所选项目的单个值。

MVC 控制器将无法将名为 "Items" 的字符串 [] 映射到您的 SectionItemCollector class,因为类型不匹配。它可能会检测到数组中项目的数量,但这些值将保持为空。

由于该限制,最好通过 AJAX.

将表单数据序列化为 JSON 对象和 post

用表格解决Post

要使用表单解决 Post,您需要进行一些更改以绑定和处理表单数据:

将以下属性添加到您的 class SectionVM:

public string[] CheckedItems { get; set; }

public string[] CheckedValues { get; set; }

更新 Create.cshtml 以将 isChecked 和 ID 作为复选框值的一部分传递。添加隐藏输入以传递 ItemName。

<div class="form-group">
    @{
        foreach (var item in Model.Items)
        {
            <div class="row">
                <div class="col-sm-1">
                    @* If checked, the value will be posted as the CheckedItems string[] *@
                    <input asp-for="@item.IsSelected" name="CheckedItems" type="checkbox" class="form-check-input" value="@item.ItemId" />

                    @* posting a hidden value as the CheckedValues string[] *@
                    <input name="CheckedValues" type="hidden" value="@item.ItemName" />
                </div>
                <div class="col-sm-4">@item.ItemName</div>
            </div>
        }
    }

</div>

最后,更新您的控制器操作以收集复选框和隐藏值并将它们 re-combine 到预期对象中:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name,Description, Items, CheckedItems, CheckedValues")] SectionVM menuSection)
{
    // convert CheckedItems and CheckedValues into List<SectionItemCollector>
    var selectedItems = new List<SectionItemCollector>();
    for (int i = 0; i < menuSection.CheckedItems.Length; i++)
    {
        // if the checked ID is number, find the related ItemName.
        long id;
        if (long.TryParse(menuSection.CheckedItems[i], out id))
        {
            var item = new SectionItemCollector
            {
                ItemId = id,
                // inferred as HTML only posts checkboxes that are selected.
                IsSelected = true,                         
                // get the ItemName with the same index.
                ItemName = menuSection.CheckedValues[i]
            };
            selectedItems.Add(item);
        }
    }
    var viewmodel = new SectionVM()
    {
        Items = selectedItems
    };

    // TODO: Set the breakpoint to the next line and inspect viewmodel to see your result.
    return View(viewmodel);
}

也许 Asp.Net 有另一种方法可以抽象出提交具有多个属性的复选框的复杂性。如果是这样,我希望有人也提交这个答案。

这是我的第一个 post,提前致歉(尤其是我的英语)

当post表单时,可以使用tag helper进行采集,直接获取model。

这是一个小例子,但我认为它是让视图和控制器代码干净和标准的最佳解决方案:

<div class="row">

    @{
        foreach (var element in Model.Elements)
        {
            int index = Model.Elements.IndexOf(element);

            <input type="text" asp-for="Elements[index].Text" />
            <input type="checkbox" asp-for="Elements[index].IsSelected" />
        }
    }
</div>

所以只需使用 asp-for 就像另一个 属性 在控制器中,您的集合在 post

之后直接绑定了元素