Как стать автором
Обновить

Создание форм для глубоко вложенных View Model в ASP.NET MVC

Время на прочтение5 мин
Количество просмотров5.7K
Автор оригинала: Jimmy Bogard
Ёще один интересный пост от Jimmy Bogard, посвященный cозданию форм для глубоко вложенных View Model в ASP.NET MVC. Несмотря на то, что в нём постоянно идёт отсылка к ASP.NET MVC 2, информация актуальна и для 3-ей версии. Под хабракатом оригинальный пост в вольном переводе.


ASP.NET MVC 2 представил множество строго типизированных помощников (helpers) для создания элементов форм в строго типизированных представлениях (views). Эти строго типизированные помощники используют лямбда выражения (lambda expressions) для того, чтобы создать полностью готовый элемент ввода, включая корректное имя и значение для элемента.

Лямбда выражения достаточно выразительны. Они позволяют создавать вам достаточно сложные модели для редактирования и иметь привязку модели (model binding) для того, чтобы сложить всё вместе. Пример сложного типа view model:
public class ProductEditModel
{
  public string Name { get; set; }
  public PriceEditModel Price { get; set; }

  public class PriceEditModel
  {
    public decimal Value { get; set; }
    public string Currency { get; set; }
  }
}

* This source code was highlighted with Source Code Highlighter.

Достаточно легко создать для него представление:
@using (Html.BeginForm()) {
  <p>
    @Html.LabelFor(m => m.Name)
    @Html.TextBoxFor(m => m.Name)
  </p>
  <p>
    @Html.LabelFor(m => m.Price.Currency)
    @Html.TextBoxFor(m => m.Price.Currency)
  </p>
  <p>
    @Html.LabelFor(m => m.Price.Value)
    @Html.TextBoxFor(m => m.Price.Value)
  </p>
}

* This source code was highlighted with Source Code Highlighter.

До тех пор, пока мы используем выражения, строящиеся с самого верхнего уровня модели, чтобы создать элементы ввода, корректный HMTL будет получен. Предположим, что вы хотите сейчас вытащить PriceEditModel в частичное представление и отделить его от родительского представления. Мы меняем в нашем представлении рендеринг свойства на рендеринг частичного представления:
@using (Html.BeginForm()) {
  <p>
    @Html.LabelFor(m => m.Name)
    @Html.TextBoxFor(m => m.Name)
  </p>
  @Html.Partial("_PriceEditModel", Model.Price);
}

* This source code was highlighted with Source Code Highlighter.

Наше частичное представление представляет собой просто вырезанный код представления, за исключением того, что сейчас оно основано на типе PriceEditModel:
@model ProductEditModel.PriceEditModel

<p>
  @Html.LabelFor(m => m.Currency)
  @Html.TextBoxFor(m => m.Currency)
</p>
<p>
  @Html.LabelFor(m => m.Value)
  @Html.TextBoxFor(m => m.Value)
</p>

* This source code was highlighted with Source Code Highlighter.

Однако, получающийся в результате HTML больше не сопоставляет члены модели корректно. Несмотря на то, что на экране всё в порядке:


Но стоит нам заглянуть в HTML, как мы увидим ошибку:


Вместо имени нашего члена, имеющего правильный родительский член в своём имени, вроде “Price.Currency”, мы видим только “Currency”. Действительно, когда мы попадаем в POST действие (action), член Price равен null, т.к. привязка модели не смогла найти соответствие:


Не совсем то, что нам хотелось бы получить!

Итак, какие у нас варианты? Для уверенности в том, что привязка модели работает для моделей с частичным представлением, мы можем привести эти модели в наших частичных представлениях к родительскому типу. Т.е. заменить типы наших моделей для частичных представлений с “PriceEditModel” на “ProductEditModel”.

Не очень привлекательный вариант!

У нас есть вариант получше – шаблонизированные помощники из MVC 2. Шаблонизированные помощники элегантно решают проблему глубоко вложенных View Model.

Работа с шаблонизированными помощниками


Шаблонизированные помощники отличаются от частичных представлений тем, что в них специальная контекстная информация передаётся вниз от родителя к потомку тогда, когда мы используем методы Html.EditorXyz() из HtmlHelper. Для того чтобы перестроить наши представления на использование шаблонизированных помощников, давайте просто создадим шаблон редактора для каждой view model, которая у нас есть:


Эти шаблоны обычные частичные представления из Razor, за исключением того, что они помещаются в специальную папку EditorTemplates. Для нашего частичного представления c ProductEditModel, мы просто перемещаем всё, что у нас было в нашем представлении:
@model ProductEditModel

<p>
  @Html.LabelFor(m => m.Name)
  @Html.TextBoxFor(m => m.Name)
</p>
@Html.EditorFor(m => m.Price)

* This source code was highlighted with Source Code Highlighter.

Однако, здесь есть одна незначительная деталь. Вместо рендеринга частичного представления Price, мы выполняем рендеринг редактор для члена Price. Шаблон PriceEditModel – это то, что у нас было в нашем оригинальном частичном представлении без каких-либо изменений:
@model ProductEditModel.PriceEditModel

<p>
  @Html.LabelFor(m => m.Currency)
  @Html.TextBoxFor(m => m.Currency)
</p>
<p>
  @Html.LabelFor(m => m.Value)
  @Html.TextBoxFor(m => m.Value)
</p>

* This source code was highlighted with Source Code Highlighter.

На данный момент отличие заключается в том, что наш шаблонизированный помощник знает, что родительская модель использовала член “Price” для создания частичного представления. В нашем родительском представлении Edit всё ещё проще:
@using (Html.BeginForm()) {
  @Html.EditorForModel()
  <input type="submit" />
}

* This source code was highlighted with Source Code Highlighter.

ASP.NET MVC будет проверять тип модели для того, чтобы убедиться в том, что шаблон редактора существует для этого типа модели, когда мы вызываем метод EditorForModel. Т.к. мы создаём шаблоны редактора для каждого отдельного типа модели, не имеет значения, где в иерархии расположены эти вложенные типы. ASP.NET MVC будет передавать родительский контекст, так что глубоко вложенные View Model будут иметь корректную информацию о нём.

Просмотрев получившийся в результате HTML, мы можем убедиться, что всё в порядке:


Имя элемента ввода сейчас имеет корректное имя родительского свойства в качестве значения. А отладка POST действия подтверждает, что привязка модели сейчас работает корректно:


С шаблонизированными помощниками из ASP.NET MVC 2, мы можем создавать вложенные модели в наших представлениях и, вместе с тем, получать все преимущества частичных представлений. Единственное предостережение – убедитесь, что вы создали представления, используя шаблонизированные помощники и Html.EditorXyz методы. В противном случае, воздействие на ваши представления будет минимальным.

И просто чтобы пожаловаться – этот способ сильно раздражает в MVC 1.0. Я выкинул кучу кода после того, как перешёл на старшие версии MVC!
Теги:
Хабы:
+10
Комментарии14

Публикации