Швидка відповідь полягає у використанні 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)
}
}
}
Але це зачіпає, чому це вирішує проблему.
Є три речі, які ви, як мінімум, побіжно зрозуміли, перш ніж вирішити цю проблему. Я повинен визнати, що я довго займався цим, коли почав працювати з фреймворком. І мені знадобився досить довгий час, щоб справді зрозуміти, що відбувається.
Це три речі:
- Як працюють
LabelFor
та інші ...For
помічники в MVC?
- Що таке дерево виразів?
- Як працює Model Binder?
Всі три ці поняття зв’язуються між собою, щоб отримати відповідь.
Як працюють 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);
Схоже, у нас є трохи лямбда, і якби вгадати підпис, можна подумати, що типом для цього аргументу буде просто a 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)
Як працює Model Binder?
Отже, як тільки ви це отримаєте, ми повинні коротко поговорити про в'яжучу модель. Коли форма розміщується, це просто як квартира
Dictionary<string, string>
, ми втратили ієрархічну структуру, яку могла мати наша вкладена модель подання. Завдання в’яжучого моделі - взяти цю комбіновану пару ключ-значення та спробувати регідратати об’єкт з деякими властивостями. Як це робиться? Ви здогадалися, використовуючи "ключ" або ім'я введення, яке було розміщено.
Отже, якщо публікація форми виглядає так
Foo.Bar.Baz.FooBar = Hello
І ви SomeViewModel
розміщуєте повідомлення в моделі з назвою , тоді вона робить зворотне, що робив помічник. Він шукає властивість під назвою "Foo". Потім він шукає властивість під назвою "Bar" від "Foo", потім шукає "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, тоді вона спробує гідратувати властивість, яку FooBar
відкликають TModel
. Якого в кращому випадку немає, а в гіршому - зовсім іншого. Якщо ви розміщували повідомлення до певної дії, яка приймала Baz
а не кореневу модель, тоді це було б чудово! Насправді часткові - це хороший спосіб змінити контекст вашого перегляду, наприклад, якщо у вас є сторінка з декількома формами, які всі публікують у різних діях, то надання часткової для кожної з них буде чудовою ідеєю.
Тепер, як тільки ви отримаєте все це, ви можете почати робити справді цікаві речі Expression<>
, програмно розширюючи їх та роблячи з ними інші акуратні речі. Я ні в що з цього не потраплю. Але, сподіваємось, це дасть вам краще зрозуміти, що відбувається за лаштунками і чому речі поводяться так, як вони.
@
перед усімаforeach
s? Чи не слід вам також мати лямбди вHtml.EditorFor
(Html.EditorFor(m => m.Note)
наприклад) та інших методах? Можливо, я помиляюся, але чи можете ви вставити ваш власний код? Я досить новачок у MVC, але ви можете досить легко вирішити це за допомогою часткових переглядів або редакторів (якщо це ім'я?).