Часть 5. Изменение форм и создание шаблонов
Хранилище музыки MVC — это учебное приложение, в которое пошаговые инструкции по использованию ASP.NET MVC и Visual Studio для веб-разработки.
MVC Music Store — это упрощенный пример реализации магазина, который продает музыкальные альбомы в Интернете и реализует базовые функции администрирования сайтов, входа пользователей и корзины для покупок.
В этой серии учебников подробно описаны все действия по созданию примера приложения ASP.NET MVC Music Store. В части 5 рассматривается изменение форм и шаблонов.
В предыдущей главе мы загружали данные из нашей базы данных и отображали их. В этой главе мы также включим редактирование данных.
Создание StoreManagerController
Начнем с создания контроллера с именем StoreManagerController. Для этого контроллера мы будем использовать функции формирования шаблонов, доступные в обновлении средств ASP.NET MVC 3. Задайте параметры для диалогового окна Добавление контроллера, как показано ниже.
При нажатии кнопки Добавить вы увидите, что механизм формирования шаблонов MVC 3 ASP.NET выполняет большую работу:
- Он создает новый StoreManagerController с локальной переменной Entity Framework.
- Она добавляет папку StoreManager в папку "Представления" проекта.
- Он добавляет представления Create.cshtml, Delete.cshtml, Details.cshtml, Edit.cshtml и Index.cshtml, строго типизированные в класс Album.
Новый класс контроллера StoreManager включает действия контроллера CRUD (создание, чтение, обновление, удаление), которые знают, как работать с классом модели Album и использовать контекст Entity Framework для доступа к базе данных.
Изменение представления с шаблонами
Важно помнить, что, хотя этот код был создан для нас, он является стандартным ASP.NET кода MVC, как мы писали в этом руководстве. Он предназначен для экономии времени, которое вы потратили бы на написание стандартного кода контроллера и создание строго типизированных представлений вручную, но это не тот тип созданного кода, который вы могли видеть предваряющимся ужасными предупреждениями в комментариях о том, как вы не должны изменять код. Это ваш код, и вы должны изменить его.
Итак, давайте начнем с быстрого редактирования представления индекса StoreManager (/Views/StoreManager/Index.cshtml). В этом представлении отобразится таблица, в которой перечислены альбомы в нашем магазине со ссылками "Изменить", "Сведения" и "Удалить", а также общедоступные свойства альбома. Мы удалим поле AlbumArtUrl, так как оно не очень полезно в этом отображении. В <разделе таблицы> кода представления удалите <элементы th> и <td> , окружающие ссылки AlbumArtUrl, как указано в выделенных строках ниже:
<table>
<tr>
<th>
Genre
</th>
<th>
Artist
</th>
<th>
Title
</th>
<th>
Price
</th>
<th>
AlbumArtUrl
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Genre.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Artist.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.AlbumArtUrl)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.AlbumId }) |
@Html.ActionLink("Details", "Details", new { id=item.AlbumId }) |
@Html.ActionLink("Delete", "Delete", new { id=item.AlbumId })
</td>
</tr>
}
</table>
Измененный код представления будет выглядеть следующим образом:
@model IEnumerable<MvcMusicStore.Models.Album>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create
New", "Create")
</p>
<table>
<tr>
<th>
Genre
</th>
<th>
Artist
</th>
<th>
Title
</th>
<th>
Price
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Genre.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Artist.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.AlbumId }) |
@Html.ActionLink("Details", "Details", new { id=item.AlbumId }) |
@Html.ActionLink("Delete", "Delete", new { id=item.AlbumId })
</td>
</tr>
}
</table>
Первый взгляд на диспетчер магазина
Теперь запустите приложение и перейдите по папке /StoreManager/. Здесь отображается только что измененный индекс диспетчера магазинов, а также список альбомов в магазине со ссылками на пункты Изменить, Сведения и Удалить.
Если щелкнуть ссылку Изменить, откроется форма редактирования с полями для альбома, включая раскрывающиеся списки Жанр и Исполнитель.
Щелкните ссылку "Назад к списку" внизу, а затем щелкните ссылку Сведения для альбома. При этом отображаются подробные сведения об отдельном альбоме.
Снова щелкните ссылку Назад к списку, а затем щелкните ссылку Удалить. Откроется диалоговое окно подтверждения, в котором отображаются сведения об альбоме и запрашивается, действительно ли мы хотим удалить его.
Нажатие кнопки Удалить внизу приведет к удалению альбома и возврату на страницу Индекс, на которой отображается удаленный альбом.
Мы не закончили работу с диспетчером магазинов, но у нас есть рабочий контроллер и код просмотра для операций CRUD, с которых нужно начать.
Просмотр кода контроллера Диспетчера магазинов
Контроллер диспетчера магазинов содержит хороший объем кода. Давайте рассмотрим это сверху вниз. Контроллер включает некоторые стандартные пространства имен для контроллера MVC, а также ссылку на наше пространство имен Models. Контроллер имеет частный экземпляр MusicStoreEntities, используемый каждым из действий контроллера для доступа к данным.
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcMusicStore.Models;
namespace MvcMusicStore.Controllers
{
public class StoreManagerController : Controller
{
private MusicStoreEntities db = new MusicStoreEntities();
Действия с индексами и сведениями диспетчера магазинов
В представлении индекса извлекается список альбомов, включая указанные в каждом альбоме сведения о жанре и исполнителе, как мы видели ранее при работе с методом Обзор в Магазине. Представление Индекс следует за ссылками на связанные объекты, чтобы отобразить имя жанра и имя исполнителя каждого альбома, чтобы контроллер был эффективным и запрашивал эти сведения в исходном запросе.
//
// GET: /StoreManager/
public ViewResult Index()
{
var albums = db.Albums.Include(a => a.Genre).Include(a => a.Artist);
return View(albums.ToList());
}
Действие контроллера сведений контроллера StoreManager работает точно так же, как и действие Сведения о контроллере магазина, созданное ранее. Оно запрашивает альбом по идентификатору с помощью метода Find(), а затем возвращает его в представление.
//
// GET: /StoreManager/Details/5
public ViewResult Details(int id)
{
Album album = db.Albums.Find(id);
return View(album);
}
Создание методов действия
Методы действий Create немного отличаются от тех, которые мы видели до сих пор, так как они обрабатывают входные данные формы. Когда пользователь впервые посещает /StoreManager/Create/, ей будет показана пустая форма. Эта HTML-страница будет содержать <элемент формы> , содержащий элементы раскрывающегося списка и текстового поля, где можно вводить сведения об альбоме.
После заполнения значений формы Альбом пользователь может нажать кнопку "Сохранить", чтобы отправить эти изменения обратно в наше приложение для сохранения в базе данных. Когда пользователь нажимает кнопку "Сохранить", <форма> выполняет http-POST обратно по URL-адресу /StoreManager/Create/ и отправляет <значения формы> как часть HTTP-POST.
ASP.NET MVC позволяет легко разделить логику этих двух сценариев вызова URL-адресов, позволяя реализовать два отдельных метода действия "Создать" в классе StoreManagerController: один для обработки начального перехода HTTP-GET по URL-адресу /StoreManager/Create/, а другой для обработки HTTP-POST отправленных изменений.
Передача данных в представление с помощью ViewBag
Мы использовали ViewBag ранее в этом руководстве, но не говорили о нем. ViewBag позволяет передавать сведения в представление без использования строго типизированного объекта модели. В этом случае наше действие изменить контроллер HTTP-GET должно передать в форму список жанров и исполнителей для заполнения раскрывающихся списков, а самый простой способ сделать это — вернуть их в виде элементов ViewBag.
ViewBag — это динамический объект, то есть вы можете вводить ViewBag.Foo или ViewBag.YourNameHere без написания кода для определения этих свойств. В этом случае код контроллера использует ViewBag.GenreId и ViewBag.ArtistId, чтобы значения раскрывающегося списка, отправленные в форме, были GenreId и ArtistId, которые являются свойствами альбома, которые они будут задавать.
Эти раскрывающиеся значения возвращаются в форму с помощью объекта SelectList, который создан именно для этой цели. Для этого используется следующий код:
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name");
Как видно из кода метода действия, для создания этого объекта используются три параметра:
- Список элементов, который будет отображаться в раскрывающемся списке. Обратите внимание, что это не просто строка. Мы передаём список жанров.
- Следующий параметр, передаваемый в SelectList, — выбранное значение. Таким образом SelectList знает, как предварительно выбрать элемент в списке. Это будет проще понять, когда мы рассмотрим форму редактирования, которая очень похожа.
- Окончательный параметр — это отображаемое свойство. В этом случае это означает, что пользователю будет отображаться свойство Genre.Name.
С учетом этого действие СОЗДАНИЕ HTTP-GET довольно простое — в ViewBag добавляются два списка SelectList, и в форму не передается объект модели (так как он еще не создан).
//
// GET: /StoreManager/Create
public ActionResult Create()
{
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name");
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name");
return View();
}
Вспомогательные функции HTML для отображения раскрывающихся списков в представлении создания
Так как мы говорили о том, как раскрывающиеся значения передаются в представление, давайте кратко рассмотрим представление, чтобы увидеть, как эти значения отображаются. В коде представления (/Views/StoreManager/Create.cshtml) вы увидите, что выполняется следующий вызов для отображения раскрывающегося списка Жанр.
@Html.DropDownList("GenreId",
String.Empty)
Это называется вспомогательной службой HTML — служебным методом, который выполняет общую задачу представления. Вспомогательные функции HTML очень полезны для поддержания краткости и удобочитаемого кода представления. Вспомогающее средство Html.DropDownList предоставляется ASP.NET MVC, но, как мы увидим позже, можно создать собственные вспомогательные средства для просмотра кода, который мы будем повторно использовать в нашем приложении.
Вызову Html.DropDownList просто необходимо указать две вещи: где получить список для отображения и какое значение (если таковое имеется) следует предварительно выбрать. Первый параметр, GenreId, указывает DropDownList на поиск значения с именем GenreId в модели или ViewBag. Второй параметр используется для указания значения, которое будет отображаться как первоначально выбранное в раскрывающемся списке. Так как эта форма является формой Создания, не существует предварительно выбранного значения и передается String.Empty.
Обработка значений публикуемой формы
Как мы уже говорили ранее, с каждой формой связаны два метода действия. Первый обрабатывает запрос HTTP-GET и отображает форму. Второй обрабатывает запрос HTTP-POST, который содержит отправленные значения формы. Обратите внимание, что действие контроллера имеет атрибут [HttpPost], который сообщает ASP.NET MVC, что оно должно отвечать только на запросы HTTP-POST.
//
// POST: /StoreManager/Create
[HttpPost]
public ActionResult Create(Album album)
{
if (ModelState.IsValid)
{
db.Albums.Add(album);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name", album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name", album.ArtistId);
return View(album);
}
Это действие имеет четыре обязанности:
-
- Чтение значений формы
-
- Проверьте, проходят ли значения формы какие-либо правила проверки.
-
- Если отправка формы действительна, сохраните данные и отобразите обновленный список.
-
- Если отправка формы недопустима, повторное воспроизведение формы с ошибками проверки
Чтение значений формы с помощью привязки модели
Действие контроллера обрабатывает отправку формы, которая включает значения для GenreId и ArtistId (из раскрывающегося списка) и значения текстового поля для Title, Price и AlbumArtUrl. Хотя можно получить прямой доступ к значениям формы, лучше использовать возможности привязки модели, встроенные в ASP.NET MVC. Когда действие контроллера принимает тип модели в качестве параметра, ASP.NET MVC попытается заполнить объект этого типа с помощью входных данных формы (а также значений route и querystring). Для этого выполняется поиск значений, имена которых соответствуют свойствам объекта модели, например при задании значения GenreId нового объекта Album выполняется поиск входных данных с именем GenreId. При создании представлений с помощью стандартных методов в ASP.NET MVC формы всегда будут отображаться с использованием имен свойств в качестве имен полей ввода, поэтому имена полей будут просто совпадать.
Проверка модели
Модель проверяется простым вызовом ModelState.IsValid. Мы еще не добавили никаких правил проверки в наш класс альбома - мы сделаем это немного - так что прямо сейчас этот проверка не имеет много действий. Важно то, что этот проверка ModelStat.IsValid будет адаптироваться к правилам проверки, которые мы ввели в нашу модель, поэтому будущие изменения правил проверки не потребуют обновления кода действия контроллера.
Сохранение отправленных значений
Если отправка формы проходит проверку, пришло время сохранить значения в базе данных. В Entity Framework требуется просто добавить модель в коллекцию Albums и вызвать SaveChanges.
db.Albums.Add(album);
db.SaveChanges();
Entity Framework создает соответствующие команды SQL для сохранения значения. После сохранения данных мы перенаправляемся обратно в список альбомов, чтобы увидеть наше обновление. Это делается путем возврата RedirectToAction с именем действия контроллера, которое требуется отобразить. В этом случае это метод Index.
Отображение недопустимых отправлений форм с ошибками проверки
В случае недопустимых входных данных формы значения раскрывающегося списка добавляются в ViewBag (как в случае HTTP-GET), а привязанные значения модели передаются обратно в представление для отображения. Ошибки проверки автоматически отображаются с помощью вспомогательного @Html.ValidationMessageFor средства HTML.
Тестирование формы создания
Чтобы проверить это, запустите приложение и перейдите по адресу /StoreManager/Create/ — здесь отобразится пустая форма, возвращенная методом HTTP-GET Создания StoreController.
Заполните некоторые значения и нажмите кнопку Создать, чтобы отправить форму.
Обработка изменений
Пара действий "Изменить" (HTTP-GET и HTTP-POST) очень похожа на методы создания действий, которые мы только что рассмотрели. Так как сценарий редактирования предполагает работу с существующим альбомом, метод Edit HTTP-GET загружает альбом на основе параметра id, переданного по маршруту. Этот код для получения альбома с помощью AlbumId такой же, как мы смотрели ранее в действии контроллера Сведений. Как и в случае с методом Create/HTTP-GET, значения раскрывающегося списка возвращаются через ViewBag. Это позволяет возвращать альбом в качестве объекта модели в представление (строго типизированное для класса Album) при передаче дополнительных данных (например, списка жанров) через ViewBag.
//
// GET: /StoreManager/Edit/5
public ActionResult Edit(int id)
{
Album album = db.Albums.Find(id);
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name", album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name", album.ArtistId);
return View(album);
}
Действие Изменить HTTP-POST очень похоже на действие Создать HTTP-POST. Единственное отличие заключается в том, что вместо добавления нового альбома в базу данных. Коллекция альбомов, мы находим текущий экземпляр альбома с помощью базы данных. Запись(альбом) и задание ей состояния Изменено. Это сообщает Entity Framework, что мы изменяем существующий альбом, а не новый.
//
// POST: /StoreManager/Edit/5
[HttpPost]
public ActionResult Edit(Album album)
{
if (ModelState.IsValid)
{
db.Entry(album).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name", album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name", album.ArtistId);
return View(album);
}
Мы можем проверить это, запустив приложение и перейдя по ссылке /StoreManger/, а затем щелкнув ссылку Изменить для альбома.
Откроется форма редактирования, отображаемая методом Edit HTTP-GET. Введите некоторые значения и нажмите кнопку Сохранить.
Эта форма публикует, сохраняет значения и возвращает нас в список Альбом, показывая, что значения были обновлены.
Обработка удаления
Удаление выполняется по тому же шаблону, что и "Изменить" и "Создать", используя одно действие контроллера для отображения формы подтверждения, а другое действие контроллера для обработки отправки формы.
Действие http-GET Delete controller (Удаление HTTP-GET) точно такое же, как и предыдущее действие контроллера сведений диспетчера магазинов.
//
// GET: /StoreManager/Delete/5
public ActionResult Delete(int id)
{
Album album = db.Albums.Find(id);
return View(album);
}
Мы отобразим форму, которая строго типизирована с типом "Альбом" с помощью шаблона Удалить содержимое представления.
В шаблоне Удалить отображаются все поля для модели, но мы можем немного упростить это. Измените код представления в файле /Views/StoreManager/Delete.cshtml на следующий.
@model MvcMusicStore.Models.Album
@{
ViewBag.Title = "Delete";
}
<h2>Delete Confirmation</h2>
<p>Are you sure you want to delete the album titled
<strong>@Model.Title</strong>?
</p>
@using (Html.BeginForm()) {
<p>
<input type="submit" value="Delete" />
</p>
<p>
@Html.ActionLink("Back to
List", "Index")
</p>
}
Отобразится упрощенное подтверждение удаления.
При нажатии кнопки Удалить форма отправляется обратно на сервер, который выполняет действие DeleteConfirmed.
//
// POST: /StoreManager/Delete/5
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Album album = db.Albums.Find(id);
db.Albums.Remove(album);
db.SaveChanges();
return RedirectToAction("Index");
}
Действие удаления контроллера HTTP-POST выполняет следующие действия:
-
- Загружает альбом по идентификатору
-
- Удаляет альбом и сохраняет изменения.
-
- Перенаправление на индекс, показывающее, что альбом был удален из списка.
Чтобы проверить это, запустите приложение и перейдите по папке /StoreManager. Выберите альбом из списка и щелкните ссылку Удалить.
Откроется экран подтверждения удаления.
Нажатие кнопки Удалить удаляет альбом и возвращает нас на страницу Индекса диспетчера магазинов, на которой показано, что альбом был удален.
Использование пользовательской вспомогательной функции HTML для усечения текста
У нас есть одна потенциальная проблема со страницей индекса диспетчера магазинов. Свойства названия альбома и имени исполнителя могут быть достаточно длинными, чтобы отбросить форматирование таблицы. Мы создадим настраиваемую вспомогатель HTML, чтобы мы могли легко усечь эти и другие свойства в представлениях.
Синтаксис Razor @helper упрощает создание собственных вспомогательных функций для использования в представлениях. Откройте представление /Views/StoreManager/Index.cshtml и добавьте следующий код сразу после @model строки.
@helper Truncate(string
input, int length)
{
if (input.Length <= length) {
@input
} else {
@input.Substring(0, length)<text>...</text>
}
}
Этот вспомогательный метод принимает строку и максимальную длину. Если предоставленный текст короче указанной длины, помощник выводит его как есть. Если он длиннее, он усекает текст и отображает "..." для оставшейся части.
Теперь мы можем использовать наш вспомогательный элемент Truncate, чтобы убедиться, что свойства Название альбома и Имя исполнителя имеют менее 25 символов. Ниже приведен полный код представления, использующий наш новый вспомогательный элемент Truncate.
@model IEnumerable<MvcMusicStore.Models.Album>
@helper Truncate(string input, int length)
{
if (input.Length <= length) {
@input
} else {
@input.Substring(0, length)<text>...</text>
}
}
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create
New", "Create")
</p>
<table>
<tr>
<th>
Genre
</th>
<th>
Artist
</th>
<th>
Title
</th>
<th>
Price
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Genre.Name)
</td>
<td>
@Truncate(item.Artist.Name, 25)
</td>
<td>
@Truncate(item.Title, 25)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.AlbumId }) |
@Html.ActionLink("Details", "Details", new { id=item.AlbumId }) |
@Html.ActionLink("Delete", "Delete", new { id=item.AlbumId })
</td>
</tr>
}
</table>
Теперь, когда мы просматриваем URL-адрес /StoreManager/, альбомы и названия сохраняются ниже нашей максимальной длины.
Примечание. Здесь показан простой случай создания и использования вспомогательного средства в одном представлении. Дополнительные сведения о создании вспомогательных приложений, которые можно использовать на всем сайте, см. в записи блога: http://bit.ly/mvc3-helper-options