MVC 3.0中的默认模型绑定器是否能够处理非顺序索引(对于简单和复杂模型类型)?我遇到的帖子表明应该这样做,但是在我的测试中它似乎没有。
给出回发值:
items[0].Id = 10
items[0].Name = "Some Item"
items[1].Id = 3
items[1].Name = "Some Item"
items[4].Id = 6
items[4].Name = "Some Item"
控制器方法:
public ActionResult(IList<MyItem> items) { ... }
加载的唯一值是0和1项;第4项被忽略了。
我已经看到了许多生成自定义索引的解决方案(Model Binding to a List),但是它们似乎都针对以前版本的MVC,而且大多数都是有点“严厉”的IMO。
我错过了什么吗?
答案 0 :(得分:67)
我有这个工作,您必须记住添加一个常见的索引隐藏输入,如您引用的文章中所述:
name = Items.Index
的隐藏输入是关键部分
<input type="hidden" name="Items.Index" value="0" />
<input type="text" name="Items[0].Name" value="someValue1" />
<input type="hidden" name="Items.Index" value="1" />
<input type="text" name="Items[1].Name" value="someValue2" />
<input type="hidden" name="Items.Index" value="3" />
<input type="text" name="Items[3].Name" value="someValue3" />
<input type="hidden" name="Items.Index" value="4" />
<input type="text" name="Items[4].Name" value="someValue4" />
希望这会有所帮助
答案 1 :(得分:5)
这个从Steve Sanderson的方法派生出来的辅助方法更简单,可以用来锚定集合中的任何项目,它似乎与MVC模型绑定一起使用。
public static IHtmlString AnchorIndex(this HtmlHelper html)
{
var htmlFieldPrefix = html.ViewData.TemplateInfo.HtmlFieldPrefix;
var m = Regex.Match(htmlFieldPrefix, @"([\w]+)\[([\w]*)\]");
if (m.Success && m.Groups.Count == 3)
return
MvcHtmlString.Create(
string.Format(
"<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />",
m.Groups[1].Value, m.Groups[2].Value));
return null;
}
E.g。只需在EditorTemplate或您将生成输入的任何其他位置调用它,如下所示,以生成索引锚定隐藏变量(如果适用)。
@model SomeViewModel
@Html.AnchorIndex()
@Html.TextBoxFor(m => m.Name)
... etc.
我认为它比史蒂夫桑德森的方法有一些优势。
它适用于EditorFor和其他内置机制来处理枚举。因此,如果Items
是视图模型上的IEnumerable<T>
属性,则以下内容将按预期工作:
<ul id="editorRows" class="list-unstyled">
@Html.EditorFor(m => m.Items)
@* Each item will correctly anchor allowing for dynamic add/deletion via Javascript *@
</ul>
它更简单,不需要任何魔术字符串。
对于数据类型,您可以使用单个EditorTemplate / DisplayTemplate,如果未在列表中的项目上使用,则它将只是无操作。
唯一的缺点是,如果绑定的根模型是可枚举的(即Action方法本身的参数,而不仅仅是参数对象图中更深处的属性),则绑定将在第一个非顺序时失败指数。不幸的是,DefaultModelBinder的.Index
功能仅适用于非root对象。在这种情况下,您唯一的选择仍然是使用上述方法。
答案 2 :(得分:4)
您引用的文章是旧文章(MVC2),但据我所知,这仍然是使用默认模型绑定器对绑定集合建模的实际方法。
如果你想要非顺序索引,就像Bassam所说,你需要指定一个索引器。索引器不需要是数字。
我们使用Steve Sanderson's BeginCollectionItem Html Helper。它会自动生成索引器作为Guid。我认为当您的集合项HTML是非顺序的时,这是比使用数字索引器更好的方法。
答案 3 :(得分:2)
本周我正在努力解决这个问题,Bassam的答案是让我走上正轨的关键。我有一个库存项目的动态列表,可以有一个数量字段。我需要知道他们选择了多少项,但项目列表可以从1到 n 不等。
我的解决方案最终相当简单。我创建了一个名为ItemVM的ViewModel,它有两个属性。 ItemID和数量。在帖子中,我接受了这些列表。使用索引,所有项目都会被传递..即使数量为空。你必须验证并处理它的服务器端,但是通过迭代处理这个动态列表是微不足道的。
在我看来,我使用的是这样的东西:
@foreach (Item item in Items)
{
<input type="hidden" name="OrderItems.Index" value="@item.ItemID" />
<input type="hidden" name="OrderItems[@item.ItemID].ItemID" value="@item.ItemID" />
<input type="number" name="OrderItems[@item.ItemID].Quantity" />
}
这给了我一个基于0的索引的List,但是控制器中的迭代从一个新的强类型模型中提取所有必要的数据。
public ActionResult Marketing(List<ItemVM> OrderItems)
...
foreach (ItemVM itemVM in OrderItems)
{
OrderItem item = new OrderItem();
item.ItemID = Convert.ToInt16(itemVM.ItemID);
item.Quantity = Convert.ToInt16(itemVM.Quantity);
if (item.Quantity > 0)
{
order.Items.Add(item);
}
}
然后,您将得到一个数量大于0的项目集合以及项目ID。
这项技术在Visual Studio 2015中使用EF 6的MVC 5中工作。也许这将有助于像我一样搜索此解决方案的人。
答案 4 :(得分:1)
或者使用此javascript函数来修复索引:(显然替换EntityName和FieldName)
function fixIndexing() {
var tableRows = $('#tblMyEntities tbody tr');
for (x = 0; x < tableRows.length; x++) {
tableRows.eq(x).attr('data-index', x);
tableRows.eq(x).children('td:nth-child(1)').children('input:first').attr('name', 'EntityName[' + x + "].FieldName1");
tableRows.eq(x).children('td:nth-child(2)').children('input:first').attr('name', 'EntityName[' + x + "].FieldName2");
tableRows.eq(x).children('td:nth-child(3)').children('input:first').attr('name', 'EntityName[' + x + "].FieldName3");
}
return true; //- Submit Form -
}
答案 5 :(得分:1)
我最终制作了一个更通用的HTML Helper: -
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;
namespace Wallboards.Web.Helpers
{
/// <summary>
/// Hidden Index Html Helper
/// </summary>
public static class HiddenIndexHtmlHelper
{
/// <summary>
/// Hiddens the index for.
/// </summary>
/// <typeparam name="TModel">The type of the model.</typeparam>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <param name="htmlHelper">The HTML helper.</param>
/// <param name="expression">The expression.</param>
/// <param name="index">The Index</param>
/// <returns>Returns Hidden Index For</returns>
public static MvcHtmlString HiddenIndexFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, int index)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var propName = metadata.PropertyName;
StringBuilder sb = new StringBuilder();
sb.AppendFormat("<input type=\"hidden\" name=\"{0}.Index\" autocomplete=\"off\" value=\"{1}\" />", propName, index);
return MvcHtmlString.Create(sb.ToString());
}
}
}
然后将它包含在Razor视图中list元素的每次迭代中: -
@Html.HiddenIndexFor(m => m.ExistingWallboardMessages, i)