MVC Ajax For 循环内的表单
MVC Ajax Form inside a For Loop
我在 For 循环中使用 Ajax.BeginForm,允许我为 for 循环中的每个项目 post 到控制器。最初,for 循环在页面加载时正确呈现每个项目。
当控制器处理了从 Ajax.BeginForm 正确填充的传入视图模型时,该方法将新生成的视图模型发送回视图,但是我的 For 循环中的每个项目现在都被复制了,并且所有文本框现在具有提交的视图模型的值。
非常困惑如何正确构建此代码,我需要提交才能基于 ajax 和局部视图工作。我确实考虑过使用 JQuery 提交,但担心我可能会丢失 AntiForgeryToken,而 Ajax.BeginForm 是一种更优雅的方法。非常感谢任何帮助。也很高兴提供更多信息。
VIEW
@model Namespace.Models.MyParentViewModel
@for (int i = 0; i < Model.Items.Count; i++)
{
using (Ajax.BeginForm("SaveItem", "Controller", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "pensions" }))
{
@Html.AntiForgeryToken()
@Html.ValidationSummary()
<div>
@Html.Hidden("ID", Model.Items[i].ID)
@Html.TextBox("TheName", Model.Items[i].TheName, new { @class = "form-control", @id = "item-" + i})
<input type="submit" value="Save" class="btn save" name="Command" />
</div>
}
}
控制器
[HttpPost]
[ValidateAntiForgeryToken]
[ActionName("SaveItem")]
public ActionResult SaveItem(MyItemViewModel mivm, string Command)
{
if (ModelState.IsValid)
{
#do some logic
}
// Return a newly populated MyViewModel with updated mivm.
var model = PopulateMyParentViewModel();
return PartialView("_MyPartialView", model);
}
private MyParentViewModel PopulateMyParentViewModel()
{
List<MyItemsViewModel> lstItems = new List<MyItemsViewModel>();
foreach (var item in enity.items.OrderBy(p => p.ID).ToList())
{
var ExistingItem = new MyItemViewModel
{
ID = item.ID,
TheName = item.TheName
};
lstItems.Add(MyItemViewModel);
}
MyParentViewModel.Items = lstItems;
return MyParentViewModel
}
我不确定你的代码中的 "pensions" 是什么,我猜它是一个 div 围绕着循环生成的每个表单?如果是这样,您的问题如下:
首先,如果 none 个项目是相互关联的(也就是说,触发一个项目的一些更新会导致另一个项目也被更新),你不应该浪费资源到将整个列表发送回浏览器 - 考虑只发送回受影响的更新显示 "record" 并用响应更新相应的项目。
导致项目重复的原因:AjaxOptions
class 有一个 属性 InsertionMode
,我记得它的默认值是 InsertionMode = InsertionMode.InsertAfter
,这会导致出现重复项。为什么?您向服务器发送 Ajax 请求,服务器发回由整个列表填充的 HTML 片段,而不是仅一个项目,然后浏览器将该片段附加到现有记录。
解决方案
如果您重新制作项目以仅发回一条记录而不是所有记录,则只需添加一个唯一标识(= 具有唯一的 id
属性)<div>
using(...)
块周围的元素,将 InsertionMode
设置为 InsertionMode=InsertionMode.Replace
并将 UpdateTargetId
属性 设置为 <div>
的 id
,像这样:
<div id="record-@i">
@using (Ajax.BeginForm("SaveItem", "Controller", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "record-" + i.ToString(), InsertionMode = InsertionMode.Replace }))
{
// ...
}
</div>
这将导致 Ajax 响应替换发送请求的容器的 contents(由 contents 我的意思是包装元素本身没有被替换!您需要使用 InsertionMode.ReplaceWith
来实现该行为)。如果你弄错了这一点,你最终会在宿主容器中倾倒大量的嵌套元素,可能会破坏使用非常特定选择器的脚本 and/or 样式。
如果你坚持发送整个项目集合,那么只需用 <div>
标记包裹 for 循环,给它一个 id
,设置 InsertionMode
到 InsertionMode=InsertionMode.Replace
,UpdateTargetId
属性 到 <div>
的 id
。
--------更新------------
回答您的评论:
假设您有一个视图 - 为了简单起见 - 以表格格式显示记录。然后,结果如下所示:
Name | Age | Salary | Hide
------------------------------------
Peter | 32 | k | Hide button
Eva | 28 | k | Hide button
假设您对 Ajax 请求所做的更改是,每当您隐藏记录时,您都会发回单个 table 行,其中包含一个按钮,现在显示 'show' 而不是 'hide' 以及关于属于触发请求的按钮的人的相同数据。
然后,当你直接(=非ajax,当你直接导航到页面)请求时,你这样做:
@model PeopleCollection
...
<table>
<thead>
<tr>
<td>Name</td>
<td>Age</td>
<td>Salary</td>
<td>Hide</td>
</tr>
</thead>
<tbody>
@foreach (Person record in Model) {
@Html.Partial("TableRow_Partial",record)
}
</tbody>
"TableRow_Partial" 视图如下所示:
@model Person
@{
AjaxOptions opts = new AjaxOptions() {
UpdateTargetId = "person-row-" + Model.Id.ToString(),
InsertionMode = InsertionMode.ReplaceWith
// etc
};
}
...
<tr id="person-row-@Model.Id">
<td>@Model.Name</td>
<td>@Model.Age</td>
<td>@Model.Salary</td>
<td>
<!-- configure the BeginForm(..) call with the action name and a routeValue anonymous object as "new { id = Model.Id}" -->
@using (Ajax.BeginForm(...)) {
<input type="submit" value="@(Model.IsHidden ? "Show" : "Hide")" />
}
</td>
</tr>
这将简单地将循环的构造提取到单独的局部视图中。处理您的 Ajax-请求的操作方法将简单地 return 这个视图,其中填充了您已更新的一条记录,如下所示:
public ActionResult UpdateRecord(Int64 id)
{
var _record = Repository.Get(id);
// logic to hide and update record
return PartialView("TableRow_Partial",_record);
}
为此,您必须将 InsertionMode
设置为 InsertionMode.ReplaceWith
。所有这一切都是当你发送一个 ajax 请求只用一条记录做某事时,你只刷新一个项目 - 触发 ajax 调用的那个,然后你只需 return新行(一行!)并将旧行替换为新行。
如果你真的想在每次对记录做某事时都将整个项目集合发送回客户端,只需将整个呈现的 table 发送到客户端并用旧的替换新的以类似的方式。
我在 For 循环中使用 Ajax.BeginForm,允许我为 for 循环中的每个项目 post 到控制器。最初,for 循环在页面加载时正确呈现每个项目。 当控制器处理了从 Ajax.BeginForm 正确填充的传入视图模型时,该方法将新生成的视图模型发送回视图,但是我的 For 循环中的每个项目现在都被复制了,并且所有文本框现在具有提交的视图模型的值。
非常困惑如何正确构建此代码,我需要提交才能基于 ajax 和局部视图工作。我确实考虑过使用 JQuery 提交,但担心我可能会丢失 AntiForgeryToken,而 Ajax.BeginForm 是一种更优雅的方法。非常感谢任何帮助。也很高兴提供更多信息。
VIEW
@model Namespace.Models.MyParentViewModel
@for (int i = 0; i < Model.Items.Count; i++)
{
using (Ajax.BeginForm("SaveItem", "Controller", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "pensions" }))
{
@Html.AntiForgeryToken()
@Html.ValidationSummary()
<div>
@Html.Hidden("ID", Model.Items[i].ID)
@Html.TextBox("TheName", Model.Items[i].TheName, new { @class = "form-control", @id = "item-" + i})
<input type="submit" value="Save" class="btn save" name="Command" />
</div>
}
}
控制器
[HttpPost]
[ValidateAntiForgeryToken]
[ActionName("SaveItem")]
public ActionResult SaveItem(MyItemViewModel mivm, string Command)
{
if (ModelState.IsValid)
{
#do some logic
}
// Return a newly populated MyViewModel with updated mivm.
var model = PopulateMyParentViewModel();
return PartialView("_MyPartialView", model);
}
private MyParentViewModel PopulateMyParentViewModel()
{
List<MyItemsViewModel> lstItems = new List<MyItemsViewModel>();
foreach (var item in enity.items.OrderBy(p => p.ID).ToList())
{
var ExistingItem = new MyItemViewModel
{
ID = item.ID,
TheName = item.TheName
};
lstItems.Add(MyItemViewModel);
}
MyParentViewModel.Items = lstItems;
return MyParentViewModel
}
我不确定你的代码中的 "pensions" 是什么,我猜它是一个 div 围绕着循环生成的每个表单?如果是这样,您的问题如下:
首先,如果 none 个项目是相互关联的(也就是说,触发一个项目的一些更新会导致另一个项目也被更新),你不应该浪费资源到将整个列表发送回浏览器 - 考虑只发送回受影响的更新显示 "record" 并用响应更新相应的项目。
导致项目重复的原因:
AjaxOptions
class 有一个 属性InsertionMode
,我记得它的默认值是InsertionMode = InsertionMode.InsertAfter
,这会导致出现重复项。为什么?您向服务器发送 Ajax 请求,服务器发回由整个列表填充的 HTML 片段,而不是仅一个项目,然后浏览器将该片段附加到现有记录。
解决方案
如果您重新制作项目以仅发回一条记录而不是所有记录,则只需添加一个唯一标识(= 具有唯一的
id
属性)<div>
using(...)
块周围的元素,将InsertionMode
设置为InsertionMode=InsertionMode.Replace
并将UpdateTargetId
属性 设置为<div>
的id
,像这样:<div id="record-@i"> @using (Ajax.BeginForm("SaveItem", "Controller", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "record-" + i.ToString(), InsertionMode = InsertionMode.Replace })) { // ... } </div>
这将导致 Ajax 响应替换发送请求的容器的 contents(由 contents 我的意思是包装元素本身没有被替换!您需要使用
InsertionMode.ReplaceWith
来实现该行为)。如果你弄错了这一点,你最终会在宿主容器中倾倒大量的嵌套元素,可能会破坏使用非常特定选择器的脚本 and/or 样式。如果你坚持发送整个项目集合,那么只需用
<div>
标记包裹 for 循环,给它一个id
,设置InsertionMode
到InsertionMode=InsertionMode.Replace
,UpdateTargetId
属性 到<div>
的id
。
--------更新------------
回答您的评论:
假设您有一个视图 - 为了简单起见 - 以表格格式显示记录。然后,结果如下所示:
Name | Age | Salary | Hide
------------------------------------
Peter | 32 | k | Hide button
Eva | 28 | k | Hide button
假设您对 Ajax 请求所做的更改是,每当您隐藏记录时,您都会发回单个 table 行,其中包含一个按钮,现在显示 'show' 而不是 'hide' 以及关于属于触发请求的按钮的人的相同数据。 然后,当你直接(=非ajax,当你直接导航到页面)请求时,你这样做:
@model PeopleCollection
...
<table>
<thead>
<tr>
<td>Name</td>
<td>Age</td>
<td>Salary</td>
<td>Hide</td>
</tr>
</thead>
<tbody>
@foreach (Person record in Model) {
@Html.Partial("TableRow_Partial",record)
}
</tbody>
"TableRow_Partial" 视图如下所示:
@model Person
@{
AjaxOptions opts = new AjaxOptions() {
UpdateTargetId = "person-row-" + Model.Id.ToString(),
InsertionMode = InsertionMode.ReplaceWith
// etc
};
}
...
<tr id="person-row-@Model.Id">
<td>@Model.Name</td>
<td>@Model.Age</td>
<td>@Model.Salary</td>
<td>
<!-- configure the BeginForm(..) call with the action name and a routeValue anonymous object as "new { id = Model.Id}" -->
@using (Ajax.BeginForm(...)) {
<input type="submit" value="@(Model.IsHidden ? "Show" : "Hide")" />
}
</td>
</tr>
这将简单地将循环的构造提取到单独的局部视图中。处理您的 Ajax-请求的操作方法将简单地 return 这个视图,其中填充了您已更新的一条记录,如下所示:
public ActionResult UpdateRecord(Int64 id)
{
var _record = Repository.Get(id);
// logic to hide and update record
return PartialView("TableRow_Partial",_record);
}
为此,您必须将 InsertionMode
设置为 InsertionMode.ReplaceWith
。所有这一切都是当你发送一个 ajax 请求只用一条记录做某事时,你只刷新一个项目 - 触发 ajax 调用的那个,然后你只需 return新行(一行!)并将旧行替换为新行。
如果你真的想在每次对记录做某事时都将整个项目集合发送回客户端,只需将整个呈现的 table 发送到客户端并用旧的替换新的以类似的方式。