簡単な答えは、for()
ループの代わりにループを使用することforeach()
です。何かのようなもの:
@for(var themeIndex = 0; themeIndex < Model.Theme.Count(); themeIndex++)
{
@Html.LabelFor(model => model.Theme[themeIndex])
@for(var productIndex=0; productIndex < Model.Theme[themeIndex].Products.Count(); productIndex++)
{
@Html.LabelFor(model=>model.Theme[themeIndex].Products[productIndex].name)
@for(var orderIndex=0; orderIndex < Model.Theme[themeIndex].Products[productIndex].Orders; orderIndex++)
{
@Html.TextBoxFor(model => model.Theme[themeIndex].Products[productIndex].Orders[orderIndex].Quantity)
@Html.TextAreaFor(model => model.Theme[themeIndex].Products[productIndex].Orders[orderIndex].Note)
@Html.EditorFor(model => model.Theme[themeIndex].Products[productIndex].Orders[orderIndex].DateRequestedDeliveryFor)
}
}
}
しかし、これはなぜこれが問題を修正するのかについての光沢があります。
この問題を解決する前に、少なくともおおまかに理解していることが3つあります。私がフレームワークを使い始めたとき、私はこれを長い間貨物養殖していたことを認めなければなりません。そして、本当に何が起こっているのかを理解するのにかなり時間がかかりました。
これらの3つは次のとおりです。
LabelFor
と他の...For
ヘルパーはMVCでどのように機能しますか?
- 式ツリーとは何ですか?
- Model Binderはどのように機能しますか?
これらの3つの概念はすべて、答えを得るために互いにリンクしています。
LabelFor
と他の...For
ヘルパーはMVCでどのように機能しますか?
だから、あなたが使ってきたHtmlHelper<T>
ための拡張機能LabelFor
とTextBoxFor
、他の、そしてあなたはおそらく、あなたがそれらを呼び出すとき、あなたは彼らにラムダを渡すことに気づき、それは魔法の一部のHTMLを生成します。しかし、どうやって?
したがって、最初に気付くのは、これらのヘルパーの署名です。最も単純なオーバーロードを見てみましょう
TextBoxFor
public static MvcHtmlString TextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
)
まず、これは強く型付けされたための拡張メソッドであるHtmlHelper
タイプで、<TModel>
。つまり、裏で何が起きているかを簡単に述べるために、かみそりがこのビューをレンダリングすると、クラスが生成されます。このクラスの内部にはHtmlHelper<TModel>
(プロパティとしてHtml
、つまりを使用できる理由として)のインスタンスが@Html...
あり、TModel
は@model
ステートメントで定義されている型です。したがって、あなたの場合、このビューTModel
を見ているときは常にのタイプになりViewModels.MyViewModels.Theme
ます。
さて、次の議論は少しトリッキーです。それでは呼び出しを見てみましょう
@Html.TextBoxFor(model=>model.SomeProperty);
少しラムダがあるように見えます。署名を推測した場合、この引数の型は単にであると考えるかもしれません。Func<TModel, TProperty>
ここTModel
で、はビューモデルTProperty
の型であり、プロパティの型として推測されます。
しかし、引数itの実際のタイプを見ると、それは正しくありませんExpression<Func<TModel, TProperty>>
。
したがって、通常はラムダを生成すると、コンパイラはラムダを取得し、それを他の関数と同様にMSILにコンパイルします(デリゲート、メソッドグループ、およびラムダは、単にコード参照であるため、ほぼ同じ意味で使用できます) 。)
ただし、コンパイラが型がであるExpression<>
ことを検出すると、ラムダをすぐにMSILにコンパイルするのではなく、式ツリーを生成します。
だから、一体何が表現ツリーなのか。さて、それは複雑ではありませんが、公園の散歩でもありません。msを引用するには:
| 式ツリーは、ツリーのようなデータ構造のコードを表します。各ノードは、式の呼び出しや、x <yなどのバイナリ演算などの式です。
簡単に言うと、式ツリーは「アクション」のコレクションとしての関数の表現です。
の場合model=>model.SomeProperty
、式ツリーにはノードがあり、「「モデル」から「一部のプロパティを取得」」と表示されます。
この式ツリーは、呼び出すことができる関数にコンパイルできますが、式ツリーである限り、ノードのコレクションにすぎません。
それで何が良いのでしょうか?
そうFunc<>
かAction<>
、あなたがそれらを持っていたら、彼らはかなりアトミックです。あなたが本当にできるのはInvoke()
彼らだけです、別名彼らに彼らがすることになっている仕事をするように言います。
Expression<Func<>>
一方、はアクションのコレクションを表し、追加、操作、訪問、またはコンパイルして呼び出すことができます。
なぜあなたは私にこれすべてを言っているのですか?
つまり、an Expression<>
が何であるかを理解したら、に戻ることができHtml.TextBoxFor
ます。テキストボックスをレンダリングするとき、それはあなたが与えているプロパティについていくつかのことを生成する必要があります。attributes
検証のためのプロパティのようなもの、特にこの場合、タグに何を付けるかを理解する必要があります<input>
。
これは、式ツリーを「ウォーク」して名前を作成することによって行われます。したがって、のような式の場合model=>model.SomeProperty
、要求されているプロパティを収集して構築し、式をウォークします<input name='SomeProperty'>
。
のようなより複雑な例ではmodel=>model.Foo.Bar.Baz.FooBar
、<input name="Foo.Bar.Baz.FooBar" value="[whatever FooBar is]" />
理にかなっていますか?ここでは、が行う作業だけでなくFunc<>
、それがどのように機能するかが重要です。
(LINQ to SQLのような他のフレームワークは、式ツリーを歩いて別の文法を構築することで同様のことを行うことに注意してください。この場合はSQLクエリです)
Model Binderはどのように機能しますか?
したがって、それが得られたら、モデルバインダーについて簡単に説明する必要があります。フォームが投稿されると、それは単にflatのようになり、
Dictionary<string, string>
ネストされたビューモデルが持っていたかもしれない階層構造が失われます。このキーと値のペアのコンボを受け取り、いくつかのプロパティを持つオブジェクトを再水和することを試みるのは、モデルバインダーの仕事です。これはどのように行うのですか?「キー」または投稿された入力の名前を使用して、それを推測しました。
フォームの投稿が次のようになっている場合
Foo.Bar.Baz.FooBar = Hello
そして、あなたはと呼ばれるモデルに投稿していますSomeViewModel
、そしてそれはヘルパーが最初にしたことの逆をします。「Foo」というプロパティを探します。次に、「Foo」から「Bar」というプロパティを探し、次に「Baz」を探します...というように...
最後に、値を解析して「FooBar」のタイプに変換し、「FooBar」に割り当てようとします。
ふ!!!!
そして出来上がり、あなたのモデルがあります。作成したModel Binderのインスタンスは、要求されたアクションに渡されます。
したがって、Html.[Type]For()
ヘルパーは式を必要とするため、ソリューションは機能しません。そして、あなたは彼らに価値を与えているだけです。その値のコンテキストが何であるかはわかりません。また、それをどう処理するかわかりません。
今、一部の人々は、パーシャルを使用してレンダリングすることを提案しました。これで理論的にはこれでうまくいきますが、おそらくあなたが期待する方法ではありません。パーシャルをレンダリングするTModel
と、別のビューコンテキストにいるため、のタイプが変更されます。これは、より短い式でプロパティを説明できることを意味します。また、ヘルパーが式の名前を生成するときに、浅くなります。コンテキスト全体ではなく、指定された式に基づいてのみ生成されます。
それで、(前の例から)「Baz」をレンダリングしたパーシャルがあるとしましょう。その部分の中では、あなたはただ言うことができます:
@Html.TextBoxFor(model=>model.FooBar)
のではなく
@Html.TextBoxFor(model=>model.Foo.Bar.Baz.FooBar)
つまり、次のような入力タグが生成されます。
<input name="FooBar" />
これは、深くネストされた大きなViewModelを必要とするアクションにこのフォームを投稿する場合、FooBar
off と呼ばれるプロパティをハイドレートしようとしTModel
ます。せいぜいそこにはなく、最悪の場合、まったく別のものです。Baz
ルートモデルではなくを受け入れる特定のアクションに投稿した場合、これはうまく機能します!実際、パーシャルはビューコンテキストを変更するための良い方法です。たとえば、複数のフォームを持つページがあり、すべてが異なるアクションに投稿する場合、それぞれにパーシャルをレンダリングすることは素晴らしいアイデアです。
これをすべて取得したらExpression<>
、プログラムを使用して拡張し、他のきちんとしたことを行うことで、で本当に面白いことを始めることができます。私はそのいずれにも入りません。しかし、うまくいけば、これにより、舞台裏で何が起こっているのか、そしてなぜ物事が彼らのように行動しているのかについての理解が深まります。
@
すべてforeach
の前に持ってはいけませんか?ラムダHtml.EditorFor
(Html.EditorFor(m => m.Note)
たとえば)と残りのメソッドも含めるべきではありませんか?誤解しているかもしれませんが、実際のコードを貼り付けてもらえますか?私はMVCにかなり慣れていませんが、部分的なビューやエディター(それが名前なら?)を使えば、かなり簡単に解決できます。