我们的项目进展相当的不错,但是现在还不能真正的出售商品,因为我们没有为顾客提供购物车。今天,我们就加入购物车的功能,毕竟赚钱才是赢道理啊!购物车的逻辑看起来应该像这样:
我们需要在每件商品的旁边都加一个"Add to cart”的按钮,客户可以随时的添加自己选择的商品。现在就让我们到Domain工程的Entities文件夹去添加一个Cart类吧:
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 decimal ComputeTotalValue() {
return lineCollection.Sum(e => e.Product.Price * e.Quantity);
}
public void Clear() {
lineCollection.Clear();
}
public IEnumerable<CartLine> Lines {
get { return lineCollection; }
}
}
public class CartLine {
public Product Product { get; set; }
public int Quantity { get; set; }
}
}
Cart类使用CartLine去展示客户选择的产品和想购买的数量,我们已经定义了添加商品到购物车的方法, 从购物车删除商品的方法,计算购物车中的商品总额的方法和重置购物车,清空说有商品的方法。我们也提供了一个属性,使用IEnumerble<CartLine>.去访问购物车的内容,所有这些通过LinQ很容易实现。
测试购物车
购物车是我们网站中非常重要的功能,我们必须确保它能正常的运行。为此我们必须要对它进行测试,我们现在就在测试工程中添加一个购物车的测试文件,叫做CartTests.cs。我们要测试的第一项内容是,当一个商品第一次被添加到购物车时,我们希望购物车能添加一个新的CartLine。
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SportsStore.Domain.Entities;
using System.Linq;
namespace SportsStore.UnitTests {
[TestClass]
public class CartTests
{
[TestMethod]
public void Can_Add_New_Lines()
{
// Arrange - create some test products
Product p1 = new Product { ProductID = 1, Name = "P1" };
Product p2 = new Product { ProductID = 2, Name = "P2" };
// Arrange - create a new cart
Cart target = new Cart();
// Act
target.AddItem(p1, 1);
target.AddItem(p2, 1);
CartLine[] results = target.Lines.ToArray();
// Assert
Assert.AreEqual(results.Length, 2);
Assert.AreEqual(results[0].Product, p1);
Assert.AreEqual(results[1].Product, p2);
}
}
}
然而,这必然存在一个隐患,如果顾客已经添加过这个商品,再次添加时,我们希望增加的是购物车中的数量,而不是创建一个新的CartLine.
[TestMethod]
public void Can_Add_Quantity_For_Existing_Lines()
{
// Arrange - create some test products
Product p1 = new Product { ProductID = 1, Name = "P1" };
Product p2 = new Product { ProductID = 2, Name = "P2" };
// Arrange - create a new cart
Cart target = new Cart();
// Act
target.AddItem(p1, 1);
target.AddItem(p2, 1);
target.AddItem(p1, 10);
CartLine[] results = target.Lines.OrderBy(c => c.Product.ProductID).ToArray();
// Assert
Assert.AreEqual(results.Length, 2);
Assert.AreEqual(results[0].Quantity, 11);
Assert.AreEqual(results[1].Quantity, 1);
}
用户也会随时改变想法,从购物车中删除商品,这个方法我们应实现了,但我们还是需要去Check,这里也需要测试。
[TestMethod]
public void Can_Remove_Line()
{
// Arrange - create some test products
Product p1 = new Product { ProductID = 1, Name = "P1" };
Product p2 = new Product { ProductID = 2, Name = "P2" };
Product p3 = new Product { ProductID = 3, Name = "P3" };
// Arrange - create a new cart
Cart target = new Cart();
// Arrange - add some products to the cart
target.AddItem(p1, 1);
target.AddItem(p2, 3);
target.AddItem(p3, 5);
target.AddItem(p2, 1);
// Act
target.RemoveLine(p2);
// Assert
Assert.AreEqual(target.Lines.Where(c => c.Product == p2).Count(), 0);
Assert.AreEqual(target.Lines.Count(), 2);
}
我们还要测试一下对商品总额的计算是否正确,说干就干,添加如下测试方法:
[TestMethod]
public void Calculate_Cart_Total() {
// Arrange - create some test products
Product p1 = new Product { ProductID = 1, Name = "P1", Price = 100M};
Product p2 = new Product { ProductID = 2, Name = "P2" , Price = 50M};
// Arrange - create a new cart
Cart target = new Cart();
// Act
target.AddItem(p1, 1);
target.AddItem(p2, 1);
target.AddItem(p1, 3);
decimal result = target.ComputeTotalValue();
// Assert
Assert.AreEqual(result, 450M);
}
我们还要测试一下,当我们重置购物车的时候,购物车里的内容是否正确:
[TestMethod]
public void Can_Clear_Contents()
{
// Arrange - create some test products
Product p1 = new Product { ProductID = 1, Name = "P1", Price = 100M };
Product p2 = new Product { ProductID = 2, Name = "P2", Price = 50M };
// Arrange - create a new cart
Cart target = new Cart();
// Arrange - add some items
target.AddItem(p1, 1);
target.AddItem(p2, 1);
// Act - reset the cart
target.Clear();
// Assert
Assert.AreEqual(target.Lines.Count(), 0);
}
好了,现在我们得为添加购物车设置一个触发点了,那就是为商品加上“Add to cart” 按钮,编辑Views/Shared/ProductSummary.cshtml 文件,为它添加一个按钮。
@model SportsStore.Domain.Entities.Product
<div class="item">
<h3>@Model.Name</h3>
@Model.Description
@using(Html.BeginForm("AddToCart", "Cart")) {
@Html.HiddenFor(x => x.ProductID)
@Html.Hidden("returnUrl", Request.Url.PathAndQuery)
<input type="submit" value="+ Add to cart" />
}
<h4>@Model.Price.ToString("c")</h4>
</div>
我们添加了一个Razor语句块,为每个产品链接创建一个小HTML,当我们提交时,它将调用Cart控制器的AddToCart action 方法,现在我们就去看看Cart控制器吧!哦,差点忘了,我们还得为我们这个BeginForm生成的表单添加一个css,不然它实在太丑了,打开你的Site.css文件,添加如下代码:
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.BeginForm helper意味着每个“Add to cart”按钮都将被渲染在自己分离的Html表单元素中,这可能让你惊讶,ASP.NET MVC每一页的forms没有数量限制吗?的确是这样,你可以有尽可能多的表单,只要你需要绝对没问题。这不是什么技术需求,然而,我们的表单都提交给同一个控制器,并且带有不同的参数,这使得按钮的处理非常简单。
实现Cart控制器
右击控制器文件夹,添加一个名为iCartController的控制器:
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 IProductsRepository repository;
public CartController(IProductsRepository repo)
{
repository = repo;
}
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 });
}
private Cart GetCart()
{
Cart cart = (Cart)Session["Cart"];
if (cart == null)
{
cart = new Cart();
Session["Cart"] = cart;
}
return cart;
}
}
}
在这个控制器中有几点需要注意:首先,我们使用了ASP.NET session state特性去存取Cart对象,这也是GetCart方法的用意所在。 ASP.NET有很好的session特性,它能使用cookies 或 URL rewriting与来自用户的请求结合在一起,去形成一个单一的浏览session。一个相关的特性是session state, 它允许我们使用session去整合数据。
我们还需要传递信息到View,当用户点击了继续购物按钮时,购物车对象和链接地址需要显示出来,我们想在Model文件夹创建一个简单的Viewmodel,命名为CartIndexViewModel:
using SportsStore.Domain.Entities;
namespace SportsStore.WebUI.Models
{
public class CartIndexViewModel
{
public Cart Cart { get; set; }
public string ReturnUrl { get; set; }
}
}
现在我们需要在Cart控制器中实现Index方法:
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
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 });
}
private Cart GetCart()
{
Cart cart = (Cart)Session["Cart"];
if (cart == null)
{
cart = new Cart();
Session["Cart"] = cart;
}
return cart;
}
}
}
最后一步,我们要创建一个强类型的Index View,右击Index方法,选择添加视图:
修改代码如下:
@model SportsStore.WebUI.Models.CartIndexViewModel
@{
ViewBag.Title = "Sports Store: 你的购物车";
}
<h2>你的购物车</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>
添加样式单代码:
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;
}
运行程序并选择产品添加到购物车,你将看到如下画面:
现在我们已经有了一个基本的购物功能,但它还有很多问题,在下一篇中,我们将进一步改进并完善我们的购物车,如果你已经想到了更好的完善方案,在下一篇中,我们可以对比一下,看看我们想的是否一致!请继续关注我的续篇!
分享到:
相关推荐
Ninject & Moq for MVC3,
ASP.Net MVC4 Ninject 依赖注入
通过学习如何在MVC5中使用Ninject框架,编写的示例代码
它能够帮助你把应用程序分离成一个个松耦合、高内聚的模块,然后用一种灵活的方式组装起来。通过使用Ninject配套你的软件架构,那么代码将会变得更加容易编写、重用性强、易于测试和修改。 MVC4 配合 Ninject 3 更...
【ASP.NET编程知识】ASP.NET MVC使用Ninject的方法教程.docx
在ASP.NET Web API和ASP.NET Web MVC中使用Ninject,
主要给大家介绍了关于ASP.NET MVC使用Ninject的相关资料,文中通过示例代码介绍的非常详细,对大家啊的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
1.创建一个空白解决方案 2.添加一个类库 名称为XXX.Domain 3.添加一个ASP.MVC名称为XXX.WebUI 4.选着空模版,勾选MVC核心引用 5.添加单元测试项目XXX.UntiTests 6.在程序包控制台里面输入以下代码 代码如下: ...
itera-virtual-book-library 使用 EntityFramework 6.0 beta、OWIN、Ninject、NLog 和各种其他框架的 ASP.NET MVC 5 应用程序。 这是我在 2013 年为 Itera 编写的一个简单项目,用于培训目的。 背后的源代码 。
MVC.net + IOC(Ninject) 的示例源码,比较简单,适合IOC的入门学习。
在学习asp.netmvc的时候要用到一个DI 容器,其非常简单优雅并且很实用。但是中文教程不多。本人在博客园中看到这个文章感觉写的不错就下载整理了一下。希望对大家有所帮助,同时也非常感谢原作者。
YQBlog - 基于Asp.Net MVC3 EF4.0技术的个人博客系统。 网站结构: 权限:.net membership 文章分类:category.js(json格式存储) 文章内容:表 blog_article,blog_articledetail 以及 视图 blog_varticle ...
第一个版本将下载NuGet软件包,然后,该应用程序将运行。 请设置项目Mvc4DDD.Administration为启动项目。 技术信息: 使用Microsoft Visual Studio 2013开发; 表示层使用MVC4; 体系结构:DDD(域驱动设计);...
[Packt Publishing] 精通 Ninject 依赖注入 (英文版) [Packt Publishing] Mastering Ninject for Dependency Injection (E-Book) ☆ 图书概要:☆ Learn how Ninject facilitates the implementation of ...
Ninject是一个轻量级的基于.Net平台的依赖注入(IOC)框架。该资源为其源码,来源github
MVC5使用Ninject实现注册依赖项解析器; Ninject.Web.Common没有NinjectWebCommon.cs
Ninject.Web.Mvc 此扩展允许和项目之间的集成。 要使用它,只需使您的HttpApplication(通常在Global.asax.cs中)扩展NinjectHttpApplication: public class YourWebApplication : NinjectHttpApplication { ...
ninject demo 做了自己使用的,现在分享出来,共大家使用
UNIT, AUTOfac ,ninject,自己写的demo 留着自己用的,现在分享出来共大家使用