MVC Razor view вложенная модель foreach


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

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

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

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

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

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

если я использую лямбда вместо этого, то я только, кажется, получаю ссылку на объект top Model, "тему", а не те, которые находятся в цикле foreach.

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

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

CS0411: аргументы типа для метод - Система.Сеть.Mvc.Формат html.InputExtensions.TextBoxFor (System.Сеть.Mvc.HtmlHelper, Система.В LINQ.Выражения.Выражение')> не может быть выведено из использования. Попробуйте указать аргументы типа явно.

спасибо.

5 91

5 ответов:

быстрый ответ заключается в использовании 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)
      }
   }
}

но это замалчивается почему это решает проблему.

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

эти три вещи:

  • How do the LabelFor и другие ...For помощники работают в MVC?
  • что такое дерево выражения?
  • как работает модель связующего?

все три из этих понятий связаны вместе, чтобы получить ответ.

How do the 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 выводится как тип свойства.

но это не совсем верно, если вы посмотрите на фактический тип аргумента its Expression<Func<TModel, TProperty>>.

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

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

что такое Выражение Дерево?

Итак,что такое дерево выражений. Ну, это не сложно, но это не прогулка в парке тоже. Цитировать МС:

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

в случае model=>model.SomeProperty, дерево выражений будет иметь узел в нем, который говорит:"получить 'некоторое свойство' из 'модели'"

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

так для чего же это хорошо?

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

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

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

так с тем пониманием, что такое Expression<>, мы можем вернуться к Html.TextBoxFor. Когда он отображает текстовое поле, ему нужно чтобы создать несколько вещей о свойство, которое вы ему даете. Такие вещи, как attributes на свойство для проверки, и в частности в этом случае он должен выяснить, что до имя the <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".

фух!!!

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


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

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

Итак, допустим, у вас была частичная, что просто визуализируется "баз" (из нашего примера ранее). Внутри этого частичного вы могли бы просто сказать:

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

, а не

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

это означает, что он будет генерировать тег "input", как это:

<input name="FooBar" />

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


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

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

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

@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 будет генерировать имена элементов в упорядоченном порядке, и поэтому у вас не будет никаких дополнительных проблем для извлечения объекта posted Theme в целом

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

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

EDIT

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

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

Я.е

@model IEnumerable<ViewModels.MyViewModels>


        @{
            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{
                   <span>No Theam avaiable</span>
            }
        }

это понятно из ошибки.

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

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

например

вместо TextboxFor(....) использовать Textbox ()

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

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

@for(int i=0;i<Model.Theme.Count;i++)
 {
   @Html.LabelFor(m=>m.Theme[i].name)
   @for(int j=0;j<Model.Theme[i].Products.Count;j++) )
     {
      @Html.LabelFor(m=>m.Theme[i].Products[j].name)
      @for(int k=0;k<Model.Theme[i].Products[j].Orders.Count;k++)
          {
           @Html.TextBoxFor(m=>Model.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)
      }
   }
}