MVC 音乐商店 第 8 部分: 购物车与 Ajax 更新

MVC 音乐商店是介绍,并分步说明了如何使用 ASP.NET MVC 和 Visual Studio 为 web 开发教程应用程序。

MVC 音乐商店是一个轻量级的示例存储实现它卖音乐专辑在线,并实现基本的网站管理、 用户登录,和购物车功能

这个系列教程详细说明所有为构建 ASP.NET MVC 音乐商店示例应用程序采取的步骤。第 8 部分涵盖了使用 Ajax 更新购物车。

我们会允许用户在购物车中放置专辑,无需注册,但他们要先注册为客人完成结帐。购物和签出过程将分成两个控制器: 允许以匿名方式将项添加到购物车,一个购物车原型控制器和一个签出控制器处理签出过程。我们开始与购物车在这一节,然后生成下面一节中的签出过程。

添加 Cart、Order 和 OrderDetail 模型类

我们的购物车和结帐过程将使一些新的类的使用。用鼠标右键单击模型文件夹并添加一个购物车类 (Cart.cs) 用下面的代码

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,允许匿名购物,但表包含名为 RecordId 整数主键。通过公约 》,实体框架代码第一次预期一个名为购物车表的主键将是 CartId 或 ID,但我们可以容易地覆盖,通过注释或代码如果我们想要。这是一个示例的我们如何可以使用简单的约定框架代码第一次实体中时他们适合我们,但我们不受约束他们时他们不。

接下来,用下面的代码添加订单类 (Order.cs)。

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; }
    }
}

此类跟踪订单的交货和摘要信息。然而,它不会编译,因为它取决于我们尚未创建一个类明细表导航属性让我们修复现在通过添加一个类命名 OrderDetail.cs,添加下面的代码

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 类,包括 DbSets,公开这些新的模式类,还包括 DbSet < 艺术家 > 一个最后一次更新。更新后的 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; }
    }
}

管理购物车业务逻辑

下一步,我们会在模型文件夹中创建的商城类。商城模型处理对车表的数据访问。此外,它将处理的业务逻辑的添加和移除项从购物车。

因为我们不想要求用户注册帐户只是将项目添加到购物车,我们将用户分配一个临时的唯一标识符 (使用 GUID 或全局唯一标识符) 当他们访问的购物车。我们会将存储使用 ASP.NET 会话类此 ID。

注意: ASP.NET 会话是一个方便的地方来存储用户特定的信息,将到期后他们离开网站。虽然滥用的会话状态可以有性能影响较大的站点上,我们光使用将工作以及出于演示的目的。

商城类公开下列方法

AddToCart用专辑作为参数,并将其添加用户的购物车。购物车表跟踪每个专辑的数量,因为它包含逻辑,如果需要创建一个新行或只是递增数量,如果用户已经订购了专辑的一个副本。

RemoveFromCart获取专辑 ID,并从用户的购物车中删除它。如果用户仅他们的购物车中有专辑的一个副本,则删除行。

EmptyCart用户的购物车中移除所有项。

GetCartItems检索列表用于显示或处理的 CartItems。

GetCount检索的用户拥有自己的购物车中的专辑总数。

GetTotal计算购物车中的所有项目的总成本。

CreateOrder在签出阶段将购物车转换为顺序。

GetCart一个静态方法,允许我们的控制器来获得购物车对象。它使用GetCartId方法来处理从该用户的会话中读取 CartId。GetCartId 方法需要 HttpContextBase,以便它可以读取用户的 CartId 从用户的会话。

这里是完整的商城类:

using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Web;
 using System.Web.Mvc;
 
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();
        }
    }
}

viewmodels

我们购物的购物车控制器将需要一些复杂信息传达给它的看法,不干净地映射到我们的模型对象。我们不想修改我们的模型以适应我们的意见 ;模型的类应表示我们的域,不是用户界面。一个解决办法是信息的将信息传递到我们使用 ViewBag 类,因为我们做了与存储管理器下拉列表的信息,但通过 ViewBag 传递大量获取难管理的意见。

问题的解决办法是使用viewmodel模式。使用此模式时我们创建强类型化的类,优化我们的特定视图的情况下,和其中公开我们的视图模板所需要的动态值/内容属性我们的控制器类,然后填充并将这些视图优化类传递给我们要使用的视图模板。这使类型安全、 编译时检查和内查看模板编辑器智能感知。

我们会在我们的购物车控制器中创建使用的两种视图模型: ShoppingCartviewmodel 将保存用户的购物车的内容和 ShoppingCartRemoveviewmodel 将用于在用户删除的东西从他们车时显示确认信息。

让我们在以保持组织的事情我们项目的根目录中创建一个新的 viewmodels 文件夹。右键单击项目,选择添加 / 新的文件夹。

viewmodels 文件夹的名称

接下来,添加 viewmodels 文件夹中的 ShoppingCartviewmodel 类。它具有两个属性: 购物车物品和在购物车中保存的所有项目的总价格的十进制数值的列表。

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 添加viewmodels 文件夹中,具有以下四个属性

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; }
    }
}

购物车控制器

购物车控制器有三个主要目的: 将项添加到购物车、 购物车,从删除项目和查看购物车中的项目。它将使三个类的使用我们刚刚创建: ShoppingCartviewmodel,ShoppingCartRemoveviewmodel 和商城。在 StoreController 和 StoreManagerController,我们将添加一个字段来保存 MusicStoreEntities 的实例。

将一种新的购物车控制器添加到使用空的控制器模板的项目。

这里是完整的商城控制器。索引和添加控制器操作应该看起来很熟悉。删除和 CartSummary 的控制器操作处理两种特殊情况下,我们会在下面一节中讨论。

using System.Linq;
 using System.Web.Mvc;
 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 更新

我们下一步就会创建一个购物车索引页,强类型化为 ShoppingCartviewmodel 并使用列表视图模板使用相同的方法

但是,没有使用 Html.ActionLink 来从购物车中删除项目,我们会使用 jQuery 以"丝"在此视图中的所有链接,有 HTML 类 RemoveLink 的 click 事件。而不是发布窗体,此 click 事件处理程序将只是对我们的 RemoveFromCart 控制器操作进行 AJAX 回调。RemoveFromCart 返回 JSON 序列化的结果,其中我们 jQuery 回调然后将分析和执行四个快速更新到使用 jQuery 的页:

  • 1.从列表中移除已删除的专辑
  • 2.更新标头中的购物车计数
  • 3.向用户显示更新消息
  • 4.更新购物车总价格

因为删除方案由索引视图中的 Ajax 回调正在处理,我们不需要附加视图 RemoveFromCart 采取行动。下面是完整的代码为 /ShoppingCart/Index 视图:

@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 + ')');
                    });
            }
        });
    });
</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>

若要测试这一点,我们需要能够将项目添加到我们的购物车。我们会更新我们存储的详细信息视图以包括"添加到购物车"按钮。虽然我们在它,我们可以包含的一些专辑附加信息,我们已经添加了因为我们最后更新此视图: 流派、 艺术家、 价格和唱片集画面。更新的存储详细信息视图代码显示如下所示。

@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>

现在我们可以单击通过商店和测试添加删除专辑,并从我们的购物车。运行该应用程序并浏览到存储索引。

下一步,单击一种类型来查看的相册列表。

现在单击唱片集标题显示我们更新唱片集详细信息视图中,包括"添加到购物车"按钮。

单击"添加到购物车"按钮显示我们购物的购物车索引视图中,购物车摘要列表。

在加载到您的购物车之后,你可以点击从购物车链接删除,请参见 Ajax 更新到您的购物车。

我们构建了购物车,允许未注册用户将项添加到购物车工作。在以下部分中,我们会让他们注册并完成结帐过程。

相关文章

IE6是一个非常老旧的网页浏览器,虽然现在很少人再使用它,但...
PHP中的count()函数是用来计算数组或容器中元素的个数。这个...
使用 AJAX(Asynchronous JavaScript and XML)技术可以在不...
Ajax(Asynchronous JavaScript and XML)是一种用于改进网页...
本文将介绍如何通过AJAX下载Excel文件流。通过AJAX,我们可以...
Ajax是一种用于客户端和服务器之间的异步通信技术。通过Ajax...