Ответ 1
Быстрый ответ заключается в использовании цикла 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?
Все три понятия объединяются для получения ответа.
Как работают помощники 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);
Похоже, у нас есть небольшая лямбда. И если угадать подпись, можно подумать, что тип для
этот аргумент был бы просто Func<TModel, TProperty>
, где TModel
- тип модели представления и TProperty
вызывается как тип свойства.
Но это не совсем правильно, если вы посмотрите на фактический тип аргумента Expression<Func<TModel, TProperty>>
.
Итак, когда вы обычно генерируете лямбда, компилятор берет лямбду и компилирует ее в MSIL, как и любой другой (поэтому вы можете использовать делегаты, группы методов и лямбды более или менее взаимозаменяемо, потому что они просто ссылки на код.)
Однако, когда компилятор видит, что тип является Expression<>
, он не сразу компилирует лямбду в MSIL, вместо этого генерирует
Дерево выражений!
Что такое Дерево выражений?
Итак, что это за дерево выражений. Ну, это не сложно, но и не прогулка в парке. Чтобы указать ms:
| Деревья выражений представляют код в древовидной структуре данных, где каждый node является выражением, например вызовом метода или двоичной операцией, такой как x < у.
Проще говоря, дерево выражений представляет собой представление функции как совокупности "действий".
В случае model=>model.SomeProperty
в дереве выражений будет node, в котором говорится: "Получить" Некоторое свойство "из" модели "
Это дерево выражений может быть скомпилировано в функцию, которую можно вызвать, но до тех пор, пока это дерево выражений, это всего лишь набор узлов.
Итак, что хорошего для?
Итак 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-запрос)
Как работает модель Binder?
Итак, как только вы это поняли, нам нужно вкратце рассказать о привязке модели. Когда форма отправляется, она просто напоминает квартиру
Dictionary<string, string>
, мы потеряли иерархическую структуру, которую могла иметь наша вложенная модель представления. Это
model, чтобы взять эту комбинацию пары ключ-значение и попытаться перевести объект с некоторыми свойствами. Как это сделать
это? Вы уже догадались, используя "ключ" или имя введенного сообщения.
Итак, если сообщение формы выглядит как
Foo.Bar.Baz.FooBar = Hello
И вы отправляете в модель под названием SomeViewModel
, затем она делает обратное тому, что сделал помощник в первую очередь. Он ищет
свойство "Foo". Затем он ищет свойство "Bar" off "Foo", затем он ищет "Baz"... и так далее...
Наконец, он пытается проанализировать значение в виде "FooBar" и назначить его "FooBar".
Уф!!!
И вуаля, у тебя есть твоя модель. Экземпляр, который только что построил модель Binder, передается в запрошенное действие.
Итак, ваше решение не работает, потому что помощникам Html.[Type]For()
требуется выражение. И вы просто даете им ценность. Это не имеет понятия
какой контекст для этого значения, и он не знает, что с ним делать.
Теперь некоторые люди предложили использовать partials для рендеринга. Теперь это теоретически будет работать, но, вероятно, не так, как вы ожидаете. Когда вы выполняете частичное, вы меняете тип TModel
, потому что вы находитесь в другом контексте представления. Это означает, что вы можете описать
ваша собственность с более коротким выражением. Это также означает, что хелпер генерирует имя для вашего выражения, оно будет мелким. Это
будет генерироваться только на основе выраженного им выражения (а не всего контекста).
Итак, давайте скажем, что у вас было частичное, что только что появилось "Baz" (из нашего примера раньше). Внутри этого части вы могли просто сказать:
@Html.TextBoxFor(model=>model.FooBar)
Вместо
@Html.TextBoxFor(model=>model.Foo.Bar.Baz.FooBar)
Это означает, что он будет генерировать входной тег следующим образом:
<input name="FooBar" />
Что, если вы отправляете эту форму в действие, ожидающее большой глубоко вложенной ViewModel, тогда он попытается увлажнить свойство
называется FooBar
off TModel
. Который в лучшем случае не существует, а в худшем - что-то совсем другое. Если вы отправляете на конкретное действие, принимающее Baz
, а не корневую модель, тогда это будет отлично! Фактически, частичные файлы - это хороший способ изменить контекст представления, например, если у вас есть страница с несколькими формами, которые все отправляют в разные действия, тогда предоставление частичного для каждого из них будет отличной идеей.
Теперь, когда вы получите все это, вы можете начать делать действительно интересные вещи с помощью Expression<>
, программно расширяя их и делая
другие аккуратные вещи с ними. Я не буду вникать в это. Но, надеюсь, это будет
дать вам лучшее представление о том, что происходит за кулисами, и почему все действует так, как они есть.