MVC Razor рассматривает вложенную модель foreach

Представьте себе общий сценарий, это более простая версия того, что я вижу. У меня на самом деле есть несколько слоев дальнейшего гнездования на моем …

Но это сценарий

Тема содержит список Категория содержит Список продуктов содержит список

Мой controller предоставляет полностью заполненную тему со всеми категориями для этой темы, Продукты в рамках этих категорий и их заказы.

Коллекция заказов имеет свойство «Количество» (среди многих других), которое должно быть доступно для редактирования.

@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) } } } 

Если я использую lambda вместо этого, тогда я только, кажется, получаю ссылку на верхний объект модели «Тема», а не те, что находятся внутри цикла foreach.

Является ли то, что я пытаюсь сделать там, даже возможно, или я переоценил или неправильно понял, что возможно?

С приведенным выше я получаю сообщение об ошибке в TextboxFor, EditorFor и т. Д.

CS0411: аргументы типа для метода «System.Web.Mvc.Html.InputExtensions.TextBoxFor (System.Web.Mvc.HtmlHelper, System.Linq.Expressions.Expression>)» не могут быть выведены из использования. Попробуйте явно указать аргументы типа.

Благодарю.

Быстрый ответ заключается в использовании цикла 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?
  • Что такое дерево выражений?
  • Как работает модель Binder?

Все три концепции объединяются для получения ответа.

Как LabelFor и другие ...For помощников работы в MVC?

Итак, вы использовали расширения HtmlHelper для LabelFor и TextBoxFor и другие, и вы, вероятно, заметили, что когда вы их вызываете, вы передаете им лямбду и магически генерирует некоторый html. Но как?

Итак, первое, что нужно заметить, это подпись для этих помощников. Давайте рассмотрим простейшую перегрузку для TextBoxFor

 public static MvcHtmlString TextBoxFor( this HtmlHelper htmlHelper, Expression> expression ) 

Во-первых, это метод расширения для строго типизированного HtmlHelper типа . Итак, чтобы просто указать, что происходит за кулисами, когда razor отображает это представление, он генерирует class. Внутри этого classа есть экземпляр HtmlHelper (как свойство Html , поэтому вы можете использовать @Html... ), где TModel – это тип, определенный в вашем заявлении @model . Итак, в вашем случае, когда вы смотрите на это представление, TModel всегда будет иметь тип ViewModels.MyViewModels.Theme .

Теперь следующий аргумент немного сложный. Поэтому давайте посмотрим на вызов

 @Html.TextBoxFor(model=>model.SomeProperty); 

Похоже, у нас есть небольшая lambda. И если угадать подпись, можно подумать, что тип этого аргумента будет просто Func , где TModel – тип модели представления, а TProperty – как тип свойства.

Но это не совсем правильно, если вы посмотрите на фактический тип аргумента Expression> .

Поэтому, когда вы обычно генерируете lambda, компилятор берет лямбду и компилирует ее в MSIL, как и любую другую функцию (именно поэтому вы можете использовать delegates, группы методов и лямбды более или менее взаимозаменяемо, потому что они являются просто кодовыми ссылками .)

Однако, когда компилятор видит, что тип является Expression<> , он не сразу компилирует лямбду вниз в MSIL, вместо этого генерирует дерево выражений!

Что такое дерево выражений ?

Итак, что это за дерево выражений. Ну, это не сложно, но это не прогулка в парке. Чтобы указать ms:

| Деревья выражений представляют код в древовидной структуре данных, где каждый узел является выражением, например, вызовом метода или двоичной операцией, такой как x

Проще говоря, дерево выражений представляет собой представление функции как совокупности «действий».

В случае model=>model.SomeProperty дерево выражений должно иметь узел в нем, который говорит: «Получить« Некоторое свойство »из« модели »»

Это дерево выражений может быть скомпилировано в функцию, которая может быть вызвана, но пока это дерево выражений, это всего лишь набор узлов.

Так что это хорошо?

Итак, Func<> или Action<> , как только вы их получите, они в значительной степени атомарны. Все, что вы действительно можете сделать, это Invoke() , а также сказать им, что они должны выполнять работу, которую они должны выполнять.

Expression> с другой стороны, представляет собой набор действий, которые могут быть добавлены, обработаны, посещены или скомпилированы и вызваны.

Так почему ты мне все это рассказываешь?

Итак, с пониманием того, что такое Expression<> , мы можем вернуться к Html.TextBoxFor . Когда он создает текстовое поле, он должен генерировать несколько вещей о свойстве, которое вы ему даете. Такие вещи, как attributes свойства для проверки, и, в частности, в этом случае необходимо выяснить, как назвать .

Он делает это путем «ходьбы» дерева выражений и создания имени. Таким образом, для выражения типа model=>model.SomeProperty , он выполняет выражение, собирающее свойства, которые вы запрашиваете, и создает .

Для более сложного примера, например model=>model.Foo.Bar.Baz.FooBar , он может генерировать

Имеют смысл? Дело не только в работе Func<> , но и в том , как она делает свою работу.

(Обратите внимание, что другие фреймворки, такие как LINQ to SQL, выполняют аналогичные действия, прогуливая дерево выражений и строя другую грамматику, в этом случае SQL-запрос)

Как работает модель Binder?

Поэтому, как только вы это узнаете, мы должны кратко поговорить о модельном связующем. Когда форма отправляется, это просто как плоский Dictionary , мы потеряли иерархическую структуру, которую могла иметь наша вложенная модель представления. Задача модельного связующего заключается в том, чтобы взять эту комбинацию пары ключ-значение и попытаться повторно укрепить объект с некоторыми свойствами. Как оно работает? Вы это догадались, используя «ключ» или имя ввода, который был отправлен.

Поэтому, если сообщение формы выглядит

 Foo.Bar.Baz.FooBar = Hello 

И вы отправляете в модель под названием SomeViewModel , затем она делает обратное тому, что сделал помощник в первую очередь. Он ищет свойство под названием «Foo». Затем он ищет свойство «Бар» с «Foo», затем он ищет «Баз» … и так далее …

Наконец, он пытается проанализировать значение в виде «FooBar» и назначить его «FooBar».

Уф !!!

И вуаля, у тебя есть твоя модель. Экземпляр, который только что построил модель Binder, передается в запрошенное действие.


Поэтому ваше решение не работает, потому что помощники Html.[Type]For() нуждаются в выражении. И вы просто даете им ценность. Он понятия не имеет, какой контекст для этого значения, и он не знает, что с ним делать.

Теперь некоторые люди предложили использовать partials для рендеринга. Теперь это теоретически будет работать, но, вероятно, не так, как вы ожидаете. Когда вы выполняете частичное, вы изменяете тип TModel , потому что вы находитесь в другом контексте представления. Это означает, что вы можете описать свою собственность с более коротким выражением. Это также означает, что хелпер генерирует имя для вашего выражения, оно будет мелким. Он будет генерироваться только на основе выражения, которое оно дано (а не всего контекста).

Поэтому давайте скажем, что у вас было частичное, что только что появилось «Баз» (из нашего примера раньше). Внутри этого части вы могли просто сказать:

 @Html.TextBoxFor(model=>model.FooBar) 

Скорее, чем

 @Html.TextBoxFor(model=>model.Foo.Bar.Baz.FooBar) 

Это означает, что он будет генерировать входной тег следующим образом:

  

Который, если вы отправляете эту форму в действие, ожидающее большой глубоко вложенной ViewModel, тогда он попытается FooBar свойство, называемое FooBar из TModel . Который в лучшем случае не существует, а в худшем – что-то совсем другое. Если вы отправляете на конкретное действие, принимающее Baz , а не корневую модель, тогда это будет отлично! Фактически, частичные файлы – это хороший способ изменить контекст представления, например, если у вас есть страница с несколькими формами, которые все отправляют в разные действия, то предоставление частичного для каждого из них будет отличной идеей.


Теперь, когда вы получите все это, вы можете начать делать действительно интересные вещи с помощью Expression<> , путем программного расширения их и выполнения с ними других опрятных вещей. Я не буду вникать в это. Но, надеюсь, это даст вам лучшее понимание того, что происходит за кулисами, и почему все действует так, как они есть.

Вы можете просто использовать EditorTemplates для этого, вам нужно создать каталог с именем «EditorTemplates» в папке просмотра вашего controllerа и разместить отдельное представление для каждого из ваших вложенных объектов (называемое именем classа сущности)

Основной вид:

 @model ViewModels.MyViewModels.Theme @Html.LabelFor(Model.Theme.name) @Html.EditorFor(Model.Theme.Categories) 

Просмотр категории (/MyController/EditorTemplates/Category.cshtml):

 @model ViewModels.MyViewModels.Category @Html.LabelFor(Model.Name) @Html.EditorFor(Model.Products) 

Вид продукта (/MyController/EditorTemplates/Product.cshtml):

 @model ViewModels.MyViewModels.Product @Html.LabelFor(Model.Name) @Html.EditorFor(Model.Orders) 

и так далее

таким образом, Html.EditorFor helper будет генерировать имена элементов упорядоченным образом, и поэтому у вас не будет никакой дополнительной проблемы для получения отправленного объекта темы в целом

Вы могли бы добавить частичный элемент и частичный продукт, каждый из которых будет занимать меньшую часть основной модели, поскольку это собственная модель, то есть тип модели категории может быть IEnumerable, вы должны пройти в Model.Theme к нему. Частичный продукт может быть IEnumerable, что вы передаете Model.Products в (из части частичного).

Я не уверен, будет ли это правильным путем, но будет интересно узнать.

РЕДАКТИРОВАТЬ

После публикации этого ответа я использовал EditorTemplates и нашел это самым простым способом обработки повторяющихся групп ввода или элементов. Он обрабатывает все ваши проблемы с сообщением о валидации и автоматически связывает проблемы с отправкой / моделью.

Когда вы используете цикл foreach в представлении для привязанной модели … Ваша модель должна быть в указанном формате.

т.е.

 @model IEnumerable @{ if (Model.Count() > 0) { @Html.DisplayFor(modelItem => Model.Theme.FirstOrDefault().name) @foreach (var theme in Model.Theme) { @Html.DisplayFor(modelItem => theme.name) @foreach(var product in theme.Products) { @Html.DisplayFor(modelItem => product.name) @foreach(var order in product.Orders) { @Html.TextBoxFor(modelItem => order.Quantity) @Html.TextAreaFor(modelItem => order.Note) @Html.EditorFor(modelItem => order.DateRequestedDeliveryFor) } } } }else{ No Theam avaiable } } 

Это ясно из ошибки.

HtmlHelpers, добавленный в «For», ожидает в качестве параметра lambda-выражения.

Если вы передаете значение напрямую, лучше используйте Normal.

например

Вместо TextboxFor (….) используйте Textbox ()

синтаксис для TextboxFor будет похож на Html.TextBoxFor (m => m.Property)

В вашем сценарии вы можете использовать basic for loop, поскольку он даст вам индекс для использования.

 @for(int i=0;im.Theme[i].name) @for(int j=0;jm.Theme[i].Products[j].name) @for(int k=0;kModel.Theme[i].Products[j].Orders[k].Quantity) @Html.TextAreaFor(m=>Model.Theme[i].Products[j].Orders[k].Note) @Html.EditorFor(m=>Model.Theme[i].Products[j].Orders[k].DateRequestedDeliveryFor) } } } 
  • Перекрытие просмотров в Android
  • Перерисовать одну строку в listview
  • Получить целенаправленный вид из ViewPager
  • Жирные модели, тощие controllerы и шаблон проектирования MVC
  • Отобразить основной вид HTML?
  • Как получить представление ActionBar?
  • C #: Как добавить подтипы в ListView
  • Interesting Posts

    Что означает «typedef void (* Something) ()» означает

    Не удалось загрузить файл или сборку «System.Data.SQLite»

    Android – Воспроизведение mp3 с байта

    Могу ли я использовать более одного интернет-провайдера одновременно?

    Почему неинициализированная переменная печатается на C ++?

    Ошибка при развертывании приложения ClickOnce. Ссылка в манифесте не соответствует идентификатору загруженной сборки

    drag and drop jlabel вокруг экрана

    Как передать функцию в качестве параметра в C #?

    Скопируйте и вставьте без отслеживаемых изменений в MS Office 2013

    Есть ли параллельный список в JDK Java?

    jQuery ajax upload с индикатором выполнения – без вспышки

    Рассчитайте, когда будет выполняться задание cron, а затем в следующий раз

    Получение ошибки при попытке настроить общие папки на экземпляре Ubuntu VMWare Fusion, работающем на OSX

    C struct hack на работе

    Как запустить скрипт powershell как администратора

    Давайте будем гением компьютера.