Быстрый ответ - использовать 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?
- Что такое дерево выражений?
- Как работает подшивка моделей?
Все три концепции связаны друг с другом, чтобы получить ответ.
Как этот 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>
. Итак, чтобы просто указать, что происходит за кулисами, когда razor визуализирует это представление, он генерирует класс. Внутри этого класса находится экземпляр HtmlHelper<TModel>
(в качестве свойства Html
, поэтому вы можете использовать @Html...
), где TModel
- тип, определенный в вашем @model
операторе. Так что в вашем случае, когда вы смотрите на это представление TModel
всегда будет типа ViewModels.MyViewModels.Theme
.
Теперь следующий аргумент немного сложен. Итак, давайте посмотрим на призыв
@Html.TextBoxFor(model=>model.SomeProperty);
Похоже, у нас есть небольшая лямбда, и если бы кто-то угадал сигнатуру, можно было бы подумать, что типом для этого аргумента будет просто a Func<TModel, TProperty>
, где TModel
- это тип модели представления и TProperty
выводится как тип свойства.
Но это не совсем так, если вы посмотрите на фактический тип аргумента its 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» в «Foo», затем ищет «Baz» ... и так далее ...
Наконец, он пытается преобразовать значение в тип «FooBar» и присвоить его «FooBar».
PHEW !!!
И вуаля, у вас есть ваша модель. Экземпляр, который только что сконструировал связыватель модели, передается в запрошенное действие.
Итак, ваше решение не работает, потому что 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
? Разве у вас не должны быть лямбды вHtml.EditorFor
(Html.EditorFor(m => m.Note)
например) и в остальных методах? Я могу ошибаться, но не могли бы вы вставить свой настоящий код? Я новичок в MVC, но вы можете довольно легко решить эту проблему с помощью частичных представлений или редакторов (если это имя?).