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

时间:2018-02-16 01:39:53

标签: razor asp.net-core-mvc model-binding

为什么当我提交表单时,控制器不会从Model.Items接收任何数据? 我使用的是asp.net core mvc 2.0

这是我的View-Model

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

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

// 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)
{
    ...
}

2 个答案:

答案 0 :(得分:2)

<强>原因:

没有名为“Items”的变量被发送到服务器,因为表单不包含名为“Items”的输入。而是通过复选框输入发送名为“item.IsSelected”的数组。

在您的视图中,以下行:(显示重要部分)

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

是一个HTML输入字段的脚手架,如下所示:(显示重要部分)

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

使用Google Chrome,在提交表单之前按F12。查看“网络”标签&gt;标题&gt;表单数据,您可以确认它为每个选中的项目提交了以下字段。

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 vales,因为它只传递所选项目的单个值。

由于类型不匹配,MVC控制器将无法将名为“Items”的字符串[]映射到SectionItemCollector类。它可能会检测数组中Items的计数,但值仍为null。

有了这个限制,最好将表单数据序列化为JSON对象并通过AJAX发布。

使用表单帖子解决

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

将以下属性添加到您的类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>

最后,更新控制器操作以收集复选框和隐藏值,并将它们重新组合到预期对象中:

[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有另一种方法来抽象提交具有多个属性的复选框的复杂性。如果是这样,我希望有人也提出这个答案。

答案 1 :(得分:1)

这是我的第一篇文章,所以对不起(特别是对于我的英语)

您可以使用标签帮助程序进行收集,并在发布表单时直接获取模型。

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

<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 并且在控制器中,您的集合在发布后具有直接绑定的元素