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

ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p

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

требует, чтобы запросы GET были идемпотентными, что означает, что они не

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

Мы хотим, чтобы стиль этих кнопок соответствовал стилю всего приложения, поэтому добавьте в конец файла Content/Site.css код CSS, показанный в листинге 8-15.

Листинг 8-15: Применяем стили к кнопкам

FORM { margin: 0; padding: 0; } DIV.item FORM { float:right; } DIV.item INPUT {

color:White; background-color: #333; border: 1px solid black; cursor:pointer;

}

Создаем несколько HTML-форм на странице

Использование вспомогательного метода Html.BeginForm в каждом списке товаров означает, что каждая кнопка Add to cart визуализируется в своем отдельном HTML-элементе form. Это может вас удивить, если раньше вы работали с ASP.NET Web Forms, где количество форм на странице ограничено одной. В ASP.NET MVC нет лимита на количество форм на странице, у вас их может быть столько, сколько вам нужно.

Нет такого технического требования, согласно которому мы должны создавать форму для каждой кнопки. Однако, так как каждая форма будет отправлять данные к одному и тому же методу контроллера, но с разными значениями параметров, будет лучше и проще поработать с нажатиями кнопок.

Реализуем Cart Controller

Нам нужно создать контроллер для обработки нажатий кнопки Add to cart. Создайте новый контроллер под названием CartController и отредактируйте его содержимое так, чтобы он соответствовал листингу 8-16.

Листинг 8-16: Создем CartController

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 CartController : Controller

{

private IProductRepository repository;

public CartController(IProductRepository repo)

{

repository = repo;

}

public RedirectToRouteResult AddToCart(int productId, string returnUrl)

{

211

Product product = repository.Products

.FirstOrDefault(p => p.ProductID == productId); if (product != null)

{

GetCart().AddItem(product, 1);

}

return RedirectToAction("Index", new { returnUrl });

}

public RedirectToRouteResult RemoveFromCart(int productId, string returnUrl)

{

Product product = repository.Products

.FirstOrDefault(p => p.ProductID == productId); if (product != null)

{

GetCart().RemoveLine(product);

}

return RedirectToAction("Index", new { returnUrl });

}

private Cart GetCart()

{

Cart cart = (Cart)Session["Cart"]; if (cart == null)

{

cart = new Cart(); Session["Cart"] = cart;

}

return cart;

}

}

}

По поводу этого контроллера есть несколько замечаний. Первое касается того, что мы используем состояние сессии ASP.NET для сохранения и извлечения объектов Cart. Это задача метода GetCart. В ASP.NET есть объект Session, который использует cookie или перезапись URL для группировки запросов от пользователя, чтобы сформировать одну сессию просмотра. Состояние сессии (session state) позволяет связывать данные с сессией. Оно идеально подходит для нашего класса Cart. Мы хотим, чтобы у каждого пользователя была своя корзина, и чтобы она сохранялась в промежутках времени между запросами. Данные, которые связываются с сессией, удаляются, когда сессия истекает (обычно потому, что пользователь не отправлял запросы некоторое время). Это означает, что мы не должны управлять хранением или жизненным циклом объектов Cart. Чтобы добавить объект в состояние сессии, мы устанавливаем значение для ключа в объекте Session, например:

Session["Cart"] = cart;

Чтобы извлечь объект снова, мы просто считываем тот же ключ, например:

Cart cart = (Cart)Session["Cart"];

Совет

Объекты состояния сеанса хранятся в памяти сервера ASP.NET по умолчанию, но вы можете настроить различные другие подходы к хранению, в том числе с использованием базы данных

SQL.

Для методов AddToCart и RemoveFromCart мы использовали имена параметров, которые соответствуют элементам input в формах HTML, которые мы создали в представлении

212

ProductSummary.cshtml. Это позволяет MVC Framework связывать входящие переменные формы POST с этими параметрами, что избавляет нас от необходимости обрабатывать форму самим.

Отображаем содержимое корзины

Последнее замечание по поводу контроллера Cart состоит в том, что и метод AddToCart, и RemoveFromCart вызывают метод RedirectToAction. В результате этого браузеру клиента отправляется HTTP-инструкция перенаправления, которая сообщает браузеру запросить новый URL. В этом случае мы сообщаем браузеру запросить URL, который будет вызывать метод действия Index контроллера Cart.

Мы реализуем метод Index и будем использовать его для отображения содержимого корзины. Если вы вернетесь к рисунку 8-8, то увидите, это наш рабочий поток после того, как пользователь нажимает кнопку Add to cart.

Нам нужно передать две порции информации в представление, которое будет отображать содержимое корзины: объект Cart и URL, который будет отображен, если пользователь нажмет кнопку Continue shopping. Для этого мы создадим простой класс модели представления. Создайте новый класс под названием CartIndexViewModel в папке Models проекта SportsStore.WebUI.

Содержание этого класса показано в листинге 8-17.

Листинг 8-17: Класс CartIndexViewModel

using SportsStore.Domain.Entities;

namespace SportsStore.WebUI.Models

{

public class CartIndexViewModel

{

public Cart Cart { get; set; } public string ReturnUrl { get; set; }

}

}

Когда у нас готова модель представления, мы можем реализовать метод действия Index в классе контроллера Cart, как показано в листинге 8-18.

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

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

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

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

namespace SportsStore.WebUI.Controllers

{

public class CartController : Controller

{

private IProductRepository repository;

public CartController(IProductRepository repo)

{

repository = repo;

}

public ViewResult Index(string returnUrl)

213

{

return View(new CartIndexViewModel

{

Cart = GetCart(), ReturnUrl = returnUrl

});

}

// ...other action methods omitted for brevity...

}

}

Последнее, что нужно сделать, чтобы отобразить содержимое корзины, - это создать новое представление. Щелкните правой кнопкой мыши метод Index и выберите Add View из контекстного меню. Назовите представление Index, отметьте флажком опцию Сreate a strongly typed view и выберите CartIndexViewModel как класс модели, как показано на рисунке 8-9.

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

214

Мы хотим, чтобы содержимое корзины выглядело так же, как и остальные страницы приложения, так что убедитесь, что вы выбрали опцию Use a layout и оставили текстовое поле пустым, чтобы по умолчанию использовался файл _Layout.cshtml. Нажмите кнопку Add, чтобы создать представление, и отредактируйте содержимое так, чтобы оно соответствовало листингу 8-19.

Листинг 8-19: Представление Index

@model SportsStore.WebUI.Models.CartIndexViewModel

@{

ViewBag.Title = "Sports Store: Your Cart";

}

<h2>Your cart</h2>

<table width="90%" align="center"> <thead>

<tr>

<th align="center">Quantity</th> <th align="left">Item</th>

<th align="right">Price</th> <th align="right">Subtotal</th>

</tr>

</thead>

<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> </tr>

}

</tbody>

<tfoot>

<tr>

<td colspan="3" align="right">Total:</td> <td align="right">

@Model.Cart.ComputeTotalValue().ToString("c") </td>

</tr>

</tfoot>

</table>

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

<a href="@Model.ReturnUrl">Continue shopping</a> </p>

Представление выглядит сложнее, чем оно есть на самом деле. Оно просто перечисляет строки в корзине и добавляет ряды для каждой из них HTML-таблицу, а также общую стоимость в каждом ряду и общую стоимость всей корзины. Нам осталось добавить еще немного CSS. Добавьте стили, показанные в листинге 8- 20, к файлу Site.css.

Листинг 8-20: CSS для отображения содержимого корзины

H2 { margin-top: 0.3em }

TFOOT TD { border-top: 1px dotted gray; font-weight: bold; }

.actionButtons A, INPUT.actionButtons {

font: .8em Arial; color: White; margin: .5em; text-decoration: none; padding: .15em 1.5em .2em 1.5em; background-color: #353535; border: 1px solid black;

}

Теперь у нас готовы базовые функции корзины. Во-первых, товары выводятся с кнопкой для добавления в корзину, как показано на рисунке 8-10.

215

Рисунок 8-10: Кнопка Add to cart

Во-вторых, когда мы нажимаем кнопку Add to cart, соответствующий товар добавляется в корзину

иотображаются общие сведения о корзине, как показано на рисунке 8-11. Мы можем нажать кнопку Continue shopping и вернуться на страницу товара, с которой пришли - все выглядит очень красиво

иработает гладко.

Рисунок 8-11: Отображение содержимого корзины

216

Резюме

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

217

SportsStore: завершение функционала для корзины покупателя

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

Использование модели связывания данных

MVC Framework использует систему под названием модель связывания данных для создания объектов C# из HTTP-запросов и передачи их в качестве значений параметров в методы действий. Таким образом, например, MVC обрабатывает формы. Платформа смотрит на параметры целевого метода действия и использует механизм связывания данных модели, чтобы получить значения входных элементов формы и преобразовать их в одноименный тип параметра.

Механизмы связывания могут создавать типы C# из любых данных, доступных в запросе. Это является одной из центральных возможностей MVC Framework. Мы создадим пользовательский механизм связывания, чтобы улучшить наш класс CartController.

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

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

Создаем пользовательский механизм связывания данных

Мы создаем пользовательский механизм связывания путем реализации интерфейса IModelBinder. Создайте новую папку под названием Binders в проекте SportsStore.WebUI, а в ней - класс CartModelBinder. Определение класса CartModelBinder показано в листинге 9-1.

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

using System;

using System.Web.Mvc;

using SportsStore.Domain.Entities; namespace SportsStore.WebUI.Binders

{

public class CartModelBinder : IModelBinder

{

private const string sessionKey = "Cart";

public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)

{

// get the Cart from the session

Cart cart = (Cart)controllerContext.HttpContext.Session[sessionKey];

218

//create the Cart if there wasn't one in the session data if (cart == null)

{

cart = new Cart(); controllerContext.HttpContext.Session[sessionKey] = cart;

}

//return the cart

return cart;

}

}

}

Интерфейс IModelBinder определяет один метод: BindModel. Мы передаем в него два параметра для того, чтобы сделать возможным создание объекта доменной модели. ControllerContext обеспечивает доступ ко всей информации, которой располагает класс контроллера и которая включает в себя детали запроса клиента. ModelBindingContext предоставляет информацию об объекте создаваемой модели, и некоторые инструменты, которые облегчат процесс связывания. Мы вернемся к этому классу в главе 22.

Класс ControllerContext интересует нас больше всего. У него есть свойство HttpContext, у которого, в свою очередь, есть свойство Session, которое позволяет получать и устанавливать данные сессии. Прочитав значение ключа из данных сессии, мы получаем объект Cart или, если его еще не существует, создаем новый.

Мы должны сообщить MVC Framework, что она может использовать класс CartModelBinder для создания экземпляров объекта Cart. Мы делаем это в методе Application_Start файла Global.asax, как показано в листинге 9-2.

Листинг 9-2: Регистрируем класс CartModelBinder

using SportsStore.Domain.Entities; using SportsStore.WebUI.Binders;

using SportsStore.WebUI.Infrastructure; using System.Web.Http;

using System.Web.Mvc;

using System.Web.Optimization; using System.Web.Routing;

namespace SportsStore.WebUI

{

public class MvcApplication : System.Web.HttpApplication

{

protected void Application_Start()

{

AreaRegistration.RegisterAllAreas();

WebApiConfig.Register(GlobalConfiguration.Configuration);

FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);

RouteConfig.RegisterRoutes(RouteTable.Routes);

BundleConfig.RegisterBundles(BundleTable.Bundles);

ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());

ModelBinders.Binders.Add(typeof(Cart), new CartModelBinder());

}

}

}

219

Теперь мы можем обновить класс CartController, чтобы удалить метод GetCart и задействовать на наш механизм связывания, который MVC Framework будет применять автоматически. Изменения показаны в листинге 9-3.

Листинг 9-3: Используем механизм связывания в CartController

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

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

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

namespace SportsStore.WebUI.Controllers

{

public class CartController : Controller

{

private IProductRepository repository;

public CartController(IProductRepository repo)

{

repository = repo;

}

public ViewResult Index(Cart cart, string returnUrl)

{

return View(new CartIndexViewModel

{

Cart = cart, ReturnUrl = returnUrl

});

}

public RedirectToRouteResult AddToCart(Cart cart, int productId, string returnUrl)

{

Product product = repository.Products.FirstOrDefault(p => p.ProductID == productId);

if (product != null)

{

cart.AddItem(product, 1);

}

return RedirectToAction("Index", new { returnUrl });

}

public RedirectToRouteResult RemoveFromCart(Cart cart, int productId, string returnUrl)

{

Product product = repository.Products.FirstOrDefault(p => p.ProductID == productId);

if (product != null)

{

cart.RemoveLine(product);

}

return RedirectToAction("Index", new { returnUrl });

}

}

}

Мы удалили метод GetCart и добавили параметр Cart в каждый метод действия. Когда MVC Framework получает запрос, который требует, скажем, вызвать метод AddToCart, она будет сначала смотреть на параметры для метода действия. Она рассмотрит список доступных механизмов

220

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