Jawaban cepatnya adalah dengan menggunakan for()
loop sebagai pengganti foreach()
loop Anda . Sesuatu seperti:
@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)
}
}
}
Tapi ini menjelaskan mengapa ini memperbaiki masalah.
Ada tiga hal yang setidaknya sepintas Anda pahami sebelum Anda dapat menyelesaikan masalah ini. Saya harus mengakui bahwa saya membudidayakan kargo ini untuk waktu yang lama ketika saya mulai bekerja dengan kerangka kerja. Dan saya butuh waktu cukup lama untuk benar-benar memahami apa yang sedang terjadi.
Ketiga hal tersebut adalah:
- Bagaimana cara kerja pembantu
LabelFor
dan lainnya ...For
di MVC?
- Apa itu Pohon Ekspresi?
- Bagaimana cara kerja Model Binder?
Ketiga konsep ini saling terkait untuk mendapatkan jawaban.
Bagaimana cara kerja pembantu LabelFor
dan lainnya ...For
di MVC?
Jadi, Anda telah menggunakan HtmlHelper<T>
ekstensi untuk LabelFor
dan TextBoxFor
dan lainnya, dan Anda mungkin memperhatikan bahwa ketika Anda memanggilnya, Anda mengirimkan lambda dan secara ajaib menghasilkan beberapa html. Tapi bagaimana caranya?
Jadi hal pertama yang harus diperhatikan adalah tanda tangan untuk para pembantu ini. Mari kita lihat kelebihan yang paling sederhana untuk
TextBoxFor
public static MvcHtmlString TextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
)
Pertama, ini adalah metode ekstensi untuk sangat diketik HtmlHelper
, jenis <TModel>
. Jadi, untuk sekadar menyatakan apa yang terjadi di balik layar, saat pisau cukur membuat tampilan ini, itu akan menghasilkan kelas. Di dalam kelas ini adalah turunan dari HtmlHelper<TModel>
(sebagai properti Html
, itulah sebabnya Anda dapat menggunakan @Html...
), di mana TModel
tipe yang ditentukan dalam @model
pernyataan Anda . Jadi dalam kasus Anda, ketika Anda melihat tampilan ini TModel
akan selalu menjadi tipe ViewModels.MyViewModels.Theme
.
Sekarang, argumen selanjutnya agak rumit. Jadi mari kita lihat sebuah doa
@Html.TextBoxFor(model=>model.SomeProperty);
Sepertinya kita memiliki sedikit lambda, Dan jika seseorang menebak tanda tangannya, orang mungkin berpikir bahwa tipe untuk argumen ini hanya akan menjadi a Func<TModel, TProperty>
, di mana TModel
adalah tipe model tampilan dan TProperty
disimpulkan sebagai tipe properti.
Tapi itu kurang tepat, jika Anda melihat tipe sebenarnya dari argumennya Expression<Func<TModel, TProperty>>
.
Jadi, ketika Anda biasanya menghasilkan lambda, kompilator mengambil lambda dan mengkompilasinya menjadi MSIL, sama seperti fungsi lainnya (itulah mengapa Anda dapat menggunakan delegasi, grup metode, dan lambda secara bergantian, karena mereka hanya referensi kode .)
Namun, ketika kompilator melihat bahwa tipenya adalah Expression<>
, ia tidak segera mengkompilasi lambda ke MSIL, melainkan menghasilkan Pohon Ekspresi!
Jadi, apa sih pohon ekspresi itu. Yah, itu tidak rumit tapi juga bukan jalan-jalan di taman. Mengutip ms:
| Pohon ekspresi mewakili kode dalam struktur data seperti pohon, di mana setiap node adalah ekspresi, misalnya, panggilan metode atau operasi biner seperti x <y.
Sederhananya, pohon ekspresi adalah representasi fungsi sebagai kumpulan "tindakan".
Dalam kasus model=>model.SomeProperty
, pohon ekspresi akan memiliki simpul di dalamnya yang berbunyi: "Dapatkan 'Beberapa Properti' dari 'model'"
Pohon ekspresi ini dapat dikompilasi menjadi fungsi yang dapat dipanggil, tetapi selama pohon ekspresi ini, itu hanya kumpulan node.
Jadi apa gunanya itu?
Jadi Func<>
atau Action<>
, begitu Anda memilikinya, mereka cukup atom. Yang dapat Anda lakukan hanyalah Invoke()
mereka, alias memberi tahu mereka untuk melakukan pekerjaan yang seharusnya mereka lakukan.
Expression<Func<>>
di sisi lain, mewakili sekumpulan tindakan, yang dapat ditambahkan, dimanipulasi, dikunjungi , atau disusun dan dipanggil.
Jadi kenapa kamu memberitahuku semua ini?
Jadi dengan pemahaman tentang apa Expression<>
itu, kita bisa kembali ke Html.TextBoxFor
. Saat merender kotak teks, ia perlu menghasilkan beberapa hal tentang properti yang Anda berikan. Hal seperti attributes
di properti untuk validasi, dan secara khusus dalam hal ini perlu untuk mencari tahu apa yang harus nama yang <input>
tag.
Ini dilakukan dengan "berjalan" pada pohon ekspresi dan membangun sebuah nama. Jadi untuk ekspresi seperti model=>model.SomeProperty
, itu berjalan ekspresi mengumpulkan properti yang Anda minta dan bangun <input name='SomeProperty'>
.
Untuk contoh yang lebih rumit, seperti model=>model.Foo.Bar.Baz.FooBar
, itu mungkin menghasilkan<input name="Foo.Bar.Baz.FooBar" value="[whatever FooBar is]" />
Masuk akal? Bukan hanya pekerjaan yang Func<>
dilakukan, tetapi cara kerjanya penting di sini.
(Perhatikan kerangka kerja lain seperti LINQ hingga SQL melakukan hal serupa dengan menjalankan pohon ekspresi dan membangun tata bahasa yang berbeda, dalam hal ini kueri SQL)
Bagaimana cara kerja Model Binder?
Jadi setelah Anda mendapatkannya, kita harus membahas secara singkat tentang model binder. Saat formulir diposting, itu hanya seperti sebuah flat
Dictionary<string, string>
, kami telah kehilangan struktur hierarki yang mungkin dimiliki oleh model tampilan bersarang kami. Tugas pengikat model adalah mengambil kombo pasangan nilai-kunci ini dan mencoba menghidrasi ulang objek dengan beberapa properti. Bagaimana cara melakukannya? Anda dapat menebaknya, dengan menggunakan "kunci" atau nama input yang diposting.
Jadi kalau bentuk postingannya seperti
Foo.Bar.Baz.FooBar = Hello
Dan Anda memposting ke model bernama SomeViewModel
, lalu melakukan kebalikan dari apa yang dilakukan helper di tempat pertama. Ini mencari properti yang disebut "Foo". Kemudian mencari properti bernama "Bar" dari "Foo", lalu mencari "Baz" ... dan seterusnya ...
Terakhir, ia mencoba untuk mengurai nilai menjadi tipe "FooBar" dan menetapkannya ke "FooBar".
PHEW !!!
Dan voila, Anda memiliki model Anda. Instance yang baru saja dibuat oleh Model Binder akan diserahkan ke Action yang diminta.
Jadi solusi Anda tidak berhasil karena Html.[Type]For()
penolong membutuhkan ekspresi. Dan Anda hanya memberi mereka nilai. Ia tidak tahu apa konteksnya untuk nilai itu, dan ia tidak tahu apa yang harus dilakukan dengannya.
Sekarang beberapa orang menyarankan menggunakan parsial untuk merender. Sekarang secara teori ini akan berhasil, tetapi mungkin tidak seperti yang Anda harapkan. Saat Anda merender parsial, Anda mengubah tipe TModel
, karena Anda berada dalam konteks tampilan yang berbeda. Ini berarti Anda dapat mendeskripsikan properti Anda dengan ekspresi yang lebih pendek. Ini juga berarti ketika helper menghasilkan nama untuk ekspresi Anda, itu akan menjadi dangkal. Itu hanya akan dihasilkan berdasarkan ekspresi yang diberikan (bukan seluruh konteks).
Jadi katakanlah Anda memiliki sebagian yang baru saja dirender "Baz" (dari contoh kita sebelumnya). Di dalam bagian itu Anda bisa mengatakan:
@Html.TextBoxFor(model=>model.FooBar)
Daripada
@Html.TextBoxFor(model=>model.Foo.Bar.Baz.FooBar)
Itu berarti itu akan menghasilkan tag input seperti ini:
<input name="FooBar" />
Yang mana, jika Anda memposting formulir ini ke tindakan yang mengharapkan ViewModel bertingkat dalam yang besar, maka itu akan mencoba untuk menghidrasi properti yang disebut FooBar
off TModel
. Yang terbaik tidak ada, dan yang terburuk adalah sesuatu yang sama sekali berbeda. Jika Anda memposting ke tindakan tertentu yang menerima Baz
, daripada model root, maka ini akan bekerja dengan baik! Faktanya, parsial adalah cara yang baik untuk mengubah konteks tampilan Anda, misalnya jika Anda memiliki laman dengan beberapa formulir yang semuanya memposting ke tindakan berbeda, maka merender parsial untuk masing-masing adalah ide yang bagus.
Sekarang setelah Anda mendapatkan semua ini, Anda dapat mulai melakukan hal-hal yang sangat menarik Expression<>
, dengan mengembangkannya secara terprogram dan melakukan hal-hal rapi lainnya dengannya. Saya tidak akan membahas semua itu. Tapi, mudah-mudahan, ini akan memberi Anda pemahaman yang lebih baik tentang apa yang terjadi di balik layar dan mengapa segala sesuatunya berjalan seperti itu.
@
sebelumnyaforeach
? Bukankah Anda juga harus memiliki lambda diHtml.EditorFor
(Html.EditorFor(m => m.Note)
, misalnya) dan metode lainnya? Saya mungkin salah, tetapi bisakah Anda menempelkan kode Anda yang sebenarnya? Saya cukup baru mengenal MVC, tetapi Anda dapat menyelesaikannya dengan cukup mudah dengan tampilan parsial, atau editor (apakah itu namanya?).