よくあるシナリオを想像してください。これは私が遭遇していることのシンプルなバージョンです。 実は私の場合、さらに2~3層の入れ子になっているのですが...。
しかし、これはシナリオです
テーマが含まれるリスト<カテゴリー>; カテゴリーに含まれるもの リスト<プロダクト>; 製品に含まれるリスト<注文>;
私のコントローラは、テーマ、そのテーマのすべてのカテゴリ、カテゴリ内の商品とその注文を完全に入力されたテーマを提供します。
受注コレクションには、編集可能でなければならない Quantity というプロパティがあります(他にもたくさんあります)。
@model ViewModels.MyViewModels.Theme
@Html.LabelFor(Model.Theme.name)
@foreach (var category in Model.Theme)
{
@Html.LabelFor(category.name)
@foreach(var product in theme.Products)
{
@Html.LabelFor(product.name)
@foreach(var order in product.Orders)
{
@Html.TextBoxFor(order.Quantity)
@Html.TextAreaFor(order.Note)
@Html.EditorFor(order.DateRequestedDeliveryFor)
}
}
}
代わりにlambdaを使用すると、foreachループ内のオブジェクトではなく、トップのModelオブジェクト、quot;Theme"への参照のみを取得するようです。
私がやろうとしていることは、果たして可能なのだろうか。それとも、可能なことを過大評価したり、誤解したりしているのだろうか。
上記で、TextboxFor、EditorForなどでエラーが発生します。
CS0411:メソッドの型引数 'System.Web.Mvc.Html.InputExtensions.TextBoxFor<TModel,TProperty>(System.Web.Mvc.HtmlHelper
、 System.Linq.Expressions.Expression<System.Func<TModel,TProperty>)'; は、使い方から推測することができません。型引数を指定してみてください 明示した。
ありがとうございます。
簡単な答えは、「foreach()」ループの代わりに「for()」ループを使用することです。 のようなもの:
@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で働いています? 式ツリーとは何ですか? *モデルバインダーのしくみ?
これらの3つの概念すべてがリンクして答えを得ます。
LabelFor
と他のはどうですか。..For
ヘルパーはMVCで働いています?
---。
したがって、「LabelFor」や「TextBoxFor」などの「HtmlHelper< T>」拡張機能を使用しました。 それらを呼び出すと、ラムダを渡し、それが魔法のように生成することに気づいたでしょう。 いくつかのhtml。 しかし、どうやって?
したがって、最初に気づくのは、これらのヘルパーの署名です。 最も単純な過負荷を見てみましょう。
TextBoxFor
。
public static MvcHtmlString TextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
)
まず、これは強くタイプされた < TModel>
の HtmlHelper
の拡張方法です。 だから、単純に。
かみそりがこのビューをレンダリングすると、クラスが生成されます。
このクラスの内部には、 HtmlHelper< TModel>
のインスタンスがあります(プロパティ Html
として、@ Htmlを使用できる理由です)。..
)、。
ここで、「TModel」は「@ model」ステートメントで定義されたタイプです。 したがって、このビュー「TModel」を見ていると。
常に「ViewModels.MyViewModels.Theme」タイプになります。
さて、次の議論は少しトリッキーです。 それでは、呼び出しを見てみましょう。
@Html.TextBoxFor(model=>model.SomeProperty);
ラムダが少しあるようです。署名を推測すると、そのタイプが考えられるかもしれません。 この引数は単に「Func< TModel、TProperty>」であり、「TModel」はビューモデルのタイプであり、「TProperty」です。 プロパティのタイプとして推測されます。
しかし、それはまったく正しくありません。引数の実際のタイプを見ると、その Expression< Func< TModel、TProperty>>
。
したがって、通常ラムダを生成すると、コンパイラーはラムダを取り出し、他のラムダと同様にMSILにコンパイルします。 機能(デリゲート、メソッドグループ、ラムダをほぼ互換性があるために使用できるのはこのためです。 コード参照。)。
ただし、コンパイラーがタイプが「Expression<>」であることを確認した場合、ラムダをすぐにMSILにコンパイルするのではなく、ランダを生成します。 表現ツリー。!
Expression Treeとは何ですか? ---。
つまり、一体何が表現ツリーなのか。 まあ、それは複雑ではありませんが、公園の散歩でもありません。 msを引用するには:
|式ツリーは、ツリーのようなデータ構造のコードを表します。各ノードは式です。たとえば、メソッド呼び出しやx<などのバイナリ演算です。 y。
簡単に言えば、表現ツリーは「アクション」のコレクションとしての関数の表現です。
model => model.SomeProperty
の場合、式ツリーには、「 'model'から 'Some Property'を取得する」というノードが含まれます。
この式ツリーは、呼び出すことができる関数に「コンパイル」できますが、式ツリーである限り、ノードのコレクションにすぎません。
それで、それは何のために良いのでしょうか? ---。
したがって、「Func<>」または「Action<>」を使用すると、それらはかなり原子的です。 あなたが本当にできることは、それらを Invoke()
、別名に彼らに伝えることです。
彼らがすることになっている仕事をしてください。
一方、「Expression< Func<>>」は、追加、操作、visited、またはcomplary.
では、なぜあなたは私にこれをすべて言っているのですか? ---。
したがって、「Expression<>」が何であるかを理解することで、「Html.TextBoxFor」に戻ることができます。 テキストボックスをレンダリングするときは、必要です。
あなたが与えているプロパティについていくつかのことを生成します。 検証のためのプロパティの「属性」のようなもの、特に。
この場合、 < input>
タグに name *するものを理解する必要があります。
これは、式ツリーを「ウォーキング」して名前を付けることによって行われます。 したがって、「model => model.SomeProperty」のような式の場合、式が歩きます。
求めているプロパティを収集し、 < input name = 'SomeProperty'>
を作成します。
model => model.Foo.Bar.Baz.FooBar
のようなより複雑な例では、< input name = "Foo.Bar.Baz.FooBar" value = "[fooBar is]を生成する可能性があります。 "/>
。
理にかなっている? Func<>
が行う作業だけでなく、その作業の方法もここで重要です。
(LINQからSQLなどの他のフレームワークは、式ツリーを歩いて別の文法を作成することで同様のことを行うことに注意してください。この場合はSQLクエリです)。
モデルバインダーはどのように機能しますか? ---。
それがわかったら、モデルバインダーについて簡単に説明する必要があります。 フォームが投稿されると、それは単にフラットのようなものです。
Dictionary< string、string>
、ネストされたビューモデルが持っていた階層構造を失いました。 それは。
このキーと値のペアのコンボを取り、いくつかのプロパティを持つオブジェクトを再水和しようとするバインダーの仕事をモデル化します。 それはどうしますか。
この? 投稿された入力の「キー」または名前を使用して、それを推測しました。
したがって、フォーム投稿は次のようになります。
Foo.Bar.Baz.FooBar = Hello
そして、「SomeViewModel」と呼ばれるモデルに投稿すると、ヘルパーが最初に行ったことの逆になります。 探します。 「Foo」と呼ばれるプロパティ。 次に、「Foo」から「Bar」と呼ばれるプロパティを探し、「Baz」を探します。.. 等々。..
最後に、値を「FooBar」のタイプに分割して、「FooBar」に割り当てようとします。
そして出来上がり、あなたはあなたのモデルを持っています。 構築されたばかりのモデルバインダーのインスタンスは、要求されたアクションに渡されます。
---。
Htmlのため、ソリューションは機能しません。[Type] For()
ヘルパーには式が必要です。 そして、あなたは彼らに価値を与えているだけです。 わかりません。
その値のコンテキストは何ですか、そしてそれはそれをどうするかわかりません。
現在、一部の人々はパーシャルを使ってレンダリングすることを提案しました。 これで理論的には機能しますが、おそらく期待どおりには機能しません。 部分レンダリングを行うと、ビューコンテキストが異なるため、「TModel」のタイプが変更されます。 これはあなたが説明できることを意味します。 短い表現であなたの財産。 また、ヘルパーが式の名前を生成すると、浅くなります。 それ。 (コンテキスト全体ではなく)与えられた式に基づいてのみ生成されます。
では、(以前の例から)「Baz」をレンダリングした部分があったとします。 その部分の中であなたはただ言うことができます:
@Html.TextBoxFor(model=>model.FooBar)
むしろ。
@Html.TextBoxFor(model=>model.Foo.Bar.Baz.FooBar)
つまり、次のような入力タグが生成されます。
<input name="FooBar" />
これは、このフォームを、深くネストされた大きなViewModelを期待しているアクションに投稿する場合、プロパティに水分を補給しようとします。 「TModel」から「FooBar」と呼ばれる。 せいぜいそこにはなく、最悪の場合、まったく別のものです。 ルートモデルではなく、「Baz」を受け入れている特定のアクションに投稿している場合、これは非常に役立ちます。! 実際、パーシャルはビューのコンテキストを変更する良い方法です。たとえば、すべてが異なるアクションに投稿する複数のフォームのページがある場合、それぞれにパーシャルをレンダリングすることは素晴らしいアイデアです。
---。
これらすべてを取得したら、プログラム的に拡張して実行することにより、「Expression<>」で本当に興味深いことを始めることができます。 彼らと一緒に他のきちんとしたもの。 私はそれのいずれにも入りません。 しかし、うまくいけば、これはそうなるでしょう。 舞台裏で何が起こっているのか、そしてなぜ物事が現状のまま機能しているのかをよりよく理解してください。
コントローラのビューフォルダにEditorTemplates"というディレクトリを作成し、ネストしたエンティティ(エンティティクラス名)ごとに別々のビューを作成する必要があるのです。
メインビュー.
@model ViewModels.MyViewModels.Theme
@Html.LabelFor(Model.Theme.name)
@Html.EditorFor(Model.Theme.Categories)
カテゴリビュー(/MyController/EditorTemplates/Category.cshtml) :
@model ViewModels.MyViewModels.Category
@Html.LabelFor(Model.Name)
@Html.EditorFor(Model.Products)
商品ビュー(/MyController/EditorTemplates/Product.cshtml) :
@model ViewModels.MyViewModels.Product
@Html.LabelFor(Model.Name)
@Html.EditorFor(Model.Orders)
云々
こうすることで、Html.EditorForヘルパーが要素名を順番に生成してくれるので、投稿されたThemeエンティティを全体として取り出す際に、これ以上の問題は発生しません。
例えば、Category'のモデルタイプはIEnumerable<Category'で、これにModel.Themeを渡します。 Product'のパーシャルはIEnumerable
それが正しい方法なのかどうかはわかりませんが、知りたいところです。
EDIT
この回答を投稿してから、私はEditorTemplatesを使い、繰り返し入力グループやアイテムを処理する最も簡単な方法であると感じました。 また、バリデーションメッセージの問題や、フォーム送信やモデルバインディングの問題もすべて自動的に処理されます。
バインドされたモデルのビュー内でforeachループを使用している場合。 ... モデルはリストされた形式になっているはずです。
つまり
@model IEnumerable<ViewModels.MyViewModels>
@{
if (Model.Count() > 0)
{
@Html.DisplayFor(modelItem => Model.Theme.FirstOrDefault().name)
@foreach (var theme in Model.Theme)
{
@Html.DisplayFor(modelItem => theme.name)
@foreach(var product in theme.Products)
{
@Html.DisplayFor(modelItem => product.name)
@foreach(var order in product.Orders)
{
@Html.TextBoxFor(modelItem => order.Quantity)
@Html.TextAreaFor(modelItem => order.Note)
@Html.EditorFor(modelItem => order.DateRequestedDeliveryFor)
}
}
}
}else{
<span>No Theam avaiable</span>
}
}
エラーから明らかです。
quot;For"が付加されたHtmlHelpersは、パラメータとしてラムダ式を想定しています。
値を直接渡す場合は、Normalを使用するのがよいでしょう。
たとえば
TextboxFor(...)の代わりにTextbox()を使用します。
TextboxForの構文は、Html.TextBoxFor(m=>m.Property)のようになります。
あなたのシナリオでは、基本的なforループを使用することができ、使用するインデックスを与えることができます。
@for(int i=0;i<Model.Theme.Count;i++)
{
@Html.LabelFor(m=>m.Theme[i].name)
@for(int j=0;j<Model.Theme[i].Products.Count;j++) )
{
@Html.LabelFor(m=>m.Theme[i].Products[j].name)
@for(int k=0;k<Model.Theme[i].Products[j].Orders.Count;k++)
{
@Html.TextBoxFor(m=>Model.Theme[i].Products[j].Orders[k].Quantity)
@Html.TextAreaFor(m=>Model.Theme[i].Products[j].Orders[k].Note)
@Html.EditorFor(m=>Model.Theme[i].Products[j].Orders[k].DateRequestedDeliveryFor)
}
}
}