Создание форм для глубоко вложенных View Model в ASP.NET MVCИсточник: habrahabrru 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. Достаточно легко создать для него представление: <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: <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! |