【问题标题】:MVC3 Non-Sequential Indices and DefaultModelBinderMVC3 非顺序索引和 DefaultModelBinder
【发布时间】:2011-12-22 00:49:11
【问题描述】:

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。

我错过了什么吗?

【问题讨论】:

    标签: asp.net-mvc-3 modelbinders defaultmodelbinder


    【解决方案1】:

    我有这个工作,你必须记住添加一个公共索引隐藏输入,如你引用的文章中所述:

    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" />
    

    希望对你有帮助

    【讨论】:

    • 我希望避免这种方法。我的手指交叉,默认模型绑定器将简单地解决丢失的索引本身。必须有一个原因(也许是更复杂的情况?)必须显式指定索引。无论哪种方式,感谢您的快速回复和示例代码。
    • 天哪!这只是让返回列表变得更加容易,而不是为 (i++) 做荒谬的事情,我可以只使用索引上的主键或任何其他 ID,并且列表返回得非常好,并且是强类型的。这些黑暗的,隐藏的秘密。这让我很快乐! +1 +啤酒!!!
    • @Yablargo,这可能是ModelState 功能。您在发布操作中使用它吗?
    • @ps2goat 我不太确定这条评论,我将删除它。从那以后,我一直在使用此功能,并且无法真正说出我当时遇到的问题。
    • 是否有客户端框架(如 Knockout)可以为DefaultModelBinder 生成和管理这些索引器?
    【解决方案2】:

    这种源自 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;
    }
    

    例如只需在 EditorTemplate 中调用它,或者在您可以生成输入的任何其他位置调用它,如下所示以生成索引锚定隐藏变量(如果适用)。

    @model SomeViewModel
    @Html.AnchorIndex()
    @Html.TextBoxFor(m => m.Name)
    ... etc.
    

    我认为它比 Steve Sanderson 的方法有一些优势。

    1. 它与 EditorFor 和其他用于处理枚举的内置机制一起使用。因此,如果 Items 是视图模型上的 IEnumerable&lt;T&gt; 属性,则以下内容按预期工作:

      <ul id="editorRows" class="list-unstyled"> @Html.EditorFor(m => m.Items) @* Each item will correctly anchor allowing for dynamic add/deletion via Javascript *@ </ul>

    2. 更简单,不需要更多的魔法字符串。

    3. 您可以为一个数据类型设置一个 EditorTemplate/DisplayTemplate,如果不用于列表中的某个项目,它将只是无操作。

    唯一的缺点是,如果被绑定的根模型是可枚举的(即 Action 方法本身的参数,而不仅仅是参数对象图中某个更深处的属性),则绑定将在第一个非顺序时失败指数。不幸的是,DefaultModelBinder 的.Index 功能仅适用于非根对象。在这种情况下,您唯一的选择仍然是使用上述方法。

    【讨论】:

    • 非常感谢这位菲尔。我非常致力于将 EditorFor 与 enumerables 一起使用,这就像一个魅力!
    【解决方案3】:

    您引用的文章是一篇旧文章(MVC2),但据我所知,这仍然是使用默认模型绑定器对绑定集合进行建模的事实上的方式。

    如果你想要非顺序索引,就像 Bassam 说的那样,你需要指定一个索引器。索引器不需要是数字的。

    我们为此使用Steve Sanderson's BeginCollectionItem Html Helper。它会自动将索引器生成为 Guid。我认为当您的集合项 HTML 不连续时,这比使用数字索引器更好。

    【讨论】:

    • 我遇到了同一篇文章,它确实解决了我所描述的问题。如上所述,我希望默认模型绑定器能够在内部处理这种情况,并且实际上不需要 BeginCollectionItem 帮助器。感谢您的回复!
    【解决方案4】:

    这周我一直在努力解决这个问题,而巴萨姆的回答是让我走上正轨的关键。我有一个可以有数量字段的库存项目的动态列表。我需要知道他们选择了多少项目,除了项目列表可以从 1 到 n

    最后我的解决方案相当简单。我创建了一个名为 ItemVM 的 ViewModel,它有两个属性。项目 ID 和数量。在发布操作中,我接受了这些列表。启用索引后,所有项目都会通过..即使数量为空。您必须在服务器端验证和处理它,但是通过迭代处理这个动态列表很简单。

    在我看来,我正在使用这样的东西:

    @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 的索引的列表,但控制器中的迭代会从新的强类型模型中提取所有必要的数据。

    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 中有效。也许这会帮助像我一样搜索此解决方案的人。

    【讨论】:

    • 你能提供完整的例子吗?我有非常相似的情况
    【解决方案5】:

    或者使用这个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 -
        }
    

    【讨论】:

      【解决方案6】:

      我最终制作了一个更通用的 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 视图中列表元素的每次迭代中:-

      @Html.HiddenIndexFor(m => m.ExistingWallboardMessages, i)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-18
        • 2018-07-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多