博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ASP.NET MVC 音乐商店 - 8. 使用 Ajax 更新的购物车
阅读量:6253 次
发布时间:2019-06-22

本文共 14530 字,大约阅读时间需要 48 分钟。

转自

 

在这个项目中,我们将允许用户在没有注册登录的情况下将专辑加入购物车,但是,在完成结账的时候必须完成注册工作。购物和结账将会被分离到两个控制器中:一个

ShoppingCart 控制器,允许匿名用户使用购物车,另一个 Checkout
控制器处理结账。我们先从购物车的控制器开始,然后在下一部分来处理结帐。

加入购物车,订单和订单明细的模型类

在购物车和结账的处理中将会使用到一些新的类,在 Models 文件夹上右键,然后使用下面的代码增加一个新的类 Cart.

using System.ComponentModel.DataAnnotations; namespace MvcMusicStore.Models {
public class Cart {
[Key] public int RecordId { get; set; } public string CartId { get; set; } public int AlbumId { get; set; } public int Count { get; set; } public System.DateTime DateCreated { get; set; } public virtual Album Album { get; set; } } }

这个类非常类似我们前面使用的类,除了 RecordId 属性上的[Key] 标注之外。我们的购物车拥有一个字符串类型的名为 CartId

的标识,用来允许匿名用户使用购物车,但是,CartId 并不是表的主键,表的主键是整数类型的名为 RecordId的字段,根据约定,EF CodeFirst
将会认为表的主键名为 CartId 或者 Id,不过,如果需要的话,我们可以很容易地通过标注或者代码来重写这个规则。这里例子演示了在使用 EF
CodeFirst 的时候。当我们的表不是约定的样子时,我们也不必被约定所局限。

下一步,使用下面的代码增加订单 Order 类。

using System.Collections.Generic; namespace MvcMusicStore.Models {
public partial class Order {
public int OrderId { get; set; } public string Username { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Address { get; set; } public string City { get; set; } public string State { get; set; } public string PostalCode { get; set; } public string Country { get; set; } public string Phone { get; set; } public string Email { get; set; } public decimal Total { get; set; } public System.DateTime OrderDate { get; set; } public List
OrderDetails { get; set; } } }

这个类跟踪订单的汇总和发货信息,它的结构也不复杂,订单依赖我们这里还没有定义的一个类,通过 OrderDetails

属性来表示订单的明细。我们来定义一下这个 OrderDetail 类。

namespace MvcMusicStore.Models {
public class OrderDetail {
public int OrderDetailId { get; set; } public int OrderId { get; set; } public int AlbumId { get; set; } public int Quantity { get; set; } public decimal UnitPrice { get; set; } public virtual Album Album { get; set; } public virtual Order Order { get; set; } } }

把我们的 MusicStoreEntities 更新一下,以便包含我们新定义的模型类,包括艺术家 Artist,更新之后的

MusicStoreEntities 如下所示。

using System.Data.Entity; namespace MvcMusicStore.Models {
public class MusicStoreEntities : DbContext {
public DbSet
Albums { get; set; } public DbSet
Genres { get; set; } public DbSet
Artists { get; set; } public DbSet
Carts { get; set; } public DbSet
Orders { get; set; } public DbSet
OrderDetails { get; set; } } }

管理购物车业务逻辑

下一步,我们在 Models 文件夹中创建 ShoppingCart 类,ShoppingCart 模型类处理  Cart

表的数据访问,另外,它还需要处理在购物车中增加或者删除项目的业务逻辑。

因为我们并不希望用户必须登录系统才可以使用购物车,对于没有登录的用户,我们需要为他们创建一个临时的唯一标识,这里使用

GUID,或者被称为全局唯一标识符,对于已经登录的用户,我们直接使用他们的名称,这个表示我们保存在 Session 中。

注意:Session 会话可以很方便地存储用户的信息,在用户离开站点之后,这些信息将会过期,滥用 Session 信息会对大型站点产生影响,我们这里使用

Session 达到演示目的。

ShoppingCart 类提供了如下的方法:

AddToCart, 将专辑作为参数加入到购物车中,在 Cart

表中跟踪每个专辑的数量,在这个方法中,我们将会检查是在表中增加一个新行,还是仅仅在用户已经选择的专辑上增加数量。

RemoveFromCart,通过专辑的标识从用户的购物车中将这个专辑的数量减少

1,如果用户仅仅剩下一个,那么就删除这一行。

EmptyCart,删除用户购物车中所有的项目。

GetCartItems,获取购物项目的列表用来显示或者处理。

GetCount,获取用户购物车中专辑的数量

GetTotal,获取购物车中商品的总价

CreateOrder,将购物车转换为结账处理过程中的订单。

GetCart ,这是一个静态方法,用来获取当前用户的购物车对象,它使用 GetCartId 方法来读取保存当前

Session 中的购物车标识,GetCartId 方法需要 HttpContextBase 以便获取当前的 Session。

实际的代码如下:

namespace MvcMusicStore.Models {
public partial class ShoppingCart {
MusicStoreEntities storeDB = new MusicStoreEntities(); string ShoppingCartId { get; set; } public const string CartSessionKey = "CartId"; public static ShoppingCart GetCart(HttpContextBase context) {
var cart = new ShoppingCart(); cart.ShoppingCartId = cart.GetCartId(context); return cart; } // Helper method to simplify shopping cart calls public static ShoppingCart GetCart(Controller controller) {
return GetCart(controller.HttpContext); } public void AddToCart(Album album) {
// Get the matching cart and album instances var cartItem = storeDB.Carts.SingleOrDefault( c => c.CartId == ShoppingCartId && c.AlbumId == album.AlbumId); if (cartItem == null) {
// Create a new cart item if no cart item exists cartItem = new Cart {
AlbumId = album.AlbumId, CartId = ShoppingCartId, Count = 1, DateCreated = DateTime.Now }; storeDB.Carts.Add(cartItem); } else {
// If the item does exist in the cart, then add one to the quantity cartItem.Count++; } // Save changes storeDB.SaveChanges(); } public int RemoveFromCart(int id) {
// Get the cart var cartItem = storeDB.Carts.Single( cart => cart.CartId == ShoppingCartId && cart.RecordId == id); int itemCount = 0; if (cartItem != null) {
if (cartItem.Count > 1) {
cartItem.Count--; itemCount = cartItem.Count; } else {
storeDB.Carts.Remove(cartItem); } // Save changes storeDB.SaveChanges(); } return itemCount; } public void EmptyCart() {
var cartItems = storeDB.Carts.Where(cart => cart.CartId == ShoppingCartId); foreach (var cartItem in cartItems) {
storeDB.Carts.Remove(cartItem); } // Save changes storeDB.SaveChanges(); } public List
GetCartItems() {
return storeDB.Carts.Where(cart => cart.CartId == ShoppingCartId).ToList(); } public int GetCount() {
// Get the count of each item in the cart and sum them up int? count = (from cartItems in storeDB.Carts where cartItems.CartId == ShoppingCartId select (int?)cartItems.Count).Sum(); // Return 0 if all entries are null return count ?? 0; } public decimal GetTotal() {
// Multiply album price by count of that album to get // the current price for each of those albums in the cart // sum all album price totals to get the cart total decimal? total = (from cartItems in storeDB.Carts where cartItems.CartId == ShoppingCartId select (int?)cartItems.Count * cartItems.Album.Price).Sum(); return total ?? decimal.Zero; } public int CreateOrder(Order order) {
decimal orderTotal = 0; var cartItems = GetCartItems(); // Iterate over the items in the cart, adding the order details for each foreach (var item in cartItems) {
var orderDetail = new OrderDetail {
AlbumId = item.AlbumId, OrderId = order.OrderId, UnitPrice = item.Album.Price, Quantity = item.Count }; // Set the order total of the shopping cart orderTotal += (item.Count * item.Album.Price); storeDB.OrderDetails.Add(orderDetail); } // Set the order's total to the orderTotal count order.Total = orderTotal; // Save the order storeDB.SaveChanges(); // Empty the shopping cart EmptyCart(); // Return the OrderId as the confirmation number return order.OrderId; } // We're using HttpContextBase to allow access to cookies. public string GetCartId(HttpContextBase context) {
if (context.Session[CartSessionKey] == null) {
if (!string.IsNullOrWhiteSpace(context.User.Identity.Name)) {
context.Session[CartSessionKey] = context.User.Identity.Name; } else {
// Generate a new random GUID using System.Guid class Guid tempCartId = Guid.NewGuid(); // Send tempCartId back to client as a cookie context.Session[CartSessionKey] = tempCartId.ToString(); } } return context.Session[CartSessionKey].ToString(); } // When a user has logged in, migrate their shopping cart to // be associated with their username public void MigrateCart(string userName) {
var shoppingCart = storeDB.Carts.Where(c => c.CartId == ShoppingCartId); foreach (Cart item in shoppingCart) {
item.CartId = userName; } storeDB.SaveChanges(); } } }

视图模型

我们的 ShoppingCart

控制器需要向视图传递复杂的信息,这些信息与现有的模型并不完全匹配,我们不希望修改模型来适应视图的需要;模型类应该表示领域信息,而不是用户界面。一个解决方案是使用
ViewBag 来向视图传递信息,就像我们在 Store Manager 中的下列列表处理中那样,但是通过 ViewBag 来传递大量信息就不好管理了。

另外一个解决方案是使用视图模型模式,使用这个模式,我们需要创建强类型的用于视图场景的类来表示信息,这个类拥有视图所需要的值或者内容。我们的控制器填充信息,然后传递这种类的对象供视图使用,这样就可以得到强类型的、编译时检查支持,并且在视图模板中带有智能提示。

我们将会创建两个视图模型用于我们的 ShoppingCart 控制器:ShoppingCartViewModel 将会用于用户的购物车,而

ShoppingCartRemoveViewModel 会用于在购物车中删除内容时的确认提示信息。

首先在项目中创建 ViewModels 文件夹来组织我们的项目文件,在项目上点击鼠标的右键,然后选择添加 –〉新文件夹。

                      

命名为 ViewModels

下一步,在 ViewModels 文件夹中增加 ShoppingCartViewModel 类,它包括两个属性,一个 CartItem

的列表,另外一个属性是购物中的总价。

using System.Collections.Generic; using MvcMusicStore.Models; namespace MvcMusicStore.ViewModels {
public class ShoppingCartViewModel {
public List
CartItems { get; set; } public decimal CartTotal { get; set; } } }

然后,增加 ShoppingCartRemoveViewModel 类,它包括五个属性。

namespace MvcMusicStore.ViewModels {
public class ShoppingCartRemoveViewModel {
public string Message { get; set; } public decimal CartTotal { get; set; } public int CartCount { get; set; } public int ItemCount { get; set; } public int DeleteId { get; set; } } }

Shopping Cart 控制器

Shopping Cart

控制器有三个主要的目的:增加项目到购物车,从购物车中删除项目,查看购物车中的项目。控制器使用到我们刚刚创建的三个类:ShoppingCartViewModel,ShoppingCartRemoveViewModel
和 ShoppingCart,像 StoreController 和 StoreManagerController 一样,我们在控制器中增加一个
MusicStoreEntities 字段来操作数据。

在项目中使用空的控制器模板创建 Shopping Cart 控制器

下面是已经完成的控制器代码,Index 和 Add 方法看起来非常熟悉。Remove 和 CartSummary 这两个 Action

方法处理两种特定的场景,我们将在后面讨论。

using MvcMusicStore.Models; using MvcMusicStore.ViewModels; namespace MvcMusicStore.Controllers {
public class ShoppingCartController : Controller {
MusicStoreEntities storeDB = new MusicStoreEntities(); // // GET: /ShoppingCart/ public ActionResult Index() {
var cart = ShoppingCart.GetCart(this.HttpContext); // Set up our ViewModel var viewModel = new ShoppingCartViewModel {
CartItems = cart.GetCartItems(), CartTotal = cart.GetTotal() }; // Return the view return View(viewModel); } // // GET: /Store/AddToCart/5 public ActionResult AddToCart(int id) {
// Retrieve the album from the database var addedAlbum = storeDB.Albums .Single(album => album.AlbumId == id); // Add it to the shopping cart var cart = ShoppingCart.GetCart(this.HttpContext); cart.AddToCart(addedAlbum); // Go back to the main store page for more shopping return RedirectToAction("Index"); } // // AJAX: /ShoppingCart/RemoveFromCart/5 [HttpPost] public ActionResult RemoveFromCart(int id) {
// Remove the item from the cart var cart = ShoppingCart.GetCart(this.HttpContext); // Get the name of the album to display confirmation string albumName = storeDB.Carts .Single(item => item.RecordId == id).Album.Title; // Remove from cart int itemCount = cart.RemoveFromCart(id); // Display the confirmation message var results = new ShoppingCartRemoveViewModel {
Message = Server.HtmlEncode(albumName) + " has been removed from your shopping cart.", CartTotal = cart.GetTotal(), CartCount = cart.GetCount(), ItemCount = itemCount, DeleteId = id }; return Json(results); } // // GET: /ShoppingCart/CartSummary [ChildActionOnly] public ActionResult CartSummary() {
var cart = ShoppingCart.GetCart(this.HttpContext); ViewData["CartCount"] = cart.GetCount(); return PartialView("CartSummary"); } } }

使用 jQuery 进行 Ajax 更新

下面我们将创建 Shopping Cart 的 Index Action 视图,这个视图使用强类型的 ShoppingCartViewModel

,像以前的视图一样,使用 List 视图模板。

在这里,我们不使用 Html.ActionLink 从购物车中删除项目,我们将会使用 JQuery 来包装客户端使用 RemoveLink

的类所有超级链接元素的事件,不是提交表单,而是通过客户端的事件向 RemoveFromCart 控制器方法发出 Ajax 请求,然后
RemoveFromCart 返回 JSON 格式的结果,这个结果被发送到我们在 AjaxOptions 的 OnSucess 参数中创建的
JavaScript 函数,在这里是 handleUpdate,handleUpdate 函数解析 JSON 格式的结果,然后通过 jQuery
执行下面的四个更新。

  1. 从列表中删除专辑
  2. 更新头部的购物车中的数量
  3. 向用户显示更新信息
  4. 更新购物车中的总价

因为在 Index 视图中我们处理了删除的场景,我们就不再需要为 RemoveFromCart 方法增加额外的视图。下面是视图的完整代码。

@model MvcMusicStore.ViewModels.ShoppingCartViewModel @{     ViewBag.Title = "Shopping Cart"; }   

Review your cart:

@Html.ActionLink("Checkout >>", "AddressAndPayment", "Checkout")

@foreach (var item in Model.CartItems) {
}
Album Name Price (each) Quantity
@Html.ActionLink(item.Album.Title, "Details", "Store", new { id = item.AlbumId }, null) @item.Album.Price @item.Count Remove from cart
Total @Model.CartTotal

为了测试一下,我们需要向购物车中增加一些项目,更新 Store 的 Details

视图包含添加到购物车按钮,在这里,我们还需要包含我们后来增加的专辑的一些额外信息,流派,艺术家,价格等等。更新后的视图如下所示。

@model MvcMusicStore.Models.Album @{
ViewBag.Title = "Album - " + Model.Title; }

@Model.Title

@Model.Title

Genre: @Model.Genre.Name

Artist: @Model.Artist.Name

Price: @String.Format("{0:F}", Model.Price)

@Html.ActionLink("Add to cart", "AddToCart", "ShoppingCart", new { id = Model.AlbumId }, "")

现在,我们可以在商店中通过购物车来购买和删除一些项目了。运行程序,浏览 Store 控制器的 Index 。

然后,点击某个分类来查看专辑的列表。

点击某个专辑来显示专辑的详细内容,现在已经有了加入购物车的按钮。

点击加入购物车之后,可以在购物车中看到。

在购物车中,可以点击从购物车中删除的链接,将会看到 Ajax 更新购物车的效果。

现在的购物车允许没有注册的用户使用购物车添加项目,在下一部分,我们将允许匿名用户注册和完成结账的处理。

转载地址:http://gpysa.baihongyu.com/

你可能感兴趣的文章
整理了一份招PHP高级工程师的面试题(转)
查看>>
Document对象中的一些重要的属性和方法(笔记)
查看>>
学习Raft算法的笔记
查看>>
[LeetCode]题解(python):053-Maximum Subarray
查看>>
SharePoint中的用户信息和检索的有关知识
查看>>
Linux系统在启动过程中grub引导文件丢失的解决方法
查看>>
day15-JavaScript条件语句和函数的定义
查看>>
使用acl网络通信库的 redis c++ 模块开发 redis 应用
查看>>
Geek的入门神器:micropython-能跑python的stm32开发板
查看>>
利用脚本获取mysql的tps,qps等状态信息
查看>>
Python Json数据排序
查看>>
[原创] zabbix学习之旅七:如何远程操作被监控机器
查看>>
第十一周编程总结
查看>>
PhoneURLConnectGEt
查看>>
darknet源码学习
查看>>
dl,dt,dd的用法
查看>>
外面的世界很精彩,然而等待你的人却可能已不在
查看>>
成为一名阿里P7Java架构师需要的技术准备(转载)
查看>>
华为oj 挑7
查看>>
【吴恩达机器学习】学习笔记——1.5无监督学习
查看>>