Давайте создадим простой проект, где практически весь код добавим самостоятельно. Это позволит лучше понять его структуру и принципы работы ASP.NET MVC 3.
Необходимо сразу отметить, что в следующих частях будет показано как можно упростить и ускорить разработку на начальном этапе. Для этого используются дополнительные инструменты, которые возьмут на себя часть рутинной работы. Поэтому все изменения, сделанные в этой части, временные и их необходимо будет удалить. Для этого можно использовать копию проекта BookCatalog (например, расположенную в другой папке) или же пересоздать его впоследствии.
Создаем Модель
Разработку начнем с Модели. В папке Models создадим класс BookDetails. Чтобы не усложнять пример добавим в него только три свойства: уникальный код (Id), название книги (Title) и имя её автора (Author):
namespace BookCatalog.Models
{
public class BookDetails
{
public int Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
}
}
Для хранения информации в эту же папку добавим класс BookRepository, который будет эмулировать работу с базой данных:
namespace BookCatalog.Models
{
using System;
using System.Collections.Generic;
public class BookRepository
{
private static int _nextId = 0;
private static readonly List<BookDetails> _book = new List<BookDetails>();
public List<BookDetails> Books { get { return _book; } }
public void Add(BookDetails newBook)
{
if (newBook == null) {
throw new ArgumentNullException("Parameter newBook can't be null.");
}
newBook.Id = _nextId;
_book.Add(newBook);
_nextId++;
}
}
}
Здесь присутствуют только метод Add() для добавления книг и свойство Books, которое предоставляет доступ к их полному списку.
Важный момент: после создания или изменения классов Модели необходимо скомпилировать проект. Это необходимо для создания или обновления его сборки. Тогда помощники ASP.NET MVC, которые будут использоваться в дальнейшем, смогут использовать актуальную информацию о классах. В частности, это относится к диалогу создания Представлений.
Модель готова к использованию. Следующий шаг – разработка Контроллера.
Разработка Контроллера
Для добавления Контроллера необходимо в Solution Explorer или Solution Navigator вызвать контекстное меню для папки Controllers и выбрать в нем пункт "Add > Controller...". В результате откроется диалог "Add Controller", упрощающий создание соответствующего класса.
Создаваемый Контроллер будет использоваться для работы с каталогом книг. Поэтому назовем его Catalog. В этом случае, согласно принятому в ASP.NET MVC соглашению, класс должен получить имя CatalogController. Укажем его в открывшемся диалоге. Кроме того, выберем опцию, указывающую что создаем пустой класс (Empty controller). Остальные настройки пока не важны, поэтому нажмем кнопку "Add".
В результате будет создан класс с единственным методом Index(), который является Действием по умолчанию. Добавим в него код для создания экземпляра Модели – списка книг. Полученные данные передадим в Представление используя метод Контроллера View().
namespace BookCatalog.Controllers
{
using System.Web.Mvc;
using BookCatalog.Models;
public class CatalogController : Controller
{
// GET: /Catalog/
// GET: /Catalog/Index/
public ActionResult Index()
{
var bookRepository = new BookRepository();
return this.View(bookRepository.Books);
}
}
}
Подобный класс можно создать также используя пункт "Add > Class". Однако в этом случае будет чуть больше работы, т.к. не будет использоваться шаблон Контроллера.
Следующий шаг – создание Действия Create для добавления данных книги в каталог. Здесь необходимо отметить, что оно состоит из двух этапов: отображение формы для ввода данных и их сохранение в репозитории. Соответственно, будет два метода.
С первым все просто. Create() будет создавать и передавать экземпляр класса BookDetails в Представление. В некоторых примерах можно встретить код, где в подобной ситуации просто передается значение null. Однако это плохой подход, т.к. приводит к созданию и обработке исключения.
// GET: /Catalog/Create/
public ActionResult Create()
{
return this.View(new BookDetails());
}
После отправки формы пользователем, ядро ASP.NET MVC получит экземпляр BookDetails. Оно постарается обратиться к методу с именем Действия при этом принимающему параметр указанного типа. Поэтому его описание будет следующим образом: Create(NewUserProfile newUser).
Необходимо отметить, что по умолчанию Действия в ASP.NET MVC отвечают только на запросы типа GET. Для отправки форм, как правило, используется POST-запрос. Поэтому, чтобы разрешить методу его обработку, необходимо отметить его атрибутом [HttpPost].
// POST: /Catalog/Create/
[HttpPost]
public ActionResult Create(BookDetails newBook)
{
var bookRepository = new BookRepository();
bookRepository.Add(newBook);
return this.RedirectToAction("Index");
}
После успешного сохранения данных веб-приложение вернется к списку. Для этого используется метод Контроллера RedirectToAction(), который осуществляет переход к указанному Действию. Добавим вывод сообщения о прошедшем событии. Для этого используем две коллекции, предоставляемые ASP.NET MVC:
- ViewBag является динамической (dynamic) коллекцией и предназначена для передачи данных от Контроллера к Представлению. Она обеспечивает доступ к помещенным в нее объектам в течении текущего запроса, после чего очищается. Кроме того, поддерживается коллекция ViewData, которая отличается только типом: IDictionary<string, object>. При этом обе коллекции указывают на одни и те же объекты, т.е. ViewBag.Message всегда равно ViewData["Message"].
- TempData это также коллекция типа IDictionary<string, object>. Но, в отличии от ViewData, объекты в ней сохраняются до первого их чтения или завершения сессии. Это подходит для передачи простых сообщений между Действиями.
Обратите внимание, что все указанные коллекции не являются строго типизированными. Поэтому использовать их следует очень осторожно. Также рекомендуется подробно документировать создаваемые в них свойства (например завести их перечень).
Условимся, что Представление, относящееся к Действию Index, будет использовать свойство ViewBag.Message для вывода сообщения. Для передачи текста между Действиями воспользуемся TempData.
В итоге, полный код Контроллера CatalogController будет выглядеть следующим образом:
namespace BookCatalog.Controllers
{
using System.Web.Mvc;
using BookCatalog.Models;
public class CatalogController : Controller
{
// GET: /Catalog/
// GET: /Catalog/Index/
public ActionResult Index()
{
this.ViewBag.Message = this.TempData["Message"];
var bookRepository = new BookRepository();
return this.View(bookRepository.Books);
}
// GET: /Catalog/Create/
public ActionResult Create()
{
return this.View(new BookDetails());
}
// POST: /Catalog/Create/
[HttpPost]
public ActionResult Create(BookDetails newBook)
{
var bookRepository = new BookRepository();
bookRepository.Add(newBook);
this.TempData["Message"] = "The book has been added to repository.";
return this.RedirectToAction("Index");
}
}
}
Обратите внимание, что после обращения к TempData["Message"], ядро ASP.NET MVC удалит ссылку на объект с этим индексом из коллекции. Поэтому при следующем обращении к Представлению Index, например при обновлении страницы, сообщение исчезнет.
Для завершения проекта не хватает Представлений и небольшой настройки.
Добавляем Представления
Шаблон разметки страниц
Перед тем как создать первое представление, давайте посмотрим на содержимое файла _Layout.cshtml. Это шаблон разметки страниц, созданный по умолчанию. Разработчики, знакомые с WebForms, сразу проведут аналогию с главными страницами (master pages).
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script>
</head>
<body>
@RenderBody()
</body>
</html>
Шаблон разметки позволяет задать единую структуру для всех или группы страниц веб-приложения. Он содержит общий код, в который будут вставлены Представления на место вызова метода RenderBody().
Свойство ViewBag.Title используется для установки текста заголовка страницы. Его значение будет определяться непосредственно в Представлениях.
Для указания используемого шаблона разметки используется конструкция:
@{ Layout = "~/Views/Shared/_Layout.cshtml"; }
Чтобы не повторять этот код в каждом Представлении, он вынесен в файл _ViewStart.cshtml. При этом всегда можно указать другой шаблон разметки для выбранного Представления, задав в нем нужное значение Layout. Также есть возможность отключить их использование, присвоив данному свойству null.
Представление для отображения списка книг (Index)
Код Представлений можно создать самостоятельно. Для этого в папке Views необходимо создать папку. Её имя, как правило, совпадает с именем Контроллера, который использует данное Представление. А в ней уже размещаются файлы с разметкой, которые называются аналогично Действиям.
Однако, ASP.NET MVC позволяет автоматизировать данный процесс. Поэтому чтобы создать Представление достаточно в редакторе Visual Studio, внутри кода Действия, вызвать контекстное меню и выбрать пункт "Add View…". В результате будет открыт диалог создания Представления. Создадим подобным образом Представление для Действия Index:
Имя (Index) и используемый движок (Razor) оставим без изменения.
Следующий пункт позволяет определить будет ли создаваемое Представление строго-типизированным (strongly-typed view). Это означает, что передаваемая ему Модель обязательно должна соответствовать заданному типу. В данном случае это будет класс BookDetails. Также в пункте Scaffold template выберем шаблоном List. Это автоматически добавит в Представление код вывода списка. Кроме того, оставим отметку для добавления ссылок на необходимые скрипты (Reference script libraries).
Остальные пункты пока не важны. Поэтому оставим их значения по умолчанию и нажмем кнопку "Add". В папке Views/Catalog будет создан файл Index.cshtml. Несколько изменим его код. Уберем все ссылки, за исключением необходимой для перехода на страницу добавления данных книги. Добавим вывод сообщения, переданного Контроллером через ViewBag.Message. В итоге получится следующий код (переформатирован для компактности):
@model IEnumerable<BookCatalog.Models.BookDetails>
@{ ViewBag.Title = "Index"; }
<h2>@ViewBag.Title</h2>
<p>@ViewBag.Message</p>
<p>@Html.ActionLink("Create New", "Create")</p>
<table>
<tr>
<th>Title</th>
<th>Author</th>
</tr>
@foreach (var item in Model) {
<tr>
<td>@Html.DisplayFor(modelItem => item.Title)</td>
<td>@Html.DisplayFor(modelItem => item.Author)</td>
</tr>
}
</table>
Рассмотрим его подробнее:
- ключевое слово @model показывает что Представление строго-типизированное (тип BookDetails);
- создается свойство ViewBag.Title и устанавливается его значение, которое будет использовано в шаблоне разметки для заголовка страницы;
- конструкция вида @ViewBag.Message используется для вывода значения указанной переменной (в данном случае это текст сообщения);
- метод ActionLink() создаёт код ссылки на указанное Действие;
- для формирования таблицы с перечнем книг используется C# конструкция foreach;
- вызовы метода DisplayFor() предназначены для вывода значений переменных;
Теперь перейдем к созданию Представления для Действия Create.
Представление для добавления данных (Create)
Создание этого Представления аналогично предыдущему, за исключением двух моментов:
- кон��екстное меню можно вызывать для любого из двух методов Create();
- в пункте Scaffold template используется шаблон Create.
Указываем маршрут для команд
Если сейчас запустить созданный проект, то результатом будет сообщение об ошибке. Причина заключена в обращении к несуществующему Контроллеру Home, который используется по умолчанию. В данном проекте вместо него необходимо указать созданный CatalogController. Для этого следует подставить соответствующее значение в методе RegisterRoutes() (файл Global.asax):
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new {
controller = "Catalog",
action = "Index",
id = UrlParameter.Optional
} // Parameter defaults
);
}
Запустим созданное веб-приложение. Оно отображает список книг и позволяет пополнять его.
Разработку можно продолжить добавляя Действия и Представления. В дальнейшем также могут появиться новые классы Модели и Контроллеры. Не стоит забывать о необходимости заменить эмулятор на реальную базу данных. При этом надо учесть, что обновленный ASP.NET MVC 3 может сильно упростить эту задачу и ускорить разработку. Как именно? Это будет показано в дальнейшем.
Созданный веб-проект временно можно не удалять. Он пригодится для экспериментов с NuGet, который рассмотрим в следующей части.