1.创建购物车
上一节中已经将每个产品的信息,每类的产品信息都显示出来了,接下来,在每一个产品旁边添加一个加入购物车按钮。单击此按钮,将显示该客户目前已选产品的摘要,包括总费用,在这里,客户可以单击继续购物按钮,回到上一个页面。
购物车是业务域的一部分,因此,在域模型中创建一个购物车的实体是有意义的。在SportsStore.Domain项目的Entities文件下添加一个名为Cart.cs的类文件。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SportsStore.Domain.Entities
{
public class Cart
{
private List<CartLine> lineCollection = new List<CartLine>();
public void AddItem(Product product,int quantity)
{
CartLine line = lineCollection.Where(p => p.Product.ProductID == product.ProductID).FirstOrDefault();
if(line ==null)
{
lineCollection.Add(new CartLine { Product = product, Quantity = quantity });
}
else
{
line.Quantity += quantity;
}
}
public void RemoveLine(Product product)
{
lineCollection.RemoveAll(l => l.Product.ProductID == product.ProductID);
}
public void Clear()
{
lineCollection.Clear();
}
public IEnumerable<CartLine> Lines
{
get { return lineCollection; }
}
public decimal ComputeTotalValue()
{
return lineCollection.Sum(p => p.Product.Price * p.Quantity);
}
}
public class CartLine
{//用户索要购买的产品和数量
public Product Product { get; set; }
public int Quantity { get; set; }
}
}
这里的Cart类在同一个文件中定义了CartLine类,以表示由客户所选的一个产品和用户项目购买的数量。还定义了一些方法,包括:给购物车添加物品、从购物车中删除已添加的物品、计算购物车物品的总价,以及删除全部物品重置购物车等。此外还提供了一个属性,它使用IEnumerable<CartLine>对购物车的内容进行访问。所有这些都很直观,利用一点点LINQ的辅助,很好的实现了。
首先添加加入购物车按钮,编辑Views/Shared/ProductSummary.cshtml视图
@model SportsStore.Domain.Entities.Product
<div class="well">
<h3>
<strong>@Model.Name</strong>
<span class="pull-right label label-primary">@Model.Price.ToString("c")</span>
</h3>
@using (Html.BeginForm("AddToCart", "Cart"))
{//BeginForm 辅助器会创建一个使用HTTPpost方法的表单,也可对其进行修改,使用get方法,但是HTTP规范要求get请求必须是幂等的,也就是说他们不会引起数据的变化,而此处将一个产品添加到购物车显然是一个变化,所以这里没有用get
<div class="pull-right">
@Html.HiddenFor(x=>x.ProductID)
@Html.Hidden("returnUrl",Request.Url.PathAndQuery)
<input type="submit" class="btn btn-success" value="加入购物车"/>
</div>
}
<span class="lead">@Model.Description</span>
</div>
在清单中添加了一个Razor代码块,为列表中的每一个产品创建一个小型的HTML表单。当该表单被递交时,将调用Cart控制器中的AddToCart动作方法。
默认情况下,BeginForm 辅助器会创建一个使用HTTPpost方法的表单,也可对其进行修改,使用get方法,但是HTTP规范要求get请求必须是幂等的,也就是说他们不会引起数据的变化,而此处将一个产品添加到购物车显然是一个变化,所以这里没有用get。
2.实现购物车控制器
此时需要一个控制器来处理“加入购物车”按钮的单击。为此,在SportsStore.WebUI项目中创建一个新的控制器,名称为“CartController”,并编辑内容,使其与清单吻合。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using SportsStore.WebUI.Models;
namespace SportsStore.WebUI.Controllers
{
public class CartController : Controller
{
private IProductsRepository repository;
public CartController(IProductsRepository repo)
{
repository = repo;
}
public ViewResult Index(string returnUrl)
{
return View(new CartIndexViewModel
{
Cart = GetCart(),
ReturnUrl = returnUrl
});
}
public RedirectToRouteResult AddToCart(int productId,string returnUrl)
{
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 });
}
//Asp.net 有一个很好的会话特性,使用重写cookie或URL的办法,将一个用户的多个请求关联在一起,形成一个单一的会话。
private Cart GetCart()
{
Cart cart = (Cart)Session["Cart"];
if (cart == null)
{
cart = new Cart();
Session["Cart"] = cart;
}
return cart;
}
}
}
该控制器有几点需要注意。第一是为了存储和接收Cart对象,这里使用了Asp.net的会话状态特性,这是GetCart方法的目的。Asp.net有一个很好的会话特性,它使用重写cookies或URL的办法,将一个用户的多个请求关联在一起,形成一个单一的浏览会话。与之有关的一个特性是会话状态,它可以将数据与会话关联起来,这对Cart类很合适。这可以使每个用户有自己的购物车,并且购物车在各次请求之间是保持有效的。当会话过期(典型的情况是用户很长时间没有任何请求)时,与会话关联的数据会被删除,这意味着不需要对Cart对象的存储或其生命周期进行管理。为了给会话状态添加一个对象,只要为Session对象上设置一个键的值即可:Session["Cart"] = cart; Cart cart = (Cart)Session["Cart"];
会话状态对象(Session对象)默认存储在ASp.net服务器的内存中,但你可以配置不同的存储方式,包括使用数据库。
对于AddToCart和RemoveFromCart方法,使用了HTML表单中input元素相匹配的参数名,这些HTML表单是在ProductSummary.cshtml视图中创建的。这可以让MVC框架将输入表单的post变量与这些参数关联起来,意即,不需要自己去处理这个表单。
3.显示购物车内容
对于Cart控制器,要注意的是AddToCart和RemoveFromCart方法都调用了RedirectToAction方法。其效果是,将一个HTTP的重定向指令发送到客户端浏览器,要求浏览器请求一个新的URL。在此例中,要求浏览器请求的URL是调用Cart控制器的Index动作方法。
在SportsStore.WebUI项目的models文件下添加一个CartIndexViewModel.cs文件
using SportsStore.Domain.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace SportsStore.WebUI.Models
{
public class CartIndexViewModel
{
public Cart Cart { get; set; }
public string ReturnUrl { get; set; }
}
}
创建Index.cshtml视图,右击Index动作方法,添加视图:
@model SportsStore.WebUI.Models.CartIndexViewModel
@{
ViewBag.Title = "我的购物车";
}
<h2>我的购物车</h2>
<table class="table">
<thead>
<tr>
<th class="text-center">数量</th>
<th class="text-center">产品</th>
<th class="text-center">价格</th>
<th class="text-center">总价</th>
</tr>
</thead>
<tbody>
@foreach(var line in Model.Cart.Lines)
{
<tr>
<td class="text-center">@line.Quantity</td>
<td class="text-center">@line.Product.Name</td>
<td class="text-center">@line.Product.Price.ToString("c")</td>
<td class="text-center">@((line.Quantity*line.Product.Price).ToString("c"))</td>
</tr>
}
</tbody>
<tfoot>
<tr>
<td></td>
<td></td>
<td colspan="1" class="text-center">总价:</td>
<td class="text-center">
@Model.Cart.ComputeTotalValue().ToString("c")
</td>
</tr>
</tfoot>
</table>
<div class="text-center">
<a class="btn btn-primary" href="@Model.ReturnUrl">继续购物</a>
</div>
该视图枚举了购物车中的各条信息,并在一个HTML的表格中为各条信息添加一个表格行,并带有各行的总价以及整个购物车的总价。给这些元素的class标签属性所赋的值对应于Bootsrap用于表格和元素对齐的样式。
效果展示
项目代码链接:点击打开链接