提交表单时模型中的列表 <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
之后直接绑定了元素
为什么当我提交表单时,控制器没有收到来自 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
之后直接绑定了元素