剃刀网页电子商务 - 将购物车添加到面包店模板网站

ASP.NET网页面包店模板站点被设计为显示产品的网站的起点。它还为客户提供订单表格,以指定特定产品的数量并提供其送货地址。该网站不提供多种产品选择。如果您想要一个面包店网站的购物车,您需要自己创建它。本文介绍了有效购物车后面的设计考虑,并显示了如何构建一个。

第一个设计考虑因素是存储。任何购物车都需要在多页上使用。有一个 ASP.NET网页之间持久状态的选项数。表单字段和URL不是维护购物车内容的好地方,因为它们不持久地超出表单字段或URL的一系列页面。应用程序变量完全不合适,因为每个人都共享相同的数据 - 如果应用程序重新启动,则会丢失。同样,进程内会话变量不够强大。如果应用程序池回收,它们将会丢失,它可以在共享托管环境中定期进行。您真的不想在他们填满的东西之后向您的用户展示您的用户,只是因为应用程序池回收清除它。此外,以前的方法都没有持续超过一次访问的数据。您可以使用cookie存储用户选择的详细信息,除了它们在他们可以保持的信息量中受到限制。最强大的方法是使用数据库。

下一个考虑是何时识别用户。用户必须在实际购买之前提供身份,并为此,他们需要使用您的网站注册帐户。但是,它们并不一定需要这样做,以选择要购买的一些产品。大多数电子商务网站都认识到他们应该允许匿名用户填写购物车,而不会强调他们首先提供个人信息。本文将展示如何管理该方案。在未来的文章中,我将演示如何进一步迈出进程,并在结账时要求注册。

数据库设计

现有的面包店数据库非常简单。它包含一个表:产品。购物车也很简单。一个基本的人只需要两张表:购物车和卡特里姆斯:

[Cart]
CartId		int	IDENTITY Primary Key Not Null
UserId		int
DateCreated	datetime NOT NULL
CheckedOut	bit NOT NULL Default 0
[CartItems]
CartItemId	int	IDENTITY Primary Key Not Null
CartId		int
ProductId	int
Quantity	int
Price		money

The Cart table has a UserId value, although anonymous users will be allowed to create a cart. The UserId column will be used in a future article to associate a cart with a registered user. CartItems has a one-to-many relationship with the Cart table via the CartId column. It also has a one-to-many relationship with the Products table via the ProductId column. You might wonder why Price has been included in the CartItems table when it already appears in the Products table. You may want to change the price of products from time to time in the Products table, but you do not want those changes to affect the price already paid for historic orders. Therefore you keep a record of the price paid separate to the "marketing" price in the products table. If you offer promotional discounts from time to time, you may want to add another column to the CartItems table to record the one that applied to a particular product.

如果要将这些表快速添加到面包店模板站点,请转到WebMatrix中的数据库工作区,确保您在主页选项卡中,然后单击“新建查询”。复制并粘贴以下第一个创建表格语句,然后单击“执行”。然后重复第二个。

CREATE TABLE Cart (
    CartId int IDENTITY Primary Key NOT NULL,
    UserId int,
    DateCreated datetime NOT NULL,
    CheckedOut bit NOT NULL Default 0
)
CREATE TABLE CartItems (
    CartItemId int IDENTITY Primary Key NOT NULL,
    CartId int NOT NULL,
    ProductId int NOT NULL,
    Quantity int NOT NULL,
    Price money NOT NULL
)

购物车

代码

从面包店模板创建了一个站点,并将更改变为上面概述的数据库,是时候开始编码了。我只对网站附带的Default.chtml文件进行了一个更改,这是从每个产品的标价中删除硬编码的美元符号,并更改要使用的格式字符串"c" instead of "f"因此,将从服务器中拾取区域货币:

@string.Format("{0:c}", p.Price)

大约有3个地方在哪里 价格 或者 特色 似乎需要这种变化。我喜欢在我的样品中看到英镑的标志。

Order.cshtml页面重新提高了很多更改,尤其是下面如下所示的HTML:

<ol id="orderProcess">
    <li><span class="step-number">1</span><a href="~/">Choose Item</a></li>
    <li class="current"><span class="step-number">2</span>Place Order</li>
    <li><span class="step-number">3</span><a href="~/ReviewCart">Review Cart</a></li>
</ol>

<h1>Place Your Order: @product.Name</h1>
<form action="" method="post">
    <fieldset class="no-legend">
        <legend>Place Your Order</legend>
        <img class="product-image order-image" src="~/Images/Products/Thumbnails/@product.ImageName" alt="Image of @product.Name"/>
        <ul class="orderPageList" data-role="listview">
            <li>
                <div>
                    <p class="description">@product.Description</p>
                </div>                
            </li>
            <li class="quantity">
                <div class="fieldcontainer" data-role="fieldcontain">
                    <label for="orderQty">Quantity</label>
                    <input type="text" id="orderQty" name="orderQty" value="@(quantity == 0 ? 1 : quantity)"/>
                    x
                    <span id="orderPrice">@string.Format("{0:f}", product.Price)</span>
                    =
                    <span id="orderTotal">@string.Format("{0:f}", quantity == 0 ? product.Price : quantity * product.Price)</span>
                </div>
            </li>
        </ul>
        <p class="actions">
            <input type="hidden" name="productId" value="@product.Id" />
            <input type="hidden" name="price" value="@product.Price" />
            @if(!IsPost){
                <input type="submit" value="Place Order" data-role="none" data-inline="true"/>
            }
        </p>
        <div id="basket">
            @if(totalItems > 0){
                <text>Your cart contains <strong>@完全是</strong> items</text>
            }
        </div>
    </fieldset>
</form>

导航到文件的顶部已修改,以便第三个链接现在指向一个名为ViewCart.chtml而不是OrderSuccess.chtml的页面。已删除电子邮件地址和送货地址字段,现在生成数量的值,以便在提交表单时持续存在。这 合计订单 也是由服务器端代码最初生成的,以便跨表单提交持续存在,尽管在选择数量时更新的JavaScript留在文件中(仅在此处包含)。地点订单按钮已包装在条件块中,以便在尚未提交源于off时,它只会出现。最后,添加DIV用于显示篮子中的当前项目数 完全是 变量大于0。

工作流程如下:当用户第一次选择产品并提交订单时,为该用户创建一个推车并输入购物车表。然后,订单的详细信息将作为Cartitem表中的新条目提交。然后,推车通过持久的cookie与该用户相关联,持久的cookie写入推车的ID。 Cookie将在未来6个月到期。只要用户不会删除他们的cookie,该网站就可以识别用户之前已在此处并具有购物车。这是文件顶部的代码块,其全部内容:

@{
    Page.Title = "Place Your Order";

    var db = Database.Open("bakery");
    var productId = UrlData[0].AsInt();
    var 价格 = Request["price"].AsDecimal();
    var quantity = Request["orderQty"].AsInt();
    var commandText = string.Empty;
    var 大车Id = 0;
    var 完全是 = 0;    
    
    commandText = "SELECT * FROM PRODUCTS WHERE ID = @0";
    var product = db.QuerySingle(commandText, productId);
    if (product == null) {
        Response.Redirect("~/");
    }

    if(Request.Cookies["cart"] != null){
        cartId = Request.Cookies["cart"].Value.AsInt();
        commandText = "SELECT SUM(Quantity) AS TotalItems FROM CartItems WHERE CartId = @0";
        object result = db.QueryValue(commandText, cartId);
        totalItems = result == DBNull.Value ? 0 : Convert.ToInt32(result);
    }

    if(IsPost && quantity > 0){
        if(Request.Cookies["cart"] == null){
            commandText = "INSERT INTO Cart (DateCreated) VALUES (GetDate())";
            db.Execute(commandText);
            cartId = (int)db.GetLastInsertId();
        }
        commandText = "SELECT Quantity FROM CartItems WHERE CartId = @0 AND ProductId = @1";
        var reduction = db.QueryValue(commandText, cartId, productId);
        if(reduction != null){
            totalItems -= reduction;
        }
        commandText = "DELETE FROM CartItems WHERE CartId = @0 AND ProductId = @1";
        db.Execute(commandText, cartId, productId);
        commandText = "INSERT INTO CartItems (CartId, ProductId, Quantity, Price) VALUES (@0, @1, @2, @3)";
        db.Execute(commandText, cartId, productId, quantity, price);
        totalItems += quantity;
        Response.Cookies["cart"].Value = cartId.ToString();
        Response.Cookies["cart"].Expires = DateTime.Now.AddMonths(6);
    }
}

代码(忽略标题设置行)分为4个块。第一个块由声明和初始化的各种变量组成。其中大多数与原始面包店模板网站相同。添加是Cart ID的整数和购物车中的总项目,以及用于保持SQL命令的字符串。第二个块试图检索具有在URL中传递的ID的产品的产品详细信息。如果未找到匹配的产品,则用户将重定向到主页。此代码从原件中或多或少不变。

代码的第三部分检查cookie是否存在名称"cart"。如果是,用户已经创建了一个阶段的购物车。购物车的ID是从Cookie获取的,这用于查询数据库以获取购物车中的项目总数。

如果用户已提交表单,则执行最后一块代码块,并且指定了大于0的数量,如果不存在推车(通过检查cookie的存在),则会创建一个。这是通过设置的实现 创建日期 SQL的字段值 getDate() 函数值,它相当于.NET的 DateTime.now.。然后是 database.getlastinsertid. 如果新创建的记录,则使用方法获取该值。代表购物车的ID。

假设为特定产品提交的任何订单是该产品的第一个订单或对该产品的现有订单的修改 - 这意味着所选产品的现有条目应替换为新的提交。对数据库执行查询以确定产品的任何现有订单的数量。这 database.queryValue. 使用方法,其期望返回标量值(因为查询只需要从一个字段中的值)。如果没有记录匹配标准, QueryValue. 返回null。否则,重新调整现有行的值,需要从当前的总值值中扣除。然后从CartItems表中删除任何匹配行。在此之后,将添加一个新的行,其中包含当前订单提交的详细信息,以及 完全是 相应调整变量以提供修订的项目计数。最后,用购物车的ID写入浏览器并将Cookie写入浏览器并在6个月内设置为过期。

审查推车

此示例中呈现的订单过程的最后步骤是能够查看购物车的内容并删除项目。

购物车

这由ReviewCart.chtml提供。以下是页面的HTML和脚本部分:

<ol id="orderProcess">
    <li><span class="step-number">1</span><a href="~/">Choose Item</a></li>
    <li><span class="step-number">2</span><a href="~/Order/1">Place Order</a></li>
    <li class="current"><span class="step-number">3</span>Review Cart</li>
</ol>
<h1>Review Your Cart</h1>
<p>&nbsp;</p>
<div id="grid">
@grid.GetHtml(
    columns: grid.Columns(
    grid.Column("Name", "Product", format: @<a href="~/Order/@item.ID">@item.Name</a>),
        grid.Column("Quantity"),
        grid.Column("Price", "Unit Price", format: @<text>@item.Price.ToString("c")</text>),
        grid.Column("Price", "Total Price", format: @<text>@item.Total.ToString("c")</text>),
        grid.Column("", format: @<form method="post">
                                <input type="hidden" value="@item.CartItemId" name="cartItem" />
                                <input type="submit" value="Remove" />
                                </form>)
    )
)
</div>
<p>&nbsp;</p>

@section scripts{
<script>
    $(function () {
        var html = '<tfoot><tr><td colspan="3"><strong>Total</strong></td><td>';
        html += '<strong>@大车Total.ToString("c")</strong>'
        html += '</td><td colspan="2">&nbsp;</td></tr></tfoot>';
        $('table').append(html);
    });
</script>
}

推车本身呈现在WebGrid中。电网通过jQuery添加了一个页脚,它用于显示推车的总值。这是关于将页脚添加到WebGrids的最简单方法。网格中的每一行都有一个最后一列中的删除按钮。如果查看HTE最后一列的格式参数,则可以看到为每个产品提供单个表单。产品的ID存储在隐藏字段中,以便用户不可见,但如果单击关联的按钮,则将传输到服务器。让我们看看服务器上的其他内容:

@{
    Page.Title = "Review Your Cart";
    
    if(Request.Cookies["cart"] == null){
        Response.Redirect("~/");
    }

    var db = Database.Open("Bakery");
    var 大车Id = Request.Cookies["cart"].Value.AsInt();
    var commandText = string.Empty;
    
    if(IsPost){
        commandText = "DELETE FROM CartItems WHERE CartItemId = @0";
        db.Execute(commandText, Request["cartItem"]);
    }

    commandText = @"SELECT p.ID, p.Name, c.Quantity, c.Price,  c.Quantity * c.Price AS Total, c.CartItemId
                        FROM CartItems c INNER JOIN Products p 
                        ON c.ProductId = p.Id WHERE c.CartId = @0";
    var 大车Items = db.Query(commandText, cartId);
    var 大车Total = cartItems.Sum(t => (decimal)t.Total);
    var grid = new WebGrid(cartItems, canPage: false, canSort: false);
}

嗯,首先,检查是否存在CART(cookie),如果没有,则用户被重定向到主页。如果访问者通过该特定测试,则将执行其余代码。声明某些变量后,将检查是否已提交任何表单以删除来自篮子的行。如果有的话,删除了CartItems表中的相应条目。在执行查询之前完成以获取购物车的显示器以获取Cart的内容。请注意 大车Total 使用枚举申请程序()扩展方法生成变量(介于jQuery函数中使用的jQuery函数中的jQuery函数)。获得此值的另一种方法是针对数据库执行单独的SQL查询,但在检索到的数据上使用LINQ查询再次访问数据是更有效的。

概括

电子商务购物车似乎为相对无意的开发人员持有很多神秘主义者。但是,它们非常简单。在此示例中所示的那个仅使用数据库帮助器的一些小型数据库表和一些初级级联的操作。它没有完成。需要有一些方法可以将购物车与用户的帐户相关联,我将在即将到来的文章中查看。此外,您可能希望添加一些维护例程,例如删除超过6个月以上的匿名购物车。

提供包含本文中的代码的示例站点 从github下载.