Bayangkan skenario umum, ini adalah versi yang lebih sederhana dari apa yang saya temukan. Saya sebenarnya memiliki beberapa lapisan bersarang lebih lanjut di saya ....
Tapi ini skenarionya
Tema berisi Kategori Daftar berisi Produk Daftar berisi Daftar
Kontroler Saya menyediakan Tema yang terisi penuh, dengan semua Kategori untuk tema itu, Produk dalam kategori ini dan pesanannya.
Koleksi pesanan memiliki properti yang disebut Kuantitas (di antara banyak lainnya) yang perlu diedit.
@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)
}
}
}
Jika saya menggunakan lambda, maka saya hanya mendapatkan referensi ke objek Model teratas, "Tema" bukan yang berada dalam loop foreach.
Apakah yang saya coba lakukan di sana mungkin atau apakah saya melebih-lebihkan atau salah paham tentang apa yang mungkin?
Dengan di atas saya mendapatkan kesalahan pada TextboxFor, EditorFor, dll
CS0411: Jenis argumen untuk metode 'System.Web.Mvc.Html.InputExtensions.TextBoxFor (System.Web.Mvc.HtmlHelper, System.Linq.Expressions.Expression>)' tidak dapat disimpulkan dari penggunaan. Coba tentukan argumen tipe secara eksplisit.
Terima kasih.
@
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?).category.name
Saya yakin itu astring
dan...For
tidak mendukung string sebagai parameter pertama:)
.for()
daripada aforeach
. Saya akan menjelaskan mengapa, karena itu membingungkan saya untuk waktu yang lama juga.Jawaban:
Jawaban cepatnya adalah dengan menggunakan
for()
loop sebagai penggantiforeach()
loop Anda . Sesuatu seperti: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:
LabelFor
dan lainnya...For
di MVC?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 untukLabelFor
danTextBoxFor
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
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 dariHtmlHelper<TModel>
(sebagai propertiHtml
, itulah sebabnya Anda dapat menggunakan@Html...
), di manaTModel
tipe yang ditentukan dalam@model
pernyataan Anda . Jadi dalam kasus Anda, ketika Anda melihat tampilan iniTModel
akan selalu menjadi tipeViewModels.MyViewModels.Theme
.Sekarang, argumen selanjutnya agak rumit. Jadi mari kita lihat sebuah doa
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 manaTModel
adalah tipe model tampilan danTProperty
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!Apa itu 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<>
atauAction<>
, begitu Anda memilikinya, mereka cukup atom. Yang dapat Anda lakukan hanyalahInvoke()
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 keHtml.TextBoxFor
. Saat merender kotak teks, ia perlu menghasilkan beberapa hal tentang properti yang Anda berikan. Hal sepertiattributes
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
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:
Daripada
Itu berarti itu akan menghasilkan tag input seperti ini:
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
offTModel
. Yang terbaik tidak ada, dan yang terburuk adalah sesuatu yang sama sekali berbeda. Jika Anda memposting ke tindakan tertentu yang menerimaBaz
, 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.sumber
Anda cukup menggunakan EditorTemplates untuk melakukan itu, Anda perlu membuat direktori bernama "EditorTemplates" di folder tampilan pengontrol Anda dan menempatkan tampilan terpisah untuk setiap entitas bertingkat Anda (dinamai sebagai nama kelas entitas)
Tampilan utama:
Tampilan kategori (/MyController/EditorTemplates/Category.cshtml):
Tampilan produk (/MyController/EditorTemplates/Product.cshtml):
dan seterusnya
dengan cara ini Html.EditorFor helper akan menghasilkan nama elemen secara berurutan dan oleh karena itu Anda tidak akan memiliki masalah lebih lanjut untuk mengambil entitas Tema yang diposting secara keseluruhan
sumber
Anda dapat menambahkan sebagian Kategori dan sebagian Produk, masing-masing akan mengambil sebagian kecil dari model utama sebagai modelnya sendiri, yaitu jenis model Kategori mungkin berupa IEnumerable, Anda akan meneruskan Model.Theme ke dalamnya. Parsial Produk mungkin berupa IEnumerable yang Anda berikan ke Model.Produk masuk (dari dalam Parsial Kategori).
Saya tidak yakin apakah itu cara yang tepat untuk maju, tetapi saya tertarik untuk mengetahuinya.
EDIT
Sejak memposting jawaban ini, saya telah menggunakan EditorTemplates dan menemukan ini cara termudah untuk menangani grup atau item input yang berulang. Ini menangani semua masalah pesan validasi Anda dan masalah pengikatan formulir / model secara otomatis.
sumber
Theme
model tidak akan terhidrasi dengan baik.Saat Anda menggunakan foreach loop dalam tampilan untuk model terikat ... Model Anda seharusnya dalam format yang terdaftar.
yaitu
sumber
Jelas dari kesalahannya.
HtmlHelpers yang ditambahkan dengan "For" mengharapkan ekspresi lambda sebagai parameter.
Jika Anda meneruskan nilai secara langsung, lebih baik gunakan nilai Normal.
misalnya
Alih-alih TextboxFor (....) gunakan Textbox ()
sintaks untuk TextboxFor akan menjadi seperti Html.TextBoxFor (m => m.Property)
Dalam skenario Anda, Anda dapat menggunakan dasar for loop, karena ini akan memberi Anda indeks untuk digunakan.
sumber
Kemungkinan lain yang lebih sederhana adalah salah satu nama properti Anda salah (mungkin yang baru saja Anda ubah di kelas). Inilah yang saya alami di RazorPages .NET Core 3.
sumber