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

ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p

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

связывания и попытается найти тот, который сможет создать экземпляры каждого типа параметра. Наш пользовательский механизм связывания должен будет создать объект Cart, что он и сделает, используя состояние сеанса. Между обращениями к нашему механизму связывания и механизму по умолчанию MVC Framework может создать набор параметров, которые необходимы для вызова метода действия, позволяя нам реорганизовать контроллер так, чтобы в нем не осталось сведений о том, как создаются объекты Cart при получении запросов.

Использование подобного механизма связывания дает нам несколько преимуществ. Первое заключается в том, что мы отделили логику для создания объектов Cart от контроллера, что позволит нам изменять способ сохранения этих объектов без необходимости изменять контроллер. Вторым преимуществом является то, что любой класс контроллера, который работает с объектами Cart, может просто объявить их как параметры метода действия и воспользоваться пользовательским механизмом связывания. Третье и, на наш взгляд, самое главное преимущество состоит в том, что теперь мы сможем тестировать контроллер Cart, не создавая имитаций встроенных возможностей ASP.NET.

Модульный тест: контроллер Cart

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

Метод AddToCart должен добавить выбранный товар в корзину покупателя.

После добавления товара в корзину он должен перенаправить нас в представление

Index.

URL, по которому пользователь сможет вернуться в каталог, должен быть корректно передан в метод действия Index.

Вот модульные тесты, которые мы добавили в файл CartTests.cs проекта SportsStore.UnitTests:

using System;

using Microsoft.VisualStudio.TestTools.UnitTesting; using SportsStore.Domain.Entities;

using System.Linq; using Moq;

using SportsStore.Domain.Abstract; using SportsStore.WebUI.Controllers; using System.Web.Mvc;

using SportsStore.WebUI.Models; namespace SportsStore.UnitTests

{

[TestClass]

public class CartTests

{

//...existing test methods omitted for brevity...

[TestMethod]

public void Can_Add_To_Cart()

{

//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", Category = "Apples"}, }.AsQueryable());

//Arrange - create a Cart

Cart cart = new Cart();

// Arrange - create the controller

CartController target = new CartController(mock.Object);

221

//Act - add a product to the cart target.AddToCart(cart, 1, null);

//Assert Assert.AreEqual(cart.Lines.Count(), 1);

Assert.AreEqual(cart.Lines.ToArray()[0].Product.ProductID, 1);

}

[TestMethod]

public void Adding_Product_To_Cart_Goes_To_Cart_Screen()

{

//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", Category = "Apples"}, }.AsQueryable());

//Arrange - create a Cart

Cart cart = new Cart();

// Arrange - create the controller

CartController target = new CartController(mock.Object);

// Act - add a product to the cart

RedirectToRouteResult result = target.AddToCart(cart, 2, "myUrl");

// Assert Assert.AreEqual(result.RouteValues["action"], "Index");

Assert.AreEqual(result.RouteValues["returnUrl"], "myUrl");

}

[TestMethod]

public void Can_View_Cart_Contents()

{

//Arrange - create a Cart Cart cart = new Cart();

//Arrange - create the controller CartController target = new CartController(null);

//Act - call the Index action method

CartIndexViewModel result = (CartIndexViewModel)target.Index(cart, "myUrl").ViewData.Model;

// Assert Assert.AreSame(result.Cart, cart);

Assert.AreEqual(result.ReturnUrl, "myUrl");

}

}

}

222

Завершаем функционал для корзины

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

Удаление товаров из корзины

Мы уже определили и проверили метод действия RemoveFromCart в контроллере. Чтобы позволить пользователю удалять товары, нужно только сделать этот метод доступным в представлении, для чего мы добавим кнопку Remove в каждую строку представления для корзины. Изменения в

Views/Cart/Index.cshtml показаны в листинге 9-4.

Листинг 9-4: Создаем кнопку Remove

<tbody>

@foreach (var line in Model.Cart.Lines) { <tr>

<td align="center">@line.Quantity</td> <td align="left">@line.Product.Name</td>

<td align="right">@line.Product.Price.ToString("c")</td>

<td align="right">@((line.Quantity * line.Product.Price).ToString("c"))</td>

<td>

@using (Html.BeginForm("RemoveFromCart", "Cart")) { @Html.Hidden("ProductId", line.Product.ProductID) @Html.HiddenFor(x => x.ReturnUrl)

<input class="actionButtons" type="submit" value="Remove" />

}

</td>

</tr>

}

</tbody>

Примечание

Мы можем использовать строго типизированный вспомогательный метод Html.HiddenFor, чтобы создать скрытое поле для свойства модели ReturnUrl, но

мы должны использовать строко-ориентированный вспомогательный метод Html.Hidden, чтобы сделать то же самое для поля Product ID. Если мы запишем

Html.HiddenFor(x => line.Product.ProductID), вспомогательный метод визуализирует скрытое поле под названием line.Product.ProductID. Имя поля не

будет совпадать с именами параметров метода действия CartController.RemoveFromCart, из-за чего механизмы связывания по умолчанию

не будут работать и MVC Framework не сможет вызвать метод.

Вы можете проверить работу кнопок Remove, если запустите приложение, добавите какие-нибудь товары в корзину и кликните по одной из них. Результат показан на рисунке 9-1.

223

Рисунок 9-1: Удаление товара из корзины

Добавляем виджет корзины

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

Чтобы решить эту проблему, мы добавим виджет, который выводит краткую информацию о корзине и при нажатии отобразит ее содержимое. Мы сделаем это почти также, как добавляли виджет навигации - используя действие, вывод которого будет внедрен в макет Razor.

Для начала нам нужно добавить в класс CartController простой метод, показанный в листинге 9-5.

Листинг 9-5: Добавляем метод Summary в контроллер Cart

public PartialViewResult Summary(Cart cart) { return PartialView(cart);

}

Как видите, это очень простой метод. Он просто визуализирует представление, предоставляя текущий объект Cart (который будет получен с помощью нашего пользовательского механизма связывания), в качестве данных представления. Нам нужно создать частичное представление, которое будет отображаться в ответ на вызов метода Summary. Кликните правой кнопкой мыши метод Summary и выберите Add View из контекстного меню. Назовите представление Summary, отметьте опцию Create as strongly typed view и установите класс модели Cart, как показано на рисунке 9-2. Мы хотим внедрить частичное представление в страницу, так что отметьте опцию

Create as a partial view.

224

Рисунок 9-2: Добавляем представление Summary

Измените новое частичное представление так, чтобы оно соответствовало листингу 9-6.

Листинг 9-6: Частичное представление Summary

@model SportsStore.Domain.Entities.Cart

<div id="cart">

<span class="caption"> <b>Your cart:</b>

@Model.Lines.Sum(x => x.Quantity) item(s), @Model.ComputeTotalValue().ToString("c")

</span>

@Html.ActionLink("Checkout", "Index", "Cart", new { returnUrl = Request.Url.PathAndQuery }, null)

</div>

Это простое представление, которое отображает количество товаров в корзине, их общую стоимость и ссылку, по которой можно просмотреть содержимое корзины. Теперь, когда мы определили представление, которое возвращает метод действия Summary, мы можем включить результат рендеринга в файл _Layout.cshtml, как показано в листинге 9-7.

225

Листинг 9-7: Добавляем частичное представление Summary в макет

<!DOCTYPE html> <html>

<head>

<meta charset="utf-8" />

<meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title>

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

<body>

<div id="header">

@{Html.RenderAction("Summary", "Cart");}

<div class="title">SPORTS STORE</div> </div>

<div id="categories">

@{ Html.RenderAction("Menu", "Nav"); } </div>

<div id="content"> @RenderBody()

</div>

</body>

</html>

Напоследок мы добавим некоторые дополнительные правила CSS для форматирования элементов в частичное представление. Добавьте стили из листинга 9-8 в файл Site.css проекта

SportsStore.WebUI.

Листинг 9-8: Добавляем стили в Site.css

DIV#cart { float:right; margin: .8em; color: Silver; background-color: #555; padding: .5em .5em .5em 1em; }

DIV#cart A { text-decoration: none; padding: .4em 1em .4em 1em; line-height:2.1em; margin-left: .5em; background-color: #333; color:White; border: 1px solid black;}

Запустив приложение, вы можете увидеть виджет корзины. Количество элементов и их общая стоимость увеличивается, когда вы добавляете товар в корзину, как показано на рисунке 9-3.

Рисунок 9-3: Виджет корзины

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

226

Отправка заказов

Мы дошли до заключительной функции для работы пользователей в SportsStore: возможности подтвердить заказ и завершить покупку. В следующих разделах мы расширим нашу доменную модель так, чтобы обеспечить поддержку ввода реквизитов доставки, и добавим функцию для обработки этой информации.

Расширяем доменную модель

Добавьте класс под названием ShippingDetails в папку Entities проекта SportsStore.Domain. Это класс, который мы будем использовать для представления клиенту полей для ввода информации о доставке. Его содержимое показано в листинге 9-9.

Листинг 9-9: Класс ShippingDetails

using System.ComponentModel.DataAnnotations;

namespace SportsStore.Domain.Entities

{

public class ShippingDetails

{

[Required(ErrorMessage = "Please enter a name")] public string Name { get; set; }

[Required(ErrorMessage = "Please enter the first address line")] public string Line1 { get; set; }

public string Line2 { get; set; } public string Line3 { get; set; }

[Required(ErrorMessage = "Please enter a city name")] public string City { get; set; }

[Required(ErrorMessage = "Please enter a state name")] public string State { get; set; }

public string Zip { get; set; }

[Required(ErrorMessage = "Please enter a country name")] public string Country { get; set; }

public bool GiftWrap { get; set; }

}

}

Как вы видите из листинга 9-9, мы используем атрибуты валидации из пространства имен

System.ComponentModel.DataAnnotations, как мы это уже делали в главе 2. Валидация будет рассмотрена подробно в главе 23.

Примечание

В классе ShippingDetails нет никакой функциональности, так что он не

нуждается в тестировании.

Добавляем процесс подтверждения заказа

Мы хотим реализовать функционал до того пункта, где пользователи смогут ввести информацию о доставке и отправить заказ. Чтобы это выполнить, для начала нам нужно добавить кнопку Checkout

227

now в представление для корзины. В листинге 9-10 показано изменение, которое мы должны внести в файл Views/Cart/Index.cshtml.

Листинг 9-10: Добавляем кнопку Checkout now

</table>

<p align="center" class="actionButtons">

<a href="@Model.ReturnUrl">Continue shopping</a> @Html.ActionLink("Checkout now", "Checkout")

</p>

Это единственное изменение генерирует ссылку, нажатие на которую вызывает метод действия Checkout контроллера Cart. Вы можете увидеть эту кнопку на рисунке 9-4.

Рисунок 9-4: Кнопка Checkout Now

Как и следовало ожидать, теперь нам нужно определить метод Checkout в классе CartController, как показано в листинге 9-11.

Листинг 9-11: Метод действия Checkout

public ViewResult Checkout() { return View(new ShippingDetails());

}

Метод Checkout возвращает представление по умолчанию и передает в него новый объект ShippingDetails как модель представления. Чтобы создать соответствующее представление, кликните правой кнопкой мыши метод Checkout, выберите Add View и заполните диалоговое окно, как показано на рисунке 9-5. Мы собираемся использовать доменный класс ShippingDetails как

228

основу для строго типизированного представления. Отметьте флажком опцию Use a layout, потому что мы визуализируем целую страницу и хотим, чтобы она выглядела также, как и все приложение.

Рисунок 9-5: Добавляем представление Checkout

Приведите содержимое представления в соответствие с разметкой, показанной в листинге 9-12.

Листинг 9-12: Представление Checkout.cshtml

@model SportsStore.Domain.Entities.ShippingDetails

@{

ViewBag.Title = "SportStore: Checkout";

}

<h2>Check out now</h2>

Please enter your details, and we'll ship your goods right away! @using (Html.BeginForm())

{

<h3>Ship to</h3>

<div>Name: @Html.EditorFor(x => x.Name)</div> <h3>Address</h3>

<div>Line 1: @Html.EditorFor(x => x.Line1)</div> <div>Line 2: @Html.EditorFor(x => x.Line2)</div>

229

<div>Line 3: @Html.EditorFor(x => x.Line3)</div> <div>City: @Html.EditorFor(x => x.City)</div> <div>State: @Html.EditorFor(x => x.State)</div> <div>Zip: @Html.EditorFor(x => x.Zip)</div> <div>Country: @Html.EditorFor(x => x.Country)</div> <h3>Options</h3>

<label>

@Html.EditorFor(x => x.GiftWrap) Gift wrap these items

</label>

<p align="center">

<input class="actionButtons" type="submit" value="Complete order" /> </p>

}

Вы увидите, как визуализируется это представление, запустив приложение, добавив товар в корзину и нажав кнопку Checkout now. На рисунке 9-6 показано, что представление отображается в виде формы для ввода реквизитов доставки.

Рисунок 9-6: Форма для реквизитов доставки

230

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