Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p

.pdf
Скачиваний:
25
Добавлен:
19.03.2016
Размер:
17.66 Mб
Скачать

SportsStore: администрирование

В этой главе мы продолжим строить приложение SportsStore и создадим средства управления каталогом товаров для администратора сайта. Мы добавим поддержку создания, редактирования и удаления объектов из хранилища, а также загрузки изображений и их отображения вместе с описаниями товаров в каталоге.

Управление каталогом

По соглашению для управления коллекциями товаров мы должны предоставить пользователю два типа страниц: страницу со списком и страницу редактирования, как показано на рисунке 10-1.

Рисунок 10-1: Эскиз пользовательского интерфейса CRUD для каталога товаров

Вместе эти страницы позволяют пользователю создавать, читать, обновлять и удалять элементы в коллекции. В совокупности эти действия называются CRUD (Create-Read-Update-Delete – Создать- Прочитать-Обновить-Удалить). Разработчикам очень часто приходится реализовывать CRUD, так что Visual Studio предлагает сгенерировать MVC-контроллеры с готовыми методами действий для операций CRUD и шаблоны представлений, которые их поддерживают.

Создаем контроллер CRUD

Мы создадим новый контроллер для обработки функций администрирования. Кликните правой кнопкой мыши папку Controllers в проекте SportsStore.WebUI и выберите Add - Controller из контекстного меню. Назовите контроллер AdminController и выберите из выпадающего списка

Template пункт Empty MVC Controller, как показано на рисунке 10-2.

Примечание

В Visual Studio есть несколько шаблонов для классов контроллеров, которые включают методы CRUD. Как мы уже говорили, они нам не нравятся и мы предпочитаем создавать классы контроллеров с нуля.

241

Рисунок 10-2: Создание контроллера с помощью диалогового окна Add Controller

Нажмите кнопку Add, чтобы создать контроллер. Для поддержки страницы со списком, показанной на рисунке 10-1, нужно добавить метод действия, который будет отображать все товары в хранилище. Следуя соглашениям MVC Framework, мы назовем этот метод Index. Измените содержимое класса контроллера в соответствии с листингом 10-1.

Листинг 10-1: Метод действия Index

using SportsStore.Domain.Abstract; using System;

using System.Collections.Generic; using System.Linq;

using System.Web; using System.Web.Mvc;

namespace SportsStore.WebUI.Controllers

{

public class AdminController : Controller

{

private IProductRepository repository;

public AdminController(IProductRepository repo)

{

repository = repo;

}

public ViewResult Index()

{

return View(repository.Products);

}

}

}

242

Модульный тест: Действие Index

Поведение метода Index, которое нас интересует, состоит в том, правильно ли он возвращает объекты Product, которые находятся в хранилище. Мы можем протестировать его, создав имитацию реализации хранилища и сравнив тестовые данные с данными, возвращенными методом действия. Вот модульный тест, который мы добавили в новый файл тестов под названием AdminTests.cs:

using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq;

using SportsStore.Domain.Abstract; using SportsStore.Domain.Entities; using SportsStore.WebUI.Controllers; using System;

using System.Collections.Generic; using System.Linq;

using System.Web.Mvc;

namespace SportsStore.UnitTests

{

[TestClass]

public class AdminTests

{

[TestMethod]

public void Index_Contains_All_Products()

{

//Arrange - create the mock repository Mock<IProductRepository> mock = new Mock<IProductRepository>(); mock.Setup(m => m.Products).Returns(new Product[] {

new Product {ProductID = 1, Name = "P1"}, new Product {ProductID = 2, Name = "P2"}, new Product {ProductID = 3, Name = "P3"},

}.AsQueryable());

//Arrange - create a controller

AdminController target = new AdminController(mock.Object); // Action

Product[] result = ((IEnumerable<Product>)target.Index().ViewData.Model).ToArray();

// Assert Assert.AreEqual(result.Length, 3); Assert.AreEqual("P1", result[0].Name); Assert.AreEqual("P2", result[1].Name); Assert.AreEqual("P3", result[2].Name);

}

}

}

Создаем новый макет

Мы собираемся создать новый макет для представлений администрирования SportsStore. Эта простая разметка будет представлять собой единственную точку, в которой мы сможем применить изменения ко всем представлениям администрирования.

Чтобы создать макет, щелкните правой кнопкой мыши папку Views/Shared в проекте

SportsStore.WebUI и выберите Add - New Item. Выберите шаблон MVC 4 Layout Page (Razor) и

назовите его _AdminLayout.cshtml, как показано на рисунке 10-3. Нажмите кнопку Add, чтобы создать новый файл.

243

выберите пункт рисунке 10-4.

Рисунок 10-3: Создаем новый макет Razor

Как мы уже объясняли ранее, по соглашению имя макета начинается с символа подчеркивания (_). Razor также используется другой технологией Microsoft под названием WebMatrix, в которой символ подчеркивания нужен для того, чтобы предотвратить отправку страниц макетов в браузеры. В MVC такая защита не требуется, но соглашение об именах макетов так или иначе переносится на приложения MVC.

Мы хотим создать в макете ссылку на файл CSS, как показано в листинге 10-2.

Листинг 10-2: Файл _AdminLayout.cshtml

<!DOCTYPE html> <html>

<head>

<meta name="viewport" content="width=device-width" />

<link href="~/Content/Admin.css" rel="stylesheet" type="text/css" />

<title></title>

</head>

<body>

<div>

@RenderBody()

</div>

</body>

</html>

Дополнение (выделено жирным шрифтом) является ссылкой на файл CSS под названием Admin.css в папке Content. Чтобы создать файл Admin.css, кликните правой кнопкой мыши папку Content,

Add - New Item, шаблон Style Sheet и укажите имя Admin.css, как показано на

244

Рисунок 10-4: Создание файла Admin.css

Замените содержимое файла Admin.css на стили, показанные в листинге 10-3.

Листинг 10-3: CSS-стили для представлений администрирования

BODY, TD { font-family: Segoe UI, Verdana }

H1 { padding: .5em; padding-top: 0; font-weight: bold; font-size: 1.5em; border-bottom: 2px solid gray; }

DIV#content { padding: .9em; }

TABLE.Grid TD, TABLE.Grid TH { border-bottom: 1px dotted gray; text-align:left; } TABLE.Grid { border-collapse: collapse; width:100%; }

TABLE.Grid TH.NumericCol, Table.Grid TD.NumericCol { text-align: right; padding-right: 1em; }

FORM {margin-bottom: 0px; }

DIV.Message { background: gray; color:White; padding: .2em; margin-top:.25em; }

.field-validation-error { color: red; display: block; }

.field-validation-valid { display: none; }

.input-validation-error { border: 1px solid red; background-color: #ffeeee; }

.validation-summary-errors { font-weight: bold; color: red; }

.validation-summary-valid { display: none; }

Реализация представлений для страниц со списком

Теперь, когда новый макет готов, мы можем добавить в проект представление для метода действия Index контроллера Admin. Кликните правой кнопкой мыши по методу Index и выберите Add View из контекстного меню. Назовите представление Index, как показано на рисунке 10-5.

245

Рисунок 10-5: Создаем представление Index

Мы собираемся использовать заготовку (scaffold view) – то есть, представление, для которого Visual Studio сама создаст разметку в зависимости от того, какой мы выберем класс для строго типизированного представления. Чтобы ее создать, выберите Product из списка классов модели и шаблон заготовки List, как показано на рисунке 10-5.

Примечание

Когда вы используете заготовку List, Visual Studio предполагает, что вы работаете с последовательностью IEnumerable типа модели представления, так что вы можете просто выбрать одиночную форму класса из списка.

Мы хотим применить наш вновь созданный макет, так что отметьте флажком опцию Use a layout и выберите файл _AdminLayout.cshtml из папки Views/Shared. Нажмите кнопку Add, чтобы создать представление. Заготовка, которую создаст Visual Studio, показана в листинге 10-4 (мы ее немного подчистили, чтобы сделать более компактной и читабельной).

246

Листинг 10-4: Заготовка представления для страниц со списком

@model IEnumerable<SportsStore.Domain.Entities.Product>

@{

ViewBag.Title = "Index";

Layout = "~/Views/Shared/_AdminLayout.cshtml";

}

<h2>Index</h2>

<p>

@Html.ActionLink("Create New", "Create") </p>

<table>

<tr>

<th>@Html.DisplayNameFor(model => model.Name)</th> <th>@Html.DisplayNameFor(model => model.Description)</th> <th>@Html.DisplayNameFor(model => model.Price)</th> <th>@Html.DisplayNameFor(model => model.Category)</th> <th></th>

</tr>

@foreach (var item in Model) { <tr>

<td>@Html.DisplayFor(modelItem => item.Name)</td> <td>@Html.DisplayFor(modelItem => item.Description)</td> <td>@Html.DisplayFor(modelItem => item.Price)</td> <td>@Html.DisplayFor(modelItem => item.Category)</td> <td>

@Html.ActionLink("Edit", "Edit", new { id = item.ProductID }) | @Html.ActionLink("Details", "Details", new { id = item.ProductID }) | @Html.ActionLink("Delete", "Delete", new { id = item.ProductID })

</td>

</tr>

}

</table>

Visual Studio смотрит на тип объекта модели представления и генерирует в таблице элементы, которые соответствуют его свойствам. Вы можете увидеть, как визуализируется это представление, если запустите приложение и перейдете по ссылке /Admin/Index. Результаты показаны на рисунке

10-6.

Рисунок 10-6: Визуализация представления для страниц со списком

247

Заготовка во многом помогает нам с настройками. У нас имеются столбцы для каждого из свойств класса Product и ссылки на другие операции CRUD, которые ведут к методам действий того же контроллера. Но стоит отметить, что она содержит излишнюю разметку. Кроме того, мы хотим использовать в представлении CSS, который мы создали ранее. Отредактируйте файл Index.cshtml в соответствии с листингом 10-5.

Листинг 10-5: Изменяем представление Index.cshtml

@model IEnumerable<SportsStore.Domain.Entities.Product>

@{

ViewBag.Title = "Admin: All Products";

Layout = "~/Views/Shared/_AdminLayout.cshtml";

}

<h1>All Products</h1> <table class="Grid">

<tr>

<th>ID</th>

<th>Name</th>

<th class="NumericCol">Price</th> <th>Actions</th>

</tr>

@foreach (var item in Model) { <tr>

<td>@item.ProductID</td>

<td>@Html.ActionLink(item.Name, "Edit", new { item.ProductID })</td> <td class="NumericCol">@item.Price.ToString("c")</td>

<td>

@using (Html.BeginForm("Delete", "Admin")) { @Html.Hidden("ProductID", item.ProductID) <input type="submit" value="Delete" />

}

</td>

</tr>

}

</table>

<p>@Html.ActionLink("Add a new product", "Create")</p>

Это представление визуализирует информацию более компактно, оно опускает некоторые свойства из класса Product и по-другому представляет ссылки на конкретные товары. Вы можете увидеть, как оно выглядит, на рисунке 10-7.

Рисунок 10-7: Визуализация измененного представления Index

248

Теперь у нас готова страница со списком. Администратор может просматривать товары в каталоге, а также использовать ссылки или кнопки для добавления, удаления и просмотра элементов. В следующих разделах мы добавим функциональность для поддержки каждого из этих действий.

Редактирование товаров

Чтобы обеспечить поддержку создания и обновления, мы добавим страницу редактирования товаров, как показано на рисунке 10-1. Эту работу мы выполним в два этапа:

Отобразим страницу, которая позволит администратору изменять значения свойств товара.

Добавим метод действия, который будет обрабатывать изменения после их отправки.

Создаем метод действия Edit

В листинге 10-6 показан метод Edit, который мы добавили к классу AdminController. Это метод действия, который мы указали в вызовах к вспомогательному методу действия Html.ActionLink в представлении Index.

Листинг 10-6: Метод Edit

using SportsStore.Domain.Abstract; using SportsStore.Domain.Entities; using System;

using System.Collections.Generic; using System.Linq;

using System.Web; using System.Web.Mvc;

namespace SportsStore.WebUI.Controllers

{

public class AdminController : Controller

{

private IProductRepository repository;

public AdminController(IProductRepository repo)

{

repository = repo;

}

public ViewResult Index()

{

return View(repository.Products);

}

public ViewResult Edit(int productId)

{

Product product = repository.Products

.FirstOrDefault(p => p.ProductID == productId); return View(product);

}

}

}

Этот простой метод находит товар с ID, который соответствует параметру productId, и передает его в качестве объекта модели представления.

249

Модульный тест: метод действия Edit

Мы хотим протестировать два вида поведения в методе действия Edit. Первое состоит в том, получаем ли мы правильный товар, когда предоставляем действительное значение ID. Очевидно, мы хотим убедиться, что отредактируем именно тот товар, который собирались. Второй вид поведения заключается в том, что мы не получаем никакого товара вообще, когда мы предоставляем недействительное значение ID. Вот тестовые методы, которые мы добавили в файл AdminTests.cs:

[TestMethod]

public void Can_Edit_Product()

{

//Arrange - create the mock repository Mock<IProductRepository> mock = new Mock<IProductRepository>(); mock.Setup(m => m.Products).Returns(new Product[] {

new Product {ProductID = 1, Name = "P1"}, new Product {ProductID = 2, Name = "P2"}, new Product {ProductID = 3, Name = "P3"},

}.AsQueryable());

//Arrange - create the controller

AdminController target = new AdminController(mock.Object); // Act

Product p1 = target.Edit(1).ViewData.Model as Product; Product p2 = target.Edit(2).ViewData.Model as Product; Product p3 = target.Edit(3).ViewData.Model as Product; // Assert

Assert.AreEqual(1, p1.ProductID); Assert.AreEqual(2, p2.ProductID); Assert.AreEqual(3, p3.ProductID);

}

[TestMethod]

public void Cannot_Edit_Nonexistent_Product()

{

//Arrange - create the mock repository Mock<IProductRepository> mock = new Mock<IProductRepository>(); mock.Setup(m => m.Products).Returns(new Product[] {

new Product {ProductID = 1, Name = "P1"}, new Product {ProductID = 2, Name = "P2"}, new Product {ProductID = 3, Name = "P3"},

}.AsQueryable());

//Arrange - create the controller

AdminController target = new AdminController(mock.Object); // Act

Product result = (Product)target.Edit(4).ViewData.Model; // Assert

Assert.IsNull(result);

}

Создаем представление редактирования

Теперь, когда у нас готов метод действия, мы можем создать представление, которое он будет визуализировать. Кликните правой кнопкой мыши по методу действия Edit и выберите пункт Add View. Оставьте имя представления Edit, отметьте флажком опцию Create a strongly-typed view и убедитесь, что в качестве класса модели выбран класс Product, как показано на рисунке 10-8.

250

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]