ASP.NET MVC 音乐商店 - 8. 使用 Ajax 更新的购物车

在这个项目中,我们将允许用户在没有注册登录的情况下将专辑加入购物车,但是,在完成结账的时候必须完成注册工作。购物和结账将会被分离到两个控制器中:一个 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<OrderDetail> 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<Album> Albums { get; set; }
public DbSet<Genre> Genres { get; set; }
public DbSet<Artist> Artists { get; set; }
public DbSet<Cart> Carts { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderDetail> 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<Cart> 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<Cart> 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";
}
<script src="/Scripts/jquery-1.4.4.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
// Document.ready -> link up remove event handler
        $(".RemoveLink").click(function () {
// Get the id from the link
            var recordToDelete = $(this).attr("data-id");
if (recordToDelete != '') {
// Perform the ajax post
                $.post("/ShoppingCart/RemoveFromCart", { "id": recordToDelete },
function (data) {
// Successful requests get here
// Update the page elements
    if (data.ItemCount == 0) {
$('#row-' + data.DeleteId).fadeOut('slow');
} else {
$('#item-count-' + data.DeleteId).text(data.ItemCount);
}
$('#cart-total').text(data.CartTotal);
$('#update-message').text(data.Message);
$('#cart-status').text('Cart (' + data.CartCount + ')');
});
}
});
});
function handleUpdate() {
// Load and deserialize the returned JSON data
        var json = context.get_data();
var data = Sys.Serialization.JavaScriptSerializer.deserialize(json);
// Update the page elements
        if (data.ItemCount == 0) {
$('#row-' + data.DeleteId).fadeOut('slow');
} else {
$('#item-count-' + data.DeleteId).text(data.ItemCount);
}
$('#cart-total').text(data.CartTotal);
$('#update-message').text(data.Message);
$('#cart-status').text('Cart (' + data.CartCount + ')');
}
</script>
<h3>
<em>Review</em> your cart:
</h3>
<p class="button">
@Html.ActionLink("Checkout >>", "AddressAndPayment", "Checkout")
</p>
<div id="update-message">
</div>
<table>
<tr>
<th>
Album Name
</th>
<th>
Price (each)
</th>
<th>
Quantity
</th>
<th>
</th>
</tr>
@foreach (var item in Model.CartItems)
{ <tr id="row-@item.RecordId">
<td>
@Html.ActionLink(item.Album.Title, "Details", "Store", new { id = item.AlbumId }, null)
</td>
<td>
@item.Album.Price
</td>
<td id="item-count-@item.RecordId">
@item.Count
</td>
<td>
<a href="#" class="RemoveLink" data-id="@item.RecordId">Remove from cart</a>
</td>
</tr>
}
<tr>
<td>
Total
</td>
<td>
</td>
<td>
</td>
<td id="cart-total">
@Model.CartTotal
</td>
</tr>
</table>

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

@model MvcMusicStore.Models.Album
@{
ViewBag.Title = "Album - " + Model.Title;
}
<h2>@Model.Title</h2>
<p>
<img alt="@Model.Title" src="@Model.AlbumArtUrl" />
</p>
<div id="album-details">
<p>
<em>Genre:</em> @Model.Genre.Name
</p>
<p>
<em>Artist:</em> @Model.Artist.Name
</p>
<p>
<em>Price:</em> @String.Format("{0:F}", Model.Price)
</p>
<p class="button">
@Html.ActionLink("Add to cart", "AddToCart", "ShoppingCart", new { id = Model.AlbumId }, "")
</p>
</div>

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

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

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

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

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

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

ASP.NET MVC 音乐商店 - 8. 使用 Ajax 更新的购物车相关推荐

  1. ASP.NET MVC 音乐商店 - 目录

    这一个系列的内容来自微软的音乐商店 Music Store, 这是项目在 Codeplex 上的地址:http://mvcmusicstore.codeplex.com/. 这个项目使用 ASP.NE ...

  2. ASP.NET MVC 音乐商店 - 9. 注册和结账

    ASP.NET MVC 音乐商店 - 9. 注册和结账 转自http://www.cnblogs.com/haogj/archive/2011/11/20/2255675.html 在这一节,我们将创 ...

  3. ASP.NET MVC 音乐商店 - 10. 完成导航和站点的设计

    ASP.NET MVC 音乐商店 - 10. 完成导航和站点的设计 我们已经完成了网站的大部分工作,但是,还有一些添加到站点的导航功能,主页,以及商店的浏览页面. 创建购物车汇总部分视图 我们希望在整 ...

  4. ASP.NET MVC 音乐商店 - 0 概览

    下载地址:http://mvcmusicstore.codeplex.com/ MVC 音乐店是用来介绍和展示使用 ASP.NETMVC 以及 Visual Studio 进行 Web 开发的示例应用 ...

  5. ASP.NET MVC 音乐商店完整项目示例

    MusicStore 项目的完整配套视频! 这是一个系列文章,原文内容出自微软的 MusicStore. 首先对原文内容进行了简单的翻译,以方便大家参考,另外对于其中的部分内容,也进行了简单的分析,使 ...

  6. ASP.NET MVC 音乐商店 - 2.控制器

    在典型的 Web 应用中,用户请求的 URL 地址通常映射到保存在网站中的文件上,例如,当用户请求 /Products.aspx 的时候,或者 /Products.php 的时候,很可能是在通过处理 ...

  7. ASP.NET MVC 音乐商店 - 1 创建项目

    转载自思胜.net培训高级讲师郝冠军博客http://www.cnblogs.com/haogj/archive/2011/11/08/2241710. 我们的项目从在 Visual Studio 中 ...

  8. ASP.NET MVC 音乐商店 - 7.成员管理和授权

    MusicStore 项目的完整配套视频! 目前,我们的 Store Manager 可以被任何人访问,让我们限制一下对站点管理的访问. 增加 AccountController 和 相应的视图 在全 ...

  9. ASP.NET MVC 音乐商店 - 6. 使用 DataAnnotations 进行模型验证

    在前面的创建专辑与编辑专辑的表单中存在一个问题:我们没有进行任何验证.字段的内容可以不输入,或者在价格的字段中输入一些字符,在执行程序的时候,这些错误会导致数据库保存过程中出现错误,我们将会看到来自数 ...

最新文章

  1. Java开发最常犯的10个错误,打死都不要犯!
  2. 2022秋招大战:算法岗挤破头,JAVA开发也被迫内卷
  3. 皮一皮:叫车就要叫这样的,霸气...
  4. 6.5 scp:远程文件复制
  5. python三维数据转换成二维_5大Python可视化库到底选哪个好?一篇文章搞定从选库到教学...
  6. 法学学士学位的完整形式是什么?
  7. b站前端大佬_知乎大佬强烈热推的5个自学网站,看了几个月,月薪三千涨三万...
  8. 高中生兴趣班c语言,C语言电脑编程兴趣班测试题目资料.doc
  9. scanner 获取控制台信息_Java使用Scanner类进行控制台输入实现方法
  10. RecycleView的LayoutManager
  11. adb java_ADB - javalzy - 博客园
  12. 【HDU6286】2018(容斥)
  13. rf扫描枪_RF枪_RF手持扫描枪_RF手持机
  14. 计算机安全常用防护策略,新手必看
  15. 远程桌面提示 “终端服务器超出了最大允许连接数” 的解决方法
  16. The Generalized Detection Method for the Dim Small Targets by Faster R-CNN Integrated with GAN 论文翻译
  17. 强烈给大家推荐一款简单好用免费的甘特图项目进度管理工具-进度猫
  18. docker -v :rw :ro
  19. lctf2017_2ze4u_reproduce
  20. A10+Android4.0 音频驱动

热门文章

  1. Microsoft Academic Search 微软学术搜索体验
  2. mysql逻辑运算符的优先顺序_布尔逻辑算符的类型和在检索式中优先执行的顺序...
  3. DeepMind用强化学习探索大脑多巴胺对学习的作用
  4. 控制反转(IoC)与依赖注入(DI)详解
  5. 上月最新面过美团大众点评拿到offer的最新面经来了(技术三面+HR)
  6. CSS之vertical-align之野史篇(超越官网的教程)
  7. 《见识》吴军——读后感
  8. 非正弦周期信号的傅里叶级数展开式
  9. C语言如何实现辗转相除法
  10. 计算机三级 网络技术 大题第一题 答题技巧分享