web-dev-qa-db-ja.com

MVC3非順次インデックスとDefaultModelBinder

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は単に無視されます。

カスタムインデックス( リストへのモデルバインディング )を生成するための多数のソリューションを見てきましたが、それらはすべて、以前のバージョンのMVCをターゲットにしているように見え、ほとんどは少し「強めの」IMOです。

何か不足していますか?

41
mindlessgoods

私はこれを機能させています、参照記事で説明されているように、一般的なインデックス作成の非表示入力を追加することを忘れないでください:

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

お役に立てれば

72
Bassam Mehanni

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

  2. それはより簡単で、それ以上の魔法の文字列を必要としません。

  3. データ型に単一のEditorTemplate/DisplayTemplateを設定できます。リスト内のアイテムで使用されていない場合は、単純に何も行われません。

唯一の欠点は、バインドされているルートモデルが列挙可能な場合(つまり、アクションメソッド自体へのパラメーターであり、パラメーターオブジェクトグラフのより深い場所にあるプロパティではない)、最初の非順次インデックスでバインドが失敗することです。残念ながら、DefaultModelBinderの_.Index_機能は、非ルートオブジェクトに対してのみ機能します。このシナリオでは、上記のアプローチを使用する唯一のオプションが残っています。

5
Phil Degenhardt

あなたが参照した記事は古いもの(MVC2)ですが、私が知る限り、これはまだデフォルトのモデルバインダーを使用してバインドコレクションをモデル化するための事実上の方法です。

Bassamが言うように、非順次インデックス付けが必要な場合は、インデクサーを指定する必要があります。インデクサーは数値である必要はありません。

これには Steve SandersonのBeginCollectionItem Html Helper を使用します。インデクサーをGuidとして自動的に生成します。これは、コレクションアイテムのHTMLがシーケンシャルでない場合に、数値インデクサーを使用するよりも優れたアプローチだと思います。

4
danludwig

今週私はこれに苦労しており、バッサムの答えは私を正しい軌道に乗せるための鍵でした。数量フィールドを持つことができる在庫アイテムの動的リストがあります。彼らが選択したアイテムの数を知る必要がありましたが、アイテムのリストは1からnまでさまざまです。

結局、私の解決策はかなりシンプルでした。 2つのプロパティを持つItemVMと呼ばれるViewModelを作成しました。 ItemIDと数量。ポストアクションでは、これらのリストを受け入れます。インデックス作成がオンの場合、数量がnullであっても、すべてのアイテムが渡されます。サーバー側で検証して処理する必要がありますが、反復処理を行うと、この動的リストを処理するのは簡単です。

私の見解では、次のようなものを使用しています:

@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で機能します。これは、私のようにこのソリューションを探している人に役立つかもしれません。

2
Jason Conville

または、次の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 -
    }
1
SlimSjakie

私はより一般的なHTMLヘルパーを作成してしまいました:-

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)
1
Stephen Garside