56

この質問は、 Why is my DisplayFor not looping through my IEnumerable<DateTime>?のフォローアップです。


ささっとリフレッシュ。

いつ:

  • モデルにはタイプのプロパティがありますIEnumerable<T>
  • Html.EditorFor()ラムダ式のみを受け入れるオーバーロードを使用して、このプロパティを渡します
  • TViews/Shared/EditorTemplates の下にタイプのエディター テンプレートがあります。

次に、MVC エンジンは、列挙可能なシーケンス内の各項目のエディター テンプレートを自動的に呼び出し、結果のリストを生成します。

たとえば、Orderプロパティを持つモデル クラスがある場合Lines:

public class Order
{
    public IEnumerable<OrderLine> Lines { get; set; }
}

public class OrderLine
{
    public string Prop1 { get; set; }
    public int Prop2 { get; set; }
}

ビュー Views/Shared/EditorTemplates/OrderLine.cshtml があります。

@model TestEditorFor.Models.OrderLine

@Html.EditorFor(m => m.Prop1)
@Html.EditorFor(m => m.Prop2)

@Html.EditorFor(m => m.Lines)次に、最上位ビューから呼び出すと、1 つだけでなく、各注文行のテキスト ボックスを含むページが表示されます。


ただし、リンクされた質問でわかるように、これは の特定のオーバーロードを使用する場合にのみ機能しますEditorFor。テンプレート名を指定すると (クラスにちなんで名付けられていないテンプレートを使用するためにOrderLine)、自動シーケンス処理は行われず、代わりに実行時エラーが発生します。

その時点で、カスタム テンプレートのモデルを として宣言しIEnumebrable<OrderLine>、何らかの方法でそのアイテムを手動で繰り返し処理して、すべてを出力する必要があります。

@foreach (var line in Model.Lines) {
    @Html.EditorFor(m => line)
}

そして、そこから問題が始まります。

この方法で生成された HTML コントロールは、すべて同じ ID と名前を持ちます。後でそれらを POST すると、モデル バインダーは の配列を作成できなくなりOrderLine、コントローラーの HttpPost メソッドで取得するモデル オブジェクトは になりますnull
これは、ラムダ式を見ると理にかなっています。実際には、構築中のオブジェクトがモデル内の元の場所にリンクされているわけではありません。

アイテムを反復処理するさまざまな方法を試しましたが、テンプレートのモデルを次のように再宣言し、次のようIList<T>に列挙することが唯一の方法のようforです。

@model IList<OrderLine>

@for (int i = 0; i < Model.Count(); i++)
{ 
    @Html.EditorFor(m => m[i].Prop1)
    @Html.EditorFor(m => m[i].Prop2)
}

次に、トップレベル ビューで:

@model TestEditorFor.Models.Order

@using (Html.BeginForm()) {
    @Html.EditorFor(m => m.Lines, "CustomTemplateName")
}

これにより、送信時にモデル バインダーによって適切に認識される適切な名前の HTML コントロールが提供されます。


これは機能しますが、非常に間違っているように感じます。

EditorForエンジンがモデル バインダーに適した HTML を生成できるようにするすべての論理リンクを保持しながら、カスタム エディター テンプレートを で使用する正しい慣用的な方法は何ですか?

4

5 に答える 5

35

Erik Funkenbusch との議論の結果、 MVC のソース コードを調べることになりましたが、それを行うには 2 つの優れた (正しい方法と慣用的な方法) 方法があるようです。

どちらもヘルパーに正しい html 名プレフィックスを提供することを含み、デフォルトの出力と同じ HTML を生成しますEditorFor

深くネストされたシナリオで動作することを確認するために、さらにテストを行います。

OrderLine次の例では、 classOrderLine.cshtmlとの 2 つのテンプレートが既にあるとしますDifferentOrderLine.cshtml


方法 1 - 中間テンプレートを使用するIEnumerable<T>

ヘルパー テンプレートを作成し、任意の名前 (例: "ManyDifferentOrderLines.cshtml") で保存します。

@model IEnumerable<OrderLine>

@{
    int i = 0;

    foreach (var line in Model)
    { 
        @Html.EditorFor(m => line, "DifferentOrderLine", "[" + i++ + "]")
    }
}

次に、メインの Order テンプレートから呼び出します。

@model Order

@Html.EditorFor(m => m.Lines, "ManyDifferentOrderLines")

方法 2 - 中間テンプレートなしIEnumerable<T>

メインの注文テンプレート:

@model Order

@{
    int i = 0;

    foreach (var line in Model.Lines)
    {
        @Html.EditorFor(m => line, "DifferentOrderLine", "Lines[" + i++ + "]")
    }
}
于 2014-10-09T17:02:09.603 に答える
4

@GSerg による回答で説明されているよりも、これを達成する簡単な方法はないようです。MVC チームがそれを行うためのより簡単な方法を考え出していないのは奇妙です。この拡張メソッドを作成して、少なくともある程度カプセル化しました。

public static MvcHtmlString EditorForEnumerable<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> expression, string templateName)
{
    var fieldName = html.NameFor(expression).ToString();
    var items = expression.Compile()(html.ViewData.Model);
    return new MvcHtmlString(string.Concat(items.Select((item, i) => html.EditorFor(m => item, templateName, fieldName + '[' + i + ']'))));
}
于 2016-09-06T09:19:03.917 に答える