Поделиться через


Часть 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.

Снимок экрана: окно контроллера Диспетчера магазинов, открытое в Microsoft Visual Web Developer 2010 Express после создания.

Новый класс контроллера 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);
}

Это действие имеет четыре обязанности:

    1. Чтение значений формы
    1. Проверьте, проходят ли значения формы какие-либо правила проверки.
    1. Если отправка формы действительна, сохраните данные и отобразите обновленный список.
    1. Если отправка формы недопустима, повторное воспроизведение формы с ошибками проверки

Чтение значений формы с помощью привязки модели

Действие контроллера обрабатывает отправку формы, которая включает значения для 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 выполняет следующие действия:

    1. Загружает альбом по идентификатору
    1. Удаляет альбом и сохраняет изменения.
    1. Перенаправление на индекс, показывающее, что альбом был удален из списка.

Чтобы проверить это, запустите приложение и перейдите по папке /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