购物车

作者 :Erik Reitan

下载 Wingtip Toys 示例项目 (C#) 下载电子书 (PDF)

本教程系列将介绍使用 ASP.NET 4.5 和 Microsoft Visual Studio Express 2013 for Web 生成 ASP.NET Web Forms应用程序的基础知识。 本教程系列随附了一个包含 C# 源代码的Visual Studio 2013项目。

本教程介绍将购物车添加到 Wingtip Toys 示例 ASP.NET Web Forms应用程序所需的业务逻辑。 本教程基于上一教程“显示数据项和详细信息”,是 Wingtip 玩具商店教程系列的一部分。 完成本教程后,示例应用的用户将能够在其购物车中添加、删除和修改产品。

学习内容:

  1. 如何为 Web 应用程序创建购物车。
  2. 如何使用户能够将商品添加到购物车。
  3. 如何添加 GridView 控件以显示购物车详细信息。
  4. 如何计算和显示订单总计。
  5. 如何删除和更新购物车中的项。
  6. 如何包括购物车计数器。

本教程中的代码功能:

  1. 实体框架代码优先
  2. 数据注释
  3. 强类型数据控件
  4. 模型绑定

创建购物车

在本教程系列的前面部分,你添加了页面和代码来查看数据库中的产品数据。 在本教程中,你将创建一个购物车来管理用户感兴趣的产品。 用户将能够浏览商品并将其添加到购物车,即使他们未注册或登录。 若要管理购物车访问,在用户 ID 首次访问购物车时,你将使用全局唯一标识符 (GUID) 向用户分配唯一的。 你将使用 ASP.NET 会话状态来存储此 ID 状态。

注意

ASP.NET 会话状态是存储用户特定信息(将在用户离开网站后过期)的便利位置。 虽然滥用会话状态可能会对较大的站点产生性能影响,但对会话状态的轻量使用非常适合用于演示目的。 Wingtip Toys 示例项目演示如何在没有外部提供程序的情况下使用会话状态,其中会话状态存储在托管网站的 Web 服务器上。 对于提供应用程序的多个实例的较大站点,或者对于在不同服务器上运行应用程序的多个实例的站点,请考虑使用 Windows Azure 缓存服务。 此缓存服务提供位于网站外部的分布式缓存服务,解决了使用进程内会话状态的问题。 有关详细信息,请参阅 如何将 ASP.NET 会话状态与 Windows Azure 网站配合使用

将 CartItem 添加为模型类

在本教程系列的前面部分,你通过在 Models 文件夹中创建 Category 和 类来定义类别和Product产品数据的架构。 现在,添加新类来定义购物车的架构。 在本教程的后面部分,你将添加一个类来处理对 CartItem 表的数据访问。 此类将提供用于在购物车中添加、删除和更新项的业务逻辑。

  1. 右键单击“ 模型 ”文件夹,然后选择“ 添加 ->新建项”。

    购物车 - 新项目

  2. 随即出现“添加新项”对话框。 选择“ 代码”,然后选择“ ”。

    “购物车 - 添加新项”对话框

  3. 将此新类命名 为 CartItem.cs

  4. 单击“添加”。
    新的类文件将显示在编辑器中。

  5. 将默认代码替换为以下代码:

    using System.ComponentModel.DataAnnotations;
    
    namespace WingtipToys.Models
    {
        public class CartItem
        {
            [Key]
            public string ItemId { get; set; }
    
            public string CartId { get; set; }
    
            public int Quantity { get; set; }
    
            public System.DateTime DateCreated { get; set; }
    
            public int ProductId { get; set; }
    
            public virtual Product Product { get; set; }
    
        }
    }
    

CartItem 包含将定义用户添加到购物车的每个产品的架构。 此类类似于前面在本教程系列中创建的其他架构类。 按照约定,Entity Framework Code First 预期表的主键 CartItemCartItemIdID。 但是,代码会使用数据注释 [Key] 属性替代默认行为。 Key ItemId 属性的 属性指定 属性ItemID是主键。

属性 CartId 指定 ID 与要购买的项关联的用户的 。 当用户访问购物车时,你将添加代码来创建此 ID 用户。 这也 ID 将存储为 ASP.NET 会话变量。

更新产品上下文

除了添加 CartItem 类外,还需要更新管理实体类并提供对数据库的数据访问的数据库上下文类。 为此,需要将新创建的 CartItem 模型类添加到 类 ProductContext

  1. “解决方案资源管理器”中,找到并打开 Models 文件夹中的 ProductContext.cs 文件。

  2. 将突出显示的代码添加到 ProductContext.cs 文件,如下所示:

    using System.Data.Entity;
     
    namespace WingtipToys.Models
    {
        public class ProductContext : DbContext
        {
            public ProductContext()
                : base("WingtipToys")
            {
            }
     
            public DbSet<Category> Categories { get; set; }
            public DbSet<Product> Products { get; set; }
            public DbSet<CartItem> ShoppingCartItems { get; set; }
        }
    }
    

如前面在本教程系列中所述, ProductContext.cs 文件中的代码会添加 System.Data.Entity 命名空间,以便你有权访问实体框架的所有核心功能。 此功能包括通过使用强类型对象来查询、插入、更新和删除数据的功能。 类 ProductContext 添加了对新添加 CartItem 的模型类的访问权限。

管理购物车业务逻辑

接下来,将在新的逻辑文件夹中创建 ShoppingCart 类。 类 ShoppingCart 处理对 CartItem 表的数据访问。 类还将包括用于在购物车中添加、删除和更新项的业务逻辑。

要添加的购物车逻辑将包含用于管理以下操作的功能:

  1. 将商品添加到购物车
  2. 从购物车中删除项目
  3. 获取购物车 ID
  4. 从购物车中检索项目
  5. 所有购物车项的总和
  6. 更新购物车数据

购物车页 (ShoppingCart.aspx) ,购物车类将一起使用来访问购物车数据。 购物车页面将显示用户添加到购物车的所有项。 除了购物车页面和类,你将 (AddToCart.aspx) 创建一个页面,以将产品添加到购物车。 还将代码添加到 ProductList.aspx 页面和 ProductDetails.aspx 页面,该页将提供 指向 AddToCart.aspx 页面的链接,以便用户可以将产品添加到购物车。

下图显示了当用户将产品添加到购物车时发生的基本过程。

购物车 - 添加到购物车

当用户单击 ProductList.aspx 页面或 ProductDetails.aspx 页上的“添加到购物车”链接时,应用程序将导航到 AddToCart.aspx 页,然后自动导航到 ShoppingCart.aspx 页。 AddToCart.aspx 页将通过调用 ShoppingCart 类中的 方法将所选产品添加到购物车。 ShoppingCart.aspx 页面将显示已添加到购物车的产品。

创建购物车类

ShoppingCart 将添加到应用程序中的单独文件夹中,以便模型 (Models 文件夹) 、 (根文件夹) 的页面和逻辑 (逻辑文件夹) 之间有明确的区别。

  1. 解决方案资源管理器中,右键单击 WingtipToys项目,然后选择“添加新>文件夹”。 将新文件夹命名 为 Logic

  2. 右键单击 “逻辑 ”文件夹,然后选择“ 添加 ->新建项”。

  3. 添加名为 ShoppingCartActions.cs 的新类文件。

  4. 将默认代码替换为以下代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using WingtipToys.Models;
    
    namespace WingtipToys.Logic
    {
      public class ShoppingCartActions : IDisposable
      {
        public string ShoppingCartId { get; set; }
    
        private ProductContext _db = new ProductContext();
    
        public const string CartSessionKey = "CartId";
    
        public void AddToCart(int id)
        {
          // Retrieve the product from the database.           
          ShoppingCartId = GetCartId();
    
          var cartItem = _db.ShoppingCartItems.SingleOrDefault(
              c => c.CartId == ShoppingCartId
              && c.ProductId == id);
          if (cartItem == null)
          {
            // Create a new cart item if no cart item exists.                 
            cartItem = new CartItem
            {
              ItemId = Guid.NewGuid().ToString(),
              ProductId = id,
              CartId = ShoppingCartId,
              Product = _db.Products.SingleOrDefault(
               p => p.ProductID == id),
              Quantity = 1,
              DateCreated = DateTime.Now
            };
    
            _db.ShoppingCartItems.Add(cartItem);
          }
          else
          {
            // If the item does exist in the cart,                  
            // then add one to the quantity.                 
            cartItem.Quantity++;
          }
          _db.SaveChanges();
        }
    
        public void Dispose()
        {
          if (_db != null)
          {
            _db.Dispose();
            _db = null;
          }
        }
    
        public string GetCartId()
        {
          if (HttpContext.Current.Session[CartSessionKey] == null)
          {
            if (!string.IsNullOrWhiteSpace(HttpContext.Current.User.Identity.Name))
            {
              HttpContext.Current.Session[CartSessionKey] = HttpContext.Current.User.Identity.Name;
            }
            else
            {
              // Generate a new random GUID using System.Guid class.     
              Guid tempCartId = Guid.NewGuid();
              HttpContext.Current.Session[CartSessionKey] = tempCartId.ToString();
            }
          }
          return HttpContext.Current.Session[CartSessionKey].ToString();
        }
    
        public List<CartItem> GetCartItems()
        {
          ShoppingCartId = GetCartId();
    
          return _db.ShoppingCartItems.Where(
              c => c.CartId == ShoppingCartId).ToList();
        }
      }
    }
    

方法 AddToCart 使单个产品能够基于产品 ID包含在购物车中。 产品将添加到购物车,或者如果购物车已包含该产品的商品,则数量递增。

方法 GetCartId 返回用户的购物车 ID 。 购物车 ID 用于跟踪用户在购物车中拥有的项。 如果用户没有现有的购物车 ID,则会为他们创建新购物车 ID 。 如果用户以注册用户身份登录,则购物车 ID 将设置为其用户名。 但是,如果用户未登录,则购物车 ID 将设置为 GUID) (唯一值。 GUID 确保基于会话,仅为每个用户创建一个购物车。

方法 GetCartItems 为用户返回购物车项的列表。 在本教程的后面部分,你将看到模型绑定用于使用 GetCartItems 方法在购物车中显示购物车项。

创建 Add-To-Cart 功能

如前所述,你将创建一个名为 AddToCart.aspx 的处理页面,用于将新产品添加到用户的购物车。 此页面将在刚创建的 类中ShoppingCart调用 AddToCart 方法。 AddToCart.aspx 页面需要将产品ID传递给它。 在 类中ShoppingCart调用 AddToCart 方法时,将使用此产品ID

注意

你将修改此页的代码隐藏 (AddToCart.aspx.cs) ,而不是 addToCart.aspx) (页面 UI。

若要创建 Add-To-Cart 功能,请执行以下操作:

  1. “解决方案资源管理器”中,右键单击“WingtipToys”项目,单击“添加 ->新建项”。
    随即出现“添加新项”对话框。

  2. 将 (Web 窗体) 的标准新页添加到名为 AddToCart.aspx 的应用程序

    购物车 - 添加 Web 窗体

  3. “解决方案资源管理器”中,右键单击“AddToCart.aspx”页,然后单击“查看代码”。 AddToCart.aspx.cs 代码隐藏文件在编辑器中打开。

  4. AddToCart.aspx.cs 代码隐藏中的现有代码替换为以下内容:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Diagnostics;
    using WingtipToys.Logic;
    
    namespace WingtipToys
    {
      public partial class AddToCart : System.Web.UI.Page
      {
        protected void Page_Load(object sender, EventArgs e)
        {
          string rawId = Request.QueryString["ProductID"];
          int productId;
          if (!String.IsNullOrEmpty(rawId) && int.TryParse(rawId, out productId))
          {
            using (ShoppingCartActions usersShoppingCart = new ShoppingCartActions())
            {
              usersShoppingCart.AddToCart(Convert.ToInt16(rawId));
            }
    
          }
          else
          {
            Debug.Fail("ERROR : We should never get to AddToCart.aspx without a ProductId.");
            throw new Exception("ERROR : It is illegal to load AddToCart.aspx without setting a ProductId.");
          }
          Response.Redirect("ShoppingCart.aspx");
        }
      }
    }
    

加载 AddToCart.aspx 页面时,将从查询字符串检索产品 ID 。 接下来,创建购物车类的一个实例,并用于调用 AddToCart 之前在本教程中添加的方法。 AddToCartShoppingCartActions.cs 文件中包含的 方法包括将所选产品添加到购物车或递增所选产品数量的逻辑。 如果产品尚未添加到购物车,则会将该产品添加到 CartItem 数据库的表中。 如果产品已添加到购物车,并且用户添加了同一产品的附加项,则表中的产品数量将递 CartItem 增。 最后,该页面将重定向回你在下一步中添加的 ShoppingCart.aspx 页面,用户可在其中看到购物车中更新的项列表。

如前所述,用户 ID 用于标识与特定用户关联的产品。 每次用户将产品添加到购物车时,都会 ID 将其添加到表中的一行 CartItem

创建购物车 UI

ShoppingCart.aspx 页面将显示用户已添加到购物车的产品。 它还将提供在购物车中添加、删除和更新项目的功能。

  1. “解决方案资源管理器”中,右键单击“WingtipToys”,单击“添加 ->新建项”。
    随即出现“添加新项”对话框。

  2. 通过选择“使用母版页的 Web 窗体”, (包含母版页的 Web 窗体) 添加新页面。 将新页面命名为 ShoppingCart.aspx

  3. 选择“ Site.Master ”,将母版页附加到新创建的 .aspx 页。

  4. ShoppingCart.aspx 页中,将现有标记替换为以下标记:

    <%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="ShoppingCart.aspx.cs" Inherits="WingtipToys.ShoppingCart" %>
    <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
        <div id="ShoppingCartTitle" runat="server" class="ContentHead"><h1>Shopping Cart</h1></div>
        <asp:GridView ID="CartList" runat="server" AutoGenerateColumns="False" ShowFooter="True" GridLines="Vertical" CellPadding="4"
            ItemType="WingtipToys.Models.CartItem" SelectMethod="GetShoppingCartItems" 
            CssClass="table table-striped table-bordered" >   
            <Columns>
            <asp:BoundField DataField="ProductID" HeaderText="ID" SortExpression="ProductID" />        
            <asp:BoundField DataField="Product.ProductName" HeaderText="Name" />        
            <asp:BoundField DataField="Product.UnitPrice" HeaderText="Price (each)" DataFormatString="{0:c}"/>     
            <asp:TemplateField   HeaderText="Quantity">            
                    <ItemTemplate>
                        <asp:TextBox ID="PurchaseQuantity" Width="40" runat="server" Text="<%#: Item.Quantity %>"></asp:TextBox> 
                    </ItemTemplate>        
            </asp:TemplateField>    
            <asp:TemplateField HeaderText="Item Total">            
                    <ItemTemplate>
                        <%#: String.Format("{0:c}", ((Convert.ToDouble(Item.Quantity)) *  Convert.ToDouble(Item.Product.UnitPrice)))%>
                    </ItemTemplate>        
            </asp:TemplateField> 
            <asp:TemplateField HeaderText="Remove Item">            
                    <ItemTemplate>
                        <asp:CheckBox id="Remove" runat="server"></asp:CheckBox>
                    </ItemTemplate>        
            </asp:TemplateField>    
            </Columns>    
        </asp:GridView>
        <div>
            <p></p>
            <strong>
                <asp:Label ID="LabelTotalText" runat="server" Text="Order Total: "></asp:Label>
                <asp:Label ID="lblTotal" runat="server" EnableViewState="false"></asp:Label>
            </strong> 
        </div>
        <br />
    </asp:Content>
    

ShoppingCart.aspx 页包括名为 的 CartListGridView 控件。 此控件使用模型绑定将购物车数据从数据库绑定到 GridView 控件。 设置 ItemTypeGridView 控件的 属性时,数据绑定表达式 Item 在控件的标记中可用,并且控件将变为强类型。 如本教程系列前面所述,可以使用 IntelliSense 选择对象的详细信息 Item 。 若要配置数据控件以使用模型绑定来选择数据,请设置 SelectMethod 控件的 属性。 在上面的标记中,将 设置为 SelectMethod 使用返回对象列表的 CartItem GetShoppingCartItems 方法。 GridView 数据控件在页面生命周期中的适当时间调用 方法,并自动绑定返回的数据。 GetShoppingCartItems仍必须添加 方法。

检索购物车项

接下来,将代码添加到 ShoppingCart.aspx.cs 代码隐藏,以检索和填充购物车 UI。

  1. “解决方案资源管理器”中,右键单击“ShoppingCart.aspx”页,然后单击“查看代码”。 ShoppingCart.aspx.cs 代码隐藏文件在编辑器中打开。

  2. 将现有代码替换为以下代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using WingtipToys.Models;
    using WingtipToys.Logic;
    
    namespace WingtipToys
    {
      public partial class ShoppingCart : System.Web.UI.Page
      {
        protected void Page_Load(object sender, EventArgs e)
        {
    
        }
    
        public List<CartItem> GetShoppingCartItems()
        {
          ShoppingCartActions actions = new ShoppingCartActions();
          return actions.GetCartItems();
        }
      }
    }
    

如上所述, GridView 数据控件在页面生命周期中的适当时间调用 GetShoppingCartItems 方法,并自动绑定返回的数据。 方法 GetShoppingCartItems 创建 对象的实例 ShoppingCartActions 。 然后,代码使用该实例通过调用 GetCartItems 方法返回购物车中的项。

将产品添加到购物车

显示 ProductList.aspxProductDetails.aspx 页面时,用户将能够使用链接将产品添加到购物车。 单击链接时,应用程序会导航到名为 AddToCart.aspx 的处理页。 AddToCart.aspx 页将调用AddToCart本教程前面添加的 ShoppingCart 类中的 方法。

现在,你将向 ProductList.aspx 页面和 ProductDetails.aspx 页面添加“添加到购物车”链接。 此链接将包括从数据库检索到的产品 ID

  1. 解决方案资源管理器中,找到并打开名为 ProductList.aspx 的页面

  2. 将黄色突出显示的标记添加到 ProductList.aspx 页面,以便整个页面如下所示:

    <%@ Page Title="Products" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" 
             CodeBehind="ProductList.aspx.cs" Inherits="WingtipToys.ProductList" %>
    <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
        <section>
            <div>
                <hgroup>
                    <h2><%: Page.Title %></h2>
                </hgroup>
    
                <asp:ListView ID="productList" runat="server" 
                    DataKeyNames="ProductID" GroupItemCount="4"
                    ItemType="WingtipToys.Models.Product" SelectMethod="GetProducts">
                    <EmptyDataTemplate>
                        <table runat="server">
                            <tr>
                                <td>No data was returned.</td>
                            </tr>
                        </table>
                    </EmptyDataTemplate>
                    <EmptyItemTemplate>
                        <td runat="server" />
                    </EmptyItemTemplate>
                    <GroupTemplate>
                        <tr id="itemPlaceholderContainer" runat="server">
                            <td id="itemPlaceholder" runat="server"></td>
                        </tr>
                    </GroupTemplate>
                    <ItemTemplate>
                        <td runat="server">
                            <table>
                                <tr>
                                    <td>
                                        <a href="ProductDetails.aspx?productID=<%#:Item.ProductID%>">
                                            <img src="/Catalog/Images/Thumbs/<%#:Item.ImagePath%>"
                                                width="100" height="75" style="border: solid" /></a>
                                    </td>
                                </tr>
                                <tr>
                                    <td>
                                        <a href="ProductDetails.aspx?productID=<%#:Item.ProductID%>">
                                            <span>
                                                <%#:Item.ProductName%>
                                            </span>
                                        </a>
                                        <br />
                                        <span>
                                            <b>Price: </b><%#:String.Format("{0:c}", Item.UnitPrice)%>
                                        </span>
                                        <br />
                                        <a href="/AddToCart.aspx?productID=<%#:Item.ProductID %>">               
                                            <span class="ProductListItem">
                                                <b>Add To Cart<b>
                                            </span>           
                                        </a>
                                    </td>
                                </tr>
                                <tr>
                                    <td>&nbsp;</td>
                                </tr>
                            </table>
                            </p>
                        </td>
                    </ItemTemplate>
                    <LayoutTemplate>
                        <table runat="server" style="width:100%;">
                            <tbody>
                                <tr runat="server">
                                    <td runat="server">
                                        <table id="groupPlaceholderContainer" runat="server" style="width:100%">
                                            <tr id="groupPlaceholder" runat="server"></tr>
                                        </table>
                                    </td>
                                </tr>
                                <tr runat="server">
                                    <td runat="server"></td>
                                </tr>
                                <tr></tr>
                            </tbody>
                        </table>
                    </LayoutTemplate>
                </asp:ListView>
            </div>
        </section>
    </asp:Content>
    

测试购物车

运行应用程序以查看如何将产品添加到购物车。

  1. F5 运行该应用程序。
    项目重新创建数据库后,浏览器将打开并显示 Default.aspx 页。

  2. 从类别导航菜单中选择“ 汽车 ”。
    “ProductList.aspx”页显示仅显示“汽车”类别中包含的产品。

    购物车 - 汽车

  3. 单击可转换汽车) (列出的第一个产品旁边的 “添加到购物车 ”链接。
    将显示 ShoppingCart.aspx 页面,其中显示了购物车中的选择。

    购物车 - 购物车

  4. 通过从类别导航菜单中选择“ 平面 ”来查看其他产品。

  5. 单击列出的第一个产品旁边的 “添加到购物车” 链接。
    ShoppingCart.aspx 页面将显示附加项。

  6. 关闭浏览器。

计算和显示订单总计

除了将产品添加到购物车之外,还将向 类添加方法GetTotalShoppingCart,并在购物车页面中显示总订单金额。

  1. 解决方案资源管理器 中,打开 Logic 文件夹中的 ShoppingCartActions.cs 文件。

  2. 将以下 GetTotal 以黄色突出显示的方法添加到 ShoppingCart 类,以便类如下所示:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using WingtipToys.Models;
    
    namespace WingtipToys.Logic
    {
      public class ShoppingCartActions : IDisposable
      {
        public string ShoppingCartId { get; set; }
    
        private ProductContext _db = new ProductContext();
    
        public const string CartSessionKey = "CartId";
    
        public void AddToCart(int id)
        {
          // Retrieve the product from the database.           
          ShoppingCartId = GetCartId();
    
          var cartItem = _db.ShoppingCartItems.SingleOrDefault(
              c => c.CartId == ShoppingCartId
              && c.ProductId == id);
          if (cartItem == null)
          {
            // Create a new cart item if no cart item exists.                 
            cartItem = new CartItem
            {
              ItemId = Guid.NewGuid().ToString(),
              ProductId = id,
              CartId = ShoppingCartId,
              Product = _db.Products.SingleOrDefault(
               p => p.ProductID == id),
              Quantity = 1,
              DateCreated = DateTime.Now
            };
    
            _db.ShoppingCartItems.Add(cartItem);
          }
          else
          {
            // If the item does exist in the cart,                  
            // then add one to the quantity.                 
            cartItem.Quantity++;
          }
          _db.SaveChanges();
        }
    
        public void Dispose()
        {
          if (_db != null)
          {
            _db.Dispose();
            _db = null;
          }
        }
    
        public string GetCartId()
        {
          if (HttpContext.Current.Session[CartSessionKey] == null)
          {
            if (!string.IsNullOrWhiteSpace(HttpContext.Current.User.Identity.Name))
            {
              HttpContext.Current.Session[CartSessionKey] = HttpContext.Current.User.Identity.Name;
            }
            else
            {
              // Generate a new random GUID using System.Guid class.     
              Guid tempCartId = Guid.NewGuid();
              HttpContext.Current.Session[CartSessionKey] = tempCartId.ToString();
            }
          }
          return HttpContext.Current.Session[CartSessionKey].ToString();
        }
    
        public List<CartItem> GetCartItems()
        {
          ShoppingCartId = GetCartId();
    
          return _db.ShoppingCartItems.Where(
              c => c.CartId == ShoppingCartId).ToList();
        }
    
        public decimal GetTotal()
        {
          ShoppingCartId = GetCartId();
          // Multiply product price by quantity of that product to get        
          // the current price for each of those products in the cart.  
          // Sum all product price totals to get the cart total.   
          decimal? total = decimal.Zero;
          total = (decimal?)(from cartItems in _db.ShoppingCartItems
                             where cartItems.CartId == ShoppingCartId
                             select (int?)cartItems.Quantity *
                             cartItems.Product.UnitPrice).Sum();
          return total ?? decimal.Zero;
        }
      }
    }
    

首先, GetTotal 方法获取用户的购物车 ID。 然后, 方法通过将产品价格乘以购物车中列出的每个产品的产品数量来获取购物车总数。

注意

上述代码使用可以为 null 的类型“int?”。 可以为 Null 的类型可以表示基础类型的所有值,也可以表示为 null 值。 有关详细信息,请参阅 使用可为空类型

修改购物车显示

接下来,将修改 ShoppingCart.aspx 页的代码以调用 GetTotal 方法,并在页面加载时在 ShoppingCart.aspx 页上显示该总计。

  1. “解决方案资源管理器”中,右键单击“ShoppingCart.aspx”页,然后选择“查看代码”。

  2. ShoppingCart.aspx.cs 文件中,通过添加以下以黄色突出显示的代码来更新 Page_Load 处理程序:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using WingtipToys.Models;
    using WingtipToys.Logic;
    
    namespace WingtipToys
    {
      public partial class ShoppingCart : System.Web.UI.Page
      {
        protected void Page_Load(object sender, EventArgs e)
        {
          using (ShoppingCartActions usersShoppingCart = new ShoppingCartActions())
          {
            decimal cartTotal = 0;
            cartTotal = usersShoppingCart.GetTotal();
            if (cartTotal > 0)
            {
              // Display Total.
              lblTotal.Text = String.Format("{0:c}", cartTotal);
            }
            else
            {
              LabelTotalText.Text = "";
              lblTotal.Text = "";
              ShoppingCartTitle.InnerText = "Shopping Cart is Empty";
            }
          }
        }
    
        public List<CartItem> GetShoppingCartItems()
        {
          ShoppingCartActions actions = new ShoppingCartActions();
          return actions.GetCartItems();
        }
      }
    }
    

加载 ShoppingCart.aspx 页面时,它将加载购物车对象,然后通过调用 GetTotal 类的 ShoppingCart 方法检索购物车总计。 如果购物车为空,则会显示一条显示该效果的消息。

测试购物车总计

立即运行应用程序,了解如何不仅将产品添加到购物车,还可以查看购物车总数。

  1. F5 运行该应用程序。
    浏览器将打开并显示 Default.aspx 页面。

  2. 从类别导航菜单中选择“ 汽车 ”。

  3. 单击第一个产品旁边的 “添加到购物车 ”链接。
    ShoppingCart.aspx 页显示订单总计。

    购物车 - 购物车总计

  4. (添加一些其他产品,例如,将飞机) 到购物车。

  5. 将显示 ShoppingCart.aspx 页面,其中包含已添加的所有产品的更新总计。

    购物车 - 多个产品

  6. 通过关闭浏览器窗口停止正在运行的应用。

将“更新”和“结帐”按钮添加到购物车

若要允许用户修改购物车,需要向购物车页面添加 “更新” 按钮和“ 结帐 ”按钮。 在本教程系列的后面部分之前,不会使用 “签出 ”按钮。

  1. 解决方案资源管理器中,打开 Web 应用程序项目根目录中的 ShoppingCart.aspx 页。

  2. 若要将 “更新 ”按钮和“ 结帐 ”按钮添加到 ShoppingCart.aspx 页,请将以黄色突出显示的标记添加到现有标记,如以下代码所示:

    <%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="ShoppingCart.aspx.cs" Inherits="WingtipToys.ShoppingCart" %>
    <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
        <div id="ShoppingCartTitle" runat="server" class="ContentHead"><h1>Shopping Cart</h1></div>
        <asp:GridView ID="CartList" runat="server" AutoGenerateColumns="False" ShowFooter="True" GridLines="Vertical" CellPadding="4"
            ItemType="WingtipToys.Models.CartItem" SelectMethod="GetShoppingCartItems"  
            CssClass="table table-striped table-bordered" >   
            <Columns>
            <asp:BoundField DataField="ProductID" HeaderText="ID" SortExpression="ProductID" />        
            <asp:BoundField DataField="Product.ProductName" HeaderText="Name" />        
            <asp:BoundField DataField="Product.UnitPrice" HeaderText="Price (each)" DataFormatString="{0:c}"/>     
            <asp:TemplateField   HeaderText="Quantity">            
                    <ItemTemplate>
                        <asp:TextBox ID="PurchaseQuantity" Width="40" runat="server" Text="<%#: Item.Quantity %>"></asp:TextBox> 
                    </ItemTemplate>        
            </asp:TemplateField>    
            <asp:TemplateField HeaderText="Item Total">            
                    <ItemTemplate>
                        <%#: String.Format("{0:c}", ((Convert.ToDouble(Item.Quantity)) *  Convert.ToDouble(Item.Product.UnitPrice)))%>
                    </ItemTemplate>        
            </asp:TemplateField> 
            <asp:TemplateField HeaderText="Remove Item">            
                    <ItemTemplate>
                        <asp:CheckBox id="Remove" runat="server"></asp:CheckBox>
                    </ItemTemplate>        
            </asp:TemplateField>    
            </Columns>    
        </asp:GridView>
        <div>
            <p></p>
            <strong>
                <asp:Label ID="LabelTotalText" runat="server" Text="Order Total: "></asp:Label>
                <asp:Label ID="lblTotal" runat="server" EnableViewState="false"></asp:Label>
            </strong> 
        </div>
      <br />
        <table> 
        <tr>
          <td>
            <asp:Button ID="UpdateBtn" runat="server" Text="Update" OnClick="UpdateBtn_Click" />
          </td>
          <td>
            <!--Checkout Placeholder -->
          </td>
        </tr>
        </table>
    </asp:Content>
    

当用户单击“ 更新 ”按钮时, UpdateBtn_Click 将调用事件处理程序。 此事件处理程序将调用将在下一步中添加的代码。

接下来,可以更新 ShoppingCart.aspx.cs 文件中包含的代码,以循环访问购物车项并调用 RemoveItemUpdateItem 方法。

  1. 解决方案资源管理器 中,打开 Web 应用程序项目的根目录中的 ShoppingCart.aspx.cs 文件。

  2. 将以下以黄色突出显示的代码部分添加到 ShoppingCart.aspx.cs 文件:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using WingtipToys.Models;
    using WingtipToys.Logic;
    using System.Collections.Specialized;
    using System.Collections;
    using System.Web.ModelBinding;
    
    namespace WingtipToys
    {
      public partial class ShoppingCart : System.Web.UI.Page
      {
        protected void Page_Load(object sender, EventArgs e)
        {
          using (ShoppingCartActions usersShoppingCart = new ShoppingCartActions())
          {
            decimal cartTotal = 0;
            cartTotal = usersShoppingCart.GetTotal();
            if (cartTotal > 0)
            {
              // Display Total.
              lblTotal.Text = String.Format("{0:c}", cartTotal);
            }
            else
            {
              LabelTotalText.Text = "";
              lblTotal.Text = "";
              ShoppingCartTitle.InnerText = "Shopping Cart is Empty";
              UpdateBtn.Visible = false;
            }
          }
        }
    
        public List<CartItem> GetShoppingCartItems()
        {
          ShoppingCartActions actions = new ShoppingCartActions();
          return actions.GetCartItems();
        }
    
        public List<CartItem> UpdateCartItems()
        {
          using (ShoppingCartActions usersShoppingCart = new ShoppingCartActions())
          {
            String cartId = usersShoppingCart.GetCartId();
    
            ShoppingCartActions.ShoppingCartUpdates[] cartUpdates = new ShoppingCartActions.ShoppingCartUpdates[CartList.Rows.Count];
            for (int i = 0; i < CartList.Rows.Count; i++)
            {
              IOrderedDictionary rowValues = new OrderedDictionary();
              rowValues = GetValues(CartList.Rows[i]);
              cartUpdates[i].ProductId = Convert.ToInt32(rowValues["ProductID"]);
    
              CheckBox cbRemove = new CheckBox();
              cbRemove = (CheckBox)CartList.Rows[i].FindControl("Remove");
              cartUpdates[i].RemoveItem = cbRemove.Checked;
    
              TextBox quantityTextBox = new TextBox();
              quantityTextBox = (TextBox)CartList.Rows[i].FindControl("PurchaseQuantity");
              cartUpdates[i].PurchaseQuantity = Convert.ToInt16(quantityTextBox.Text.ToString());
            }
            usersShoppingCart.UpdateShoppingCartDatabase(cartId, cartUpdates);
            CartList.DataBind();
            lblTotal.Text = String.Format("{0:c}", usersShoppingCart.GetTotal());
            return usersShoppingCart.GetCartItems();
          }
        }
    
        public static IOrderedDictionary GetValues(GridViewRow row)
        {
          IOrderedDictionary values = new OrderedDictionary();
          foreach (DataControlFieldCell cell in row.Cells)
          {
            if (cell.Visible)
            {
              // Extract values from the cell.
              cell.ContainingField.ExtractValuesFromCell(values, cell, row.RowState, true);
            }
          }
          return values;
        }
    
        protected void UpdateBtn_Click(object sender, EventArgs e)
        {
          UpdateCartItems();
        }
      }
    }
    

当用户单击 ShoppingCart.aspx 页上的“更新”按钮时,将调用 UpdateCartItems 方法。 UpdateCartItems 方法获取购物车中每个项目的更新值。 然后,UpdateCartItems 方法调用 UpdateShoppingCartDatabase 方法, (下一步) 添加和说明,以在购物车中添加或删除项。 更新数据库以反映对购物车的更新后,通过调用 DataBindGridView 的 方法,在购物车页上更新 GridView 控件。 此外,购物车页面上的总订单金额也会更新,以反映更新的项列表。

更新和删除购物车项

ShoppingCart.aspx 页上,可以看到已添加用于更新项数量和删除项的控件。 现在,添加使这些控件正常工作的代码。

  1. 解决方案资源管理器 中,打开 Logic 文件夹中的 ShoppingCartActions.cs 文件。

  2. 将以下以黄色突出显示的代码添加到 ShoppingCartActions.cs 类文件:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using WingtipToys.Models;
    
    namespace WingtipToys.Logic
    {
      public class ShoppingCartActions : IDisposable
      {
        public string ShoppingCartId { get; set; }
    
        private ProductContext _db = new ProductContext();
    
        public const string CartSessionKey = "CartId";
    
        public void AddToCart(int id)
        {
          // Retrieve the product from the database.           
          ShoppingCartId = GetCartId();
    
          var cartItem = _db.ShoppingCartItems.SingleOrDefault(
              c => c.CartId == ShoppingCartId
              && c.ProductId == id);
          if (cartItem == null)
          {
            // Create a new cart item if no cart item exists.                 
            cartItem = new CartItem
            {
              ItemId = Guid.NewGuid().ToString(),
              ProductId = id,
              CartId = ShoppingCartId,
              Product = _db.Products.SingleOrDefault(
               p => p.ProductID == id),
              Quantity = 1,
              DateCreated = DateTime.Now
            };
    
            _db.ShoppingCartItems.Add(cartItem);
          }
          else
          {
            // If the item does exist in the cart,                  
            // then add one to the quantity.                 
            cartItem.Quantity++;
          }
          _db.SaveChanges();
        }
    
        public void Dispose()
        {
          if (_db != null)
          {
            _db.Dispose();
            _db = null;
          }
        }
    
        public string GetCartId()
        {
          if (HttpContext.Current.Session[CartSessionKey] == null)
          {
            if (!string.IsNullOrWhiteSpace(HttpContext.Current.User.Identity.Name))
            {
              HttpContext.Current.Session[CartSessionKey] = HttpContext.Current.User.Identity.Name;
            }
            else
            {
              // Generate a new random GUID using System.Guid class.     
              Guid tempCartId = Guid.NewGuid();
              HttpContext.Current.Session[CartSessionKey] = tempCartId.ToString();
            }
          }
          return HttpContext.Current.Session[CartSessionKey].ToString();
        }
    
        public List<CartItem> GetCartItems()
        {
          ShoppingCartId = GetCartId();
    
          return _db.ShoppingCartItems.Where(
              c => c.CartId == ShoppingCartId).ToList();
        }
    
        public decimal GetTotal()
        {
          ShoppingCartId = GetCartId();
          // Multiply product price by quantity of that product to get        
          // the current price for each of those products in the cart.  
          // Sum all product price totals to get the cart total.   
          decimal? total = decimal.Zero;
          total = (decimal?)(from cartItems in _db.ShoppingCartItems
                             where cartItems.CartId == ShoppingCartId
                             select (int?)cartItems.Quantity *
                             cartItems.Product.UnitPrice).Sum();
          return total ?? decimal.Zero;
        }
    
        public ShoppingCartActions GetCart(HttpContext context)
        {
          using (var cart = new ShoppingCartActions())
          {
            cart.ShoppingCartId = cart.GetCartId();
            return cart;
          }
        }
    
        public void UpdateShoppingCartDatabase(String cartId, ShoppingCartUpdates[] CartItemUpdates)
        {
          using (var db = new WingtipToys.Models.ProductContext())
          {
            try
            {
              int CartItemCount = CartItemUpdates.Count();
              List<CartItem> myCart = GetCartItems();
              foreach (var cartItem in myCart)
              {
                // Iterate through all rows within shopping cart list
                for (int i = 0; i < CartItemCount; i++)
                {
                  if (cartItem.Product.ProductID == CartItemUpdates[i].ProductId)
                  {
                    if (CartItemUpdates[i].PurchaseQuantity < 1 || CartItemUpdates[i].RemoveItem == true)
                    {
                      RemoveItem(cartId, cartItem.ProductId);
                    }
                    else
                    {
                      UpdateItem(cartId, cartItem.ProductId, CartItemUpdates[i].PurchaseQuantity);
                    }
                  }
                }
              }
            }
            catch (Exception exp)
            {
              throw new Exception("ERROR: Unable to Update Cart Database - " + exp.Message.ToString(), exp);
            }
          }
        }
    
        public void RemoveItem(string removeCartID, int removeProductID)
        {
          using (var _db = new WingtipToys.Models.ProductContext())
          {
            try
            {
              var myItem = (from c in _db.ShoppingCartItems where c.CartId == removeCartID && c.Product.ProductID == removeProductID select c).FirstOrDefault();
              if (myItem != null)
              {
                // Remove Item.
                _db.ShoppingCartItems.Remove(myItem);
                _db.SaveChanges();
              }
            }
            catch (Exception exp)
            {
              throw new Exception("ERROR: Unable to Remove Cart Item - " + exp.Message.ToString(), exp);
            }
          }
        }
    
        public void UpdateItem(string updateCartID, int updateProductID, int quantity)
        {
          using (var _db = new WingtipToys.Models.ProductContext())
          {
            try
            {
              var myItem = (from c in _db.ShoppingCartItems where c.CartId == updateCartID && c.Product.ProductID == updateProductID select c).FirstOrDefault();
              if (myItem != null)
              {
                myItem.Quantity = quantity;
                _db.SaveChanges();
              }
            }
            catch (Exception exp)
            {
              throw new Exception("ERROR: Unable to Update Cart Item - " + exp.Message.ToString(), exp);
            }
          }
        }
    
        public void EmptyCart()
        {
          ShoppingCartId = GetCartId();
          var cartItems = _db.ShoppingCartItems.Where(
              c => c.CartId == ShoppingCartId);
          foreach (var cartItem in cartItems)
          {
            _db.ShoppingCartItems.Remove(cartItem);
          }
          // Save changes.             
          _db.SaveChanges();
        }
    
        public int GetCount()
        {
          ShoppingCartId = GetCartId();
    
          // Get the count of each item in the cart and sum them up          
          int? count = (from cartItems in _db.ShoppingCartItems
                        where cartItems.CartId == ShoppingCartId
                        select (int?)cartItems.Quantity).Sum();
          // Return 0 if all entries are null         
          return count ?? 0;
        }
    
        public struct ShoppingCartUpdates
        {
          public int ProductId;
          public int PurchaseQuantity;
          public bool RemoveItem;
        }
      }
    }
    

方法 UpdateShoppingCartDatabase (从 UpdateCartItemsShoppingCart.aspx.cs 页上的 方法调用)包含更新或删除购物车中的项的逻辑。 方法 UpdateShoppingCartDatabase 循环访问购物车列表中的所有行。 如果购物车项已标记为要删除,或者数量小于 1, RemoveItem 则调用 方法。 否则,调用 方法时 UpdateItem ,将检查购物车项的更新。 删除或更新购物车项后,将保存数据库更改。

结构 ShoppingCartUpdates 用于保存所有购物车项。 方法 UpdateShoppingCartDatabase 使用 ShoppingCartUpdates 结构来确定是否需要更新或删除任何项。

在下一教程中,你将使用 EmptyCart 方法在购买产品后清除购物车。 但目前,你将使用 GetCount 刚刚添加到 ShoppingCartActions.cs 文件的方法来确定购物车中有多少项。

添加购物车计数器

若要允许用户查看购物车中的项目总数,请将计数器添加到 Site.Master 页。 此计数器还将充当购物车的链接。

  1. “解决方案资源管理器”中,打开“Site.Master”页。

  2. 通过将购物车计数器链接(如黄色所示)添加到导航部分来修改标记,使其如下所示:

    <ul class="nav navbar-nav">
          <li><a runat="server" href="~/">Home</a></li>
          <li><a runat="server" href="~/About">About</a></li>
          <li><a runat="server" href="~/Contact">Contact</a></li>
          <li><a runat="server" href="~/ProductList">Products</a></li>
          <li><a runat="server" href="~/ShoppingCart" ID="cartCount">&nbsp;</a></li>
      </ul>
    
  3. 接下来,通过添加以黄色突出显示的代码来更新 Site.Master.cs 文件的代码隐藏,如下所示:

    using System;
    using System.Collections.Generic;
    using System.Security.Claims;
    using System.Security.Principal;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Linq;
    using WingtipToys.Models;
    using WingtipToys.Logic;
    
    namespace WingtipToys
    {
        public partial class SiteMaster : MasterPage
        {
            private const string AntiXsrfTokenKey = "__AntiXsrfToken";
            private const string AntiXsrfUserNameKey = "__AntiXsrfUserName";
            private string _antiXsrfTokenValue;
    
            protected void Page_Init(object sender, EventArgs e)
            {
                // The code below helps to protect against XSRF attacks
                var requestCookie = Request.Cookies[AntiXsrfTokenKey];
                Guid requestCookieGuidValue;
                if (requestCookie != null && Guid.TryParse(requestCookie.Value, out requestCookieGuidValue))
                {
                    // Use the Anti-XSRF token from the cookie
                    _antiXsrfTokenValue = requestCookie.Value;
                    Page.ViewStateUserKey = _antiXsrfTokenValue;
                }
                else
                {
                    // Generate a new Anti-XSRF token and save to the cookie
                    _antiXsrfTokenValue = Guid.NewGuid().ToString("N");
                    Page.ViewStateUserKey = _antiXsrfTokenValue;
    
                    var responseCookie = new HttpCookie(AntiXsrfTokenKey)
                    {
                        HttpOnly = true,
                        Value = _antiXsrfTokenValue
                    };
                    if (FormsAuthentication.RequireSSL && Request.IsSecureConnection)
                    {
                        responseCookie.Secure = true;
                    }
                    Response.Cookies.Set(responseCookie);
                }
    
                Page.PreLoad += master_Page_PreLoad;
            }
    
            protected void master_Page_PreLoad(object sender, EventArgs e)
            {
                if (!IsPostBack)
                {
                    // Set Anti-XSRF token
                    ViewState[AntiXsrfTokenKey] = Page.ViewStateUserKey;
                    ViewState[AntiXsrfUserNameKey] = Context.User.Identity.Name ?? String.Empty;
                }
                else
                {
                    // Validate the Anti-XSRF token
                    if ((string)ViewState[AntiXsrfTokenKey] != _antiXsrfTokenValue
                        || (string)ViewState[AntiXsrfUserNameKey] != (Context.User.Identity.Name ?? String.Empty))
                    {
                        throw new InvalidOperationException("Validation of Anti-XSRF token failed.");
                    }
                }
            }
    
            protected void Page_Load(object sender, EventArgs e)
            {
    
            }
    
            protected void Page_PreRender(object sender, EventArgs e)
            {
              using (ShoppingCartActions usersShoppingCart = new ShoppingCartActions())
              {
                string cartStr = string.Format("Cart ({0})", usersShoppingCart.GetCount());
                cartCount.InnerText = cartStr;
              }
            }
    
            public IQueryable<Category> GetCategories()
            {
              var _db = new WingtipToys.Models.ProductContext();
              IQueryable<Category> query = _db.Categories;
              return query;
            }
    
            protected void Unnamed_LoggingOut(object sender, LoginCancelEventArgs e)
            {
                Context.GetOwinContext().Authentication.SignOut();
            }
        }
    }
    

在页面呈现为 HTML 之前,会 Page_PreRender 引发 事件。 在 Page_PreRender 处理程序中,购物车的总计数通过调用 GetCount 方法确定。 返回的值将添加到 cartCountSite.Master 页的标记中包含的范围。 使用 <span> 标记可以正确呈现内部元素。 显示网站的任何页面时,将显示购物车总计。 用户还可以单击购物车总计以显示购物车。

测试已完成的购物车

现在可以运行应用程序,了解如何在购物车中添加、删除和更新项。 购物车总计将反映购物车中所有项的总成本。

  1. F5 运行该应用程序。
    浏览器将打开并显示 Default.aspx 页。

  2. 从类别导航菜单中选择“ 汽车 ”。

  3. 单击第一个产品旁边的 “添加到购物车 ”链接。
    ShoppingCart.aspx 页显示订单总计。

  4. 从类别导航菜单中选择“ 平面 ”。

  5. 单击第一个产品旁边的 “添加到购物车 ”链接。

  6. 将购物车中第一项的数量设置为 3,然后选择第二项的“删除项检查”框。

  7. 单击“ 更新 ”按钮更新购物车页面并显示新订单总计。

    购物车 - 购物车更新

总结

在本教程中,你已为 Wingtip Toys Web Forms 示例应用程序创建了购物车。 在本教程中,你使用了 Entity Framework Code First、数据注释、强类型数据控件和模型绑定。

购物车支持添加、删除和更新用户选择购买的商品。 除了实现购物车功能外,你还了解了如何在 GridView 控件中显示购物车项并计算订单总计。

为了了解所述功能在实际业务应用程序中的工作原理,可以查看 nopCommerce - 基于 ASP.NET 开放源代码电子商务购物车的示例。 最初,它建立在Web Forms,多年来,它移动到MVC,现在 ASP.NET Core。

添加信息

ASP.NET 会话状态概述