您不需要显式索引平面数据。如果你认为你有
<input type='text' name='data' value='George' />
<input type='text' name='data' value='John' />
<input type='text' name='data' value='Paul' />
<input type='text' name='data' value='Ringo' />
然后在你的控制器中你可以使用
public ActionResult Create(string[] data)
{
//data should now be a string array of 4 elements
return RedirectToAction("Index");
}
要了解活页夹,基本上是向后工作。当您提交表单时,假设它正在发布到您的 Create 方法,模型绑定器会检查方法参数。它会看到你有一个字符串数组作为参数,它被称为data。它喜欢字符串,因为表单数据是作为字符串提交的。除了在表单集合中查找键为data 的元素外,它不需要做任何实际工作。所有匹配的项目都将添加到数组中并分配给您的参数。
之所以有效,是因为参数与表单元素具有同名。如果名称不匹配,您将得到 null,因为没有找到具有该键名的任何内容。
如果您使用强视图(具有显式模型的视图),那么您可以使用 MVC 助手为您生成这些视图,并且输入元素将被分配正确的名称以映射回您的对象。
例如,如果您有一个模型:
public class BandMembers
{
public string[] data {get; set;}
}
在您看来,您将其指定为模型并使用了适当的 HTML 助手,您的操作方法可能如下所示:
public ActionResult Create(BandMembers band)
{
//band now has a property called 'data' with 4 elements
return RedirectToAction("Index");
}
这应该会产生一个名为band 的实例化对象,它的属性names 有4 个值。这是因为模型绑定器看到一个名为 band 的参数与表单集合中的任何已知键都不匹配,意识到它是一个复杂对象(不是字符串、int、string[]、int[] 等)并检查它的成员。它看到这个对象有一个名为 data 的字符串数组,并且表单集合中有具有该名称的键。它收集值,将它们分配给 data 属性并将该对象分配给您的带参数。
现在您了解视图模型了!
*如果您在控制器中使用了 BandMembers 类但将其命名为data,则会收到警告,您将得到一个空值。这是因为模型绑定器在表单集合中找到键为 data 的项目,但无法弄清楚如何将它们从字符串转换为 BandMembers 对象。
编辑
关于您通过更深入的示例进行的编辑,这就是我想出的。
首先,我的模型是为了让我们在同一页面上。我创建了一个 FormData 对象,其中包含一个部分列表以充当对象的集合。
public class FormData
{
public List<Section> Sections { get; set; }
public FormData()
{
}
}
还有我的 Section.cs 类:
public class Section
{
public bool IsDeleted { get; set; }
public bool IsNew { get; set; }
public int Id { get; set; }
public int SectionOrder { get; set; }
public string Title { get; set; }
public string SubTitle { get; set; }
public Section()
{
}
}
在您的 Section 上使用 EditorTemplate 可以轻松呈现带有为您生成的索引的内容。我自己模拟了这个项目并验证了它是有效的。不幸的是,正如您所看到的,一旦您删除了一个项目,您的索引就会出现故障。那么你如何解决这个问题?当然,您可以去阅读索引并重写它们,或者 - 只是不要删除它们!我在模拟项目中所做的是在 Section 上添加一个名为 IsDeleted 的新属性并将其呈现为隐藏元素。在删除单击的 JavaScript 处理程序中,我隐藏了该行并将该行的 IsDeleted 输入的隐藏输入更新为“true”。当我提交表单时,我现在将拥有一个完整的集合以及一个方便的标志,让我知道我需要从我的模型中删除哪些行。
我创建了一个测试视图,该视图绑定到一个名为 FormData 的模型,其中包含一个列表。
@model MVCEditorTemplateDemo.Models.FormData
@using (Html.BeginForm())
{
<table id="section-container">
@Html.EditorFor(m => m.Sections)
</table>
@Ajax.ActionLink("Add Section", "GetNewSection", "Home", new AjaxOptions() { HttpMethod="POST", InsertionMode=InsertionMode.InsertAfter, UpdateTargetId="section-container" })
<input type="submit" value="Submit" />
}
是的,EditorFor 正在收集收藏!但是它怎么知道如何处理它呢?我在我的 Views/Home 中创建了一个名为 EditorTemplates 的文件夹(如果你想跨控制器使用它可以在 Shared 中),我在其中放置了一个名为 Section.cshtml 的局部视图。名称很重要——它应该与将要渲染的对象的名称相匹配。由于我的模型包含名为 Section 的对象,因此我的 EditorTemplate 也应称为 Section。
这是它的样子(EditorTemplates\Section.cshtml):
@model MVCEditorTemplateDemo.Models.Section
<tr>
<td><a href="#" class="edit-section"><span class="glyphicon glyphicon-question-sign"></span></a></td>
<td>@Html.TextBoxFor(m => Model.SectionOrder, new { @class = "form-control" })</td>
<td>@Html.TextBoxFor(m => Model.Title, new { @class = "form-control" })</td>
<td>@Html.TextBoxFor(m => Model.SubTitle, new { @class = "form-control" })</td>
<td>
@Html.HiddenFor(m => Model.Id)
@Html.HiddenFor(m => Model.IsNew)
@Html.HiddenFor(m => Model.IsDeleted)
<a href="#" class="delete-section"><span class="glyphicon glyphicon-remove"></span></a>
</td>
</tr>
为了满足您的要求,我尽量保持它与您所拥有的尽可能接近。当您计划动态添加或远程元素时,我通常不会使用表格。一些浏览器的表现不太好,更不用说我的设计师对渲染导致的并发症表有强烈的仇恨。
好的,现在您已经有了让 ASP.NET MVC 自动呈现您的项目并自动生成索引所需的东西。那么让我们看看如何删除该行。
回到我的测试视图,我添加了一个脚本部分,如下所示:
@section scripts
{
<script type="text/javascript">
$(function () {
$("table").on("click", ".delete-section", function() {
$(this).closest('tr').hide();
$(this).prev('input').val('true');
});
});
</script>
}
这非常有效。当用户单击删除按钮时,他们会立即获得 UI 反馈,表明该行已消失,当提交表单时,我将使用一个方便的属性来呈现我呈现的整个项目集合,让我知道我需要从中删除哪些项目我的数据存储。最好的部分是我不必迭代我的集合,我的所有索引都是自动生成的。
我对你的问题的回答到此结束。
但后来我很好奇我需要做什么才能创建新行。所以让我们回顾一下Test 视图和Ajax.Action 助手。您会立即注意到我正在指示浏览器执行 POST 请求。为什么?因为浏览器可以缓存 GET 请求以优化性能。通常您不会在意,因为您通常会为每个请求返回相同的 HTML,但由于我们需要包含特殊命名,因此我们的 HTML 实际上每次都是不同的(在我们的输入名称中包含索引)。其余的都是不言自明的。真正的诀窍是在服务器端——我们如何返回一个部分以通过正确的索引向该表添加一行?
不幸的是,该框架虽然擅长正常查找视图,但似乎在检查 EditorTemplates 和 DisplayTemplates 文件夹时失败了。因此,如果我们不使用模板,我们的 Action 会比通常情况下更脏。
public ActionResult GetNewSection()
{
var section = new Section() { IsNew = true };
FakedData.Sections.Add(section);
ViewData.TemplateInfo.HtmlFieldPrefix = string.Format("Sections[{0}]", FakedData.Sections.Count-1);
return PartialView("~/Views/Home/EditorTemplates/Section.cshtml", section);
}
好的,那么我们看到了什么?首先,我创建了一个新的 Section 对象,因为我需要它来渲染 EditorTemplate。我添加了第二个新属性 IsNew,但目前我实际上并没有对它做任何事情。我只是想要一种方便的方式来查看即将在我的 POST 方法中添加和删除的内容。
我将该新部分添加到我的数据存储 (FakedData)。您可以改为以另一种方式跟踪新请求的数量 - 只需确保每次单击“添加部分”链接时它都会增加。
现在是诀窍。由于我们要返回一个局部视图,它没有父模型的上下文。如果我们只返回只传入部分的模板,它就不会知道它是更大集合的一部分,也不会适当地生成名称。所以我们用 HtmlFieldPrefix 字段告诉它我们在哪里。我使用我的数据存储来跟踪正确的索引,但同样,这可能来自任何地方,如果您添加了 IsNew 属性,您将能够在提交时将新的(和未删除的)记录添加到您的存储中.
就像我说的那样,框架仅根据名称查找模板有点麻烦,因此我们必须给出它的路径才能正确返回。总体上没什么大不了的,但有点烦人。
现在我为 Test 视图添加了一个 POST 处理程序,并且我可靠地获得了删除、添加和总计数的项目计数。请记住,行可以是新的和已删除的!
[HttpPost]
public ActionResult Test(FormData form)
{
var sectionCount = form.Sections.Count();
var deletedCount = form.Sections.Count(i => i.IsDeleted);
var newItemCount = form.Sections.Count(i => i.IsNew);
form.Sections = form.Sections.Where(s => !s.IsDeleted).ToList();
FakedData = form;
return RedirectToAction("Test");
}
就是这样。我们有一个完整的端到端渲染你的集合与适当的索引,“删除”行,添加新行,我们不必破解模型绑定器,操纵名称或诉诸 JavaScript 黑客来重新编号我们提交的元素。
我渴望对此提供反馈。如果我没有看到更好的答案,这可能是我以后做此类事情时总是使用的路线。