[解決済み] MVC Razor ビューのネストされたforeachのモデル
質問
よくあるシナリオを想像してください。これは私が遭遇していることの単純化したバージョンです。 私は実際に、私の上にさらにネストした 2 つのレイヤーを持っています......。
しかし、これはシナリオです。
テーマにはリストが含まれる カテゴリがリストを含む 製品に含まれるリスト
私のコントローラは、そのテーマのためのすべてのカテゴリ、このカテゴリ内の製品とそれらの順序を持つ、完全に入力されたテーマを提供します。
受注コレクションには、編集可能である必要がある数量と呼ばれるプロパティがあります(他の多くのプロパティと一緒に)。
@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)
}
}
}
私は代わりにラムダを使用する場合、私は唯一のforeachループ内のものではないトップモデルオブジェクト、"テーマ"への参照を取得するように見える。
私がそこでやろうとしていることは可能なのでしょうか、それとも可能であることを過大評価または誤解しているのでしょうか?
上記で、TextboxFor、EditorForなどでエラーが出ました。
CS0411です。メソッドの型引数 'System.Web.Mvc.Html.InputExtensions.TextBoxFor(System.Web.Mvc.HtmlHelper.TextBoxFor(System.Web.Mvc.HtmlHelper, System.Linq.Expressions.Expression>)' の使用法から推測できません。 は、使用方法から推測することができません。型引数を明示的に指定してみてください。 を明示的に指定してみてください。
ありがとうございます。
どのように解決するのですか?
手っ取り早い答えは
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で機能するのでしょうか? - エクスプレッションツリーとは何ですか?
- モデルバインダーはどのように動作しますか?
これら3つのコンセプトがリンクして、答えを導き出すのです。
はどのように
LabelFor
などの
...For
ヘルパーはMVCで機能しますか?
というわけで、あなたは
HtmlHelper<T>
の拡張を
LabelFor
と
TextBoxFor
などがあり、そして
を呼び出すとき、ラムダを渡すと、それが
魔法のように
を生成します。
を生成します。しかし、どのように?
まず最初に注目すべきは、これらのヘルパーのシグネチャです。最も単純な
TextBoxFor
public static MvcHtmlString TextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
)
まず、これは強く型付けされた拡張メソッドである
HtmlHelper
の、型の
<TModel>
. というわけで、簡単に言うと
razor がこのビューをレンダリングすると、クラスが生成されます。
このクラスの内部には
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にコンパイルすることはせず、代わりに
表現ツリー!
とは何ですか? 表現ツリー ?
さて、式木とはいったい何なのでしょう。複雑なものではありませんが、簡単なものでもありません。というわけで。
| 式木はツリー状のデータ構造でコードを表し、各ノードは式、たとえばメソッドの呼び出しや、x < y のようなバイナリ演算です。
簡単に言うと、式木は関数をアクションのコレクションとして表現したものです。
の場合
model=>model.SomeProperty
の場合、式ツリーには次のようなノードがあります: "「モデル」から「何らかのプロパティ」を取得します。
この式木は コンパイル を呼び出すことができますが、式木である限り、それは単なるノードの集まりです。
では、それは何に使えるのでしょうか?
そこで
Func<>
または
Action<>
というように、一旦手に入れたら、それはほとんど原子のようなものです。あなたが本当にできることは
Invoke()
であり、また、彼らに
するように指示することです。
Expression<Func<>>
はアクションの集合体を表し、追加や操作が可能です。
訪問
や
コンパイル済み
と呼び出されます。
では、なぜこのような話をするのでしょうか?
ということを理解した上で
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クエリを構築することで同様のことを行うことに注意してください)
モデルバインダーはどのように動作するのでしょうか?
では、それを理解した上で、モデルバインダーについて簡単に説明しなければなりません。フォームが投稿されるときは、単純にフラットな
Dictionary<string, string>
のようになり、ネストされたビューモデルが持っていたかもしれない階層的な構造は失われてしまいます。そこで
モデルバインダーの仕事は、このキーと値のペアの組み合わせを受け取り、いくつかのプロパティを持つオブジェクトを再生成しようとすることです。それはどのように行うのでしょうか?
を行うのでしょうか?投稿された入力の "キー" または名前を使用することによって、あなたはそれを推測しました。
というわけで、フォームの投稿が以下のような場合
Foo.Bar.Baz.FooBar = Hello
というモデルに対して投稿しています。
SomeViewModel
というモデルに投稿している場合、ヘルパーが最初に行ったことの逆を行います。それは
というプロパティを探します。次に、"Foo" から "Bar" というプロパティを探し、次に "Baz" を探し... といった具合になります。
最後に、値を "FooBar" の型にパースして、それを "FooBar" に割り当てようとします。
PHEW!!!!
ほら、モデルができました。モデルバインダーが構築したインスタンスは、リクエストされたActionに渡されます。
ということは、あなたのソリューションは
Html.[Type]For()
ヘルパーには式が必要だからです。そして、あなたは彼らに値を与えているだけなのです。それは
その値のコンテキストが何なのか、そしてそれをどうすればいいのかがわからないのです。
レンダリングにパーシャルを使用することを提案した人がいます。理論的にはうまくいきますが、おそらくあなたが期待するような方法ではありません。パーシャルをレンダリングするとき、あなたは
TModel
のタイプを変更することになります。なぜなら、異なるビューコンテキストにいるからです。これは、より短い式でプロパティを記述できることを意味します。
これは、より短い式であなたのプロパティを記述できることを意味します。これは、ヘルパーが式の名前を生成する際に、その名前が浅くなることも意味します。これは
は与えられた式に基づいてのみ生成されます (コンテキスト全体ではありません)。
例えば、(先ほどの例で) "Baz" をレンダリングするパーシャルがあったとします。そのパーシャルの内部では、次のように言うことができます。
@Html.TextBoxFor(model=>model.FooBar)
よりも
@Html.TextBoxFor(model=>model.Foo.Bar.Baz.FooBar)
つまり、このような入力タグが生成されることになります。
<input name="FooBar" />
このフォームを、深くネストされた大きな ViewModel を想定しているアクションに投稿する場合、このアクションは
と呼ばれる
FooBar
から
TModel
. というのは、よく言えば「ない」、悪く言えば「まったく別のもの」なのです。を受け入れる特定のアクションに投稿していた場合、そのアクションは
Baz
を受け入れる特定のアクションに投稿しているのであれば、これはとてもうまくいくでしょう! 実際、パーシャルはビューコンテキストを変更する良い方法です。たとえば、異なるアクションに投稿する複数のフォームを持つページがあった場合、それぞれのフォームに対してパーシャルをレンダリングするのは素晴らしいアイデアです。
さて、これらをすべて理解した上で、本当に面白いことを始めるには
Expression<>
で本当に面白いことができるようになります。
をプログラム的に拡張したり、他のすてきなことをすることができます。私はそのうちのどれにも触れません。しかし、願わくば、これで
舞台裏で何が起こっているのか、なぜそのように動作するのかについて、よりよく理解していただけると幸いです。
関連
-
[解決済み】OnCollisionEnter2Dが実行されない?
-
[解決済み】ランダムなブーリアンを生成する最速の方法
-
[解決済み] 現在のアクティビティからルートビューを取得する
-
[解決済み] ファイルアップロード ASP.NET MVC 3.0
-
[解決済み] Razor View Page で名前空間をインポートするにはどうしたらいいですか?
-
[解決済み] razor viewエンジンでの@文字のエスケープ
-
[解決済み] razor で三項演算子を使用するには (特に HTML 属性について)?
-
[解決済み] Razor でローカル変数を宣言するには?
-
[解決済み】部分ビューから特定のセクションにコンテンツを注入する ASP.NET MVC 3 with Razor View Engine
-
[解決済み] [Solved] Replace line break characters with <br /> in ASP.NET MVC Razor view
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】GDI+、JPEG画像をMemoryStreamに変換する際にジェネリックエラーが発生しました。
-
[解決済み】Ajax処理で「無効なJSONプリミティブ」と表示される件
-
[解決済み】C#におけるtypedefの等価性
-
[解決済み] エンティティタイプ <type> は、現在のコンテキストのモデルの一部ではありません。
-
[解決済み】バックスラッシュを含むパス文字列のエスケープシーケンスが認識されない件
-
[解決済み】WPFでXamlファイルにコメントを追加する方法は?
-
[解決済み】なぜこのコードはInvalidOperationExceptionを投げるのですか?
-
[解決済み】5.7.57 SMTP - MAIL FROMエラー時に匿名メールを送信するためにクライアントが認証されない
-
[解決済み】C#のequal to演算子でtextとvarcharのデータ型は互換性がない
-
[解決済み】スレッド終了またはアプリケーションの要求により、I/O操作が中断されました。