Представьте себе общий сценарий, это более простая версия того, с чем я столкнулся. У меня на самом деле есть пара слоев дальнейшего вложения ....
Но это сценарий
Тема содержит список Категория содержит список Продукт содержит список
My Controller предоставляет полностью заполненную тему со всеми категориями для этой темы, продуктами в этих категориях и их заказами.
Коллекция заказов имеет свойство Quantity (среди многих других), которое необходимо редактировать.
@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)
}
}
}
Если вместо этого я использую лямбда-выражение, мне кажется, что я получаю ссылку только на верхний объект модели, «Тема», а не на те, которые находятся в цикле foreach.
Возможно ли то, что я пытаюсь сделать там, или я переоценил или неправильно понял то, что возможно?
С указанным выше я получаю сообщение об ошибке в TextboxFor, EditorFor и т. Д.
CS0411: аргументы типа для метода 'System.Web.Mvc.Html.InputExtensions.TextBoxFor (System.Web.Mvc.HtmlHelper, System.Linq.Expressions.Expression>)' не могут быть выведены из использования. Попробуйте явно указать аргументы типа.
Спасибо.
@
раньше всехforeach
? Разве у вас не должны быть лямбды вHtml.EditorFor
(Html.EditorFor(m => m.Note)
например) и в остальных методах? Я могу ошибаться, но не могли бы вы вставить свой настоящий код? Я новичок в MVC, но вы можете довольно легко решить эту проблему с помощью частичных представлений или редакторов (если это имя?).category.name
Я уверен, что это astring
и...For
не поддерживает строку в качестве первого параметра:)
.for()
а неforeach
. Я объясню почему, потому что это меня тоже чертовски смущало.Ответы:
Быстрый ответ - использовать
for()
цикл вместо вашихforeach()
циклов. Что-то вроде:Но это замалчивает, почему это решает проблему.
Есть три вещи, которые вы хотя бы поверхностно понимаете, прежде чем сможете решить эту проблему. Должен признаться, я долго занимался этим, когда начал работать с фреймворком. И мне потребовалось довольно много времени, чтобы по-настоящему понять, что происходит.
Вот эти три вещи:
LabelFor
и другие...For
помощники работают в MVC?Все три концепции связаны друг с другом, чтобы получить ответ.
Как этот
LabelFor
и другие...For
помощники работают в MVC?Итак, вы использовали
HtmlHelper<T>
расширения дляLabelFor
иTextBoxFor
и других, и вы, вероятно, заметили, что когда вы вызываете их, вы передаете им лямбда, и он волшебным образом генерирует некоторый html. Но как?Итак, первое, что нужно заметить, - это подпись этих помощников. Давайте посмотрим на простейшую перегрузку для
TextBoxFor
Во-первых, это метод расширения для строго типизированного
HtmlHelper
типа<TModel>
. Итак, чтобы просто указать, что происходит за кулисами, когда razor визуализирует это представление, он генерирует класс. Внутри этого класса находится экземплярHtmlHelper<TModel>
(в качестве свойстваHtml
, поэтому вы можете использовать@Html...
), гдеTModel
- тип, определенный в вашем@model
операторе. Так что в вашем случае, когда вы смотрите на это представлениеTModel
всегда будет типаViewModels.MyViewModels.Theme
.Теперь следующий аргумент немного сложен. Итак, давайте посмотрим на призыв
Похоже, у нас есть небольшая лямбда, и если бы кто-то угадал сигнатуру, можно было бы подумать, что типом для этого аргумента будет просто 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>
, мы потеряли иерархическую структуру, которая могла быть у нашей модели вложенного представления. Задача связывателя модели - взять эту комбинацию пары ключ-значение и попытаться повторно гидратировать объект с некоторыми свойствами. Как оно работает? Вы уже догадались, используя «ключ» или имя введенного сообщения.Итак, если сообщение формы выглядит как
И вы отправляете сообщение в модель с именем
SomeViewModel
, а затем она делает обратное тому, что делал помощник в первую очередь. Он ищет свойство под названием «Foo». Затем он ищет свойство под названием «Bar» в «Foo», затем ищет «Baz» ... и так далее ...Наконец, он пытается преобразовать значение в тип «FooBar» и присвоить его «FooBar».
PHEW !!!
И вуаля, у вас есть ваша модель. Экземпляр, который только что сконструировал связыватель модели, передается в запрошенное действие.
Итак, ваше решение не работает, потому что
Html.[Type]For()
помощникам нужно выражение. И вы просто придаете им значение. Он не знает, каков контекст этого значения, и не знает, что с ним делать.Теперь некоторые люди предлагают использовать для рендеринга частичные файлы. Теоретически это сработает, но, вероятно, не так, как вы ожидаете. При рендеринге партиала вы меняете тип
TModel
, потому что находитесь в другом контексте представления. Это означает, что вы можете описать свою собственность более коротким выражением. Это также означает, что когда помощник генерирует имя для вашего выражения, оно будет поверхностным. Он будет генерироваться только на основе заданного выражения (а не всего контекста).Допустим, у вас есть партиал, который только что отрисовал "Baz" (из нашего предыдущего примера). Внутри этого фрагмента вы можете просто сказать:
Скорее, чем
Это означает, что он сгенерирует такой входной тег:
Что, если вы отправляете эту форму в действие, которое ожидает большой глубоко вложенной ViewModel, тогда оно попытается гидратировать свойство, вызываемое
FooBar
изTModel
. Которого в лучшем случае нет, а в худшем - что-то совсем другое. Если бы вы отправляли сообщение в конкретное действие, которое принималоBaz
, а не корневую модель, то это отлично сработало бы! Фактически, партиалы - это хороший способ изменить контекст вашего представления, например, если у вас есть страница с несколькими формами, которые все публикуют для разных действий, тогда рендеринг партиала для каждой из них будет отличной идеей.Теперь, когда вы получите все это, вы можете начать делать действительно интересные вещи
Expression<>
, программно расширяя их и делая с ними другие полезные вещи. Я не буду вдаваться в подробности. Но, надеюсь, это даст вам лучшее понимание того, что происходит за кулисами, и почему все идет так, как есть.источник
Вы можете просто использовать EditorTemplates для этого, вам нужно создать каталог с именем "EditorTemplates" в папке представления вашего контроллера и разместить отдельное представление для каждой из ваших вложенных сущностей (названных как имя класса сущности)
Главный вид:
Просмотр категории (/MyController/EditorTemplates/Category.cshtml):
Просмотр продукта (/MyController/EditorTemplates/Product.cshtml):
и так далее
таким образом помощник Html.EditorFor сгенерирует имена элементов в упорядоченном порядке, и, следовательно, у вас не возникнет дополнительных проблем с получением опубликованного объекта Theme в целом
источник
Вы можете добавить партиал категории и партиал продукта, каждая из которых будет занимать меньшую часть основной модели в качестве собственной модели, т.е. тип модели категории может быть IEnumerable, вы передадите ему Model.Theme. Партиал Product может быть IEnumerable, в который вы передаете Model.Products (из партиала Category).
Я не уверен, что это правильный путь вперед, но мне было бы интересно узнать.
РЕДАКТИРОВАТЬ
После публикации этого ответа я использовал EditorTemplates и считаю, что это самый простой способ обрабатывать повторяющиеся группы ввода или элементы. Он автоматически обрабатывает все ваши проблемы с сообщениями проверки и проблемы с отправкой формы / привязкой модели.
источник
Theme
модель не будет гидратирована должным образом.Когда вы используете цикл foreach в представлении для связанной модели ... Ваша модель должна быть в указанном формате.
т.е.
источник
Это ясно из ошибки.
HtmlHelpers с добавлением «For» ожидает лямбда-выражение в качестве параметра.
Если вы передаете значение напрямую, лучше используйте Нормальный.
например
Вместо TextboxFor (....) используйте Textbox ()
синтаксис для TextboxFor будет похож на Html.TextBoxFor (m => m.Property)
В вашем сценарии вы можете использовать базовый цикл for, так как он даст вам индекс для использования.
источник
Другая гораздо более простая возможность состоит в том, что одно из ваших имен свойств неверно (возможно, вы только что изменили в классе). Это было для меня в RazorPages .NET Core 3.
источник