【问题标题】:List<T> within the model not reaching controller when form is submitted提交表单时模型中的 List<T> 未到达控制器
【发布时间】:2018-02-16 01:39:53
【问题描述】:

为什么当我提交表单时,控制器没有收到来自 Model.Items 的任何数据? 我正在使用 asp.net core 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);
}

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

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

【问题讨论】:

  • 您的视图是否指定了模型?它通常是视图的第一行。 @model SectionVM
  • 是的,我只是将其包含在问题中以避免混淆,谢谢

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


【解决方案1】:

原因:

没有名为“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”。

努力寻找解决方案:

您可以通过向输入标记提供可选的“名称”参数来覆盖默认名称。

<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 类,因为类型不匹配。它可能会检测到数组中项目的计数,但值将保持为空。

由于这个限制,最好将表单数据序列化为 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 有另一种方法可以抽象出提交具有多个属性的复选框的复杂性。如果是这样,我希望有人也提交这个答案。

【讨论】:

  • 我明白了,但是有没有办法将 Model.Items 作为 List 或 JSON 传递给控制器​​?
  • 我也尝试以这种方式绑定属性: public async Task Create([Bind("Id,Name,Description")] SectionVM menuSection, List Items)
  • 我对它进行了更多尝试,并提供了一个示例解决方案来传递该数据。
  • 那个工作正常。最后,我只是传递了所选项目的 id,因此我可以使用它从数据上下文中获取项目。谢谢
【解决方案2】:

这是我的第一篇文章,提前抱歉(尤其是我的英语)

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-11-13
    • 1970-01-01
    • 1970-01-01
    • 2014-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多