65.9K
CodeProject 正在变化。 阅读更多。
Home

为 ASP.NET MVC 音乐商店 3.0 的购物车添加数量更新功能

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (15投票s)

2012 年 2 月 27 日

CPOL

10分钟阅读

viewsIcon

109303

downloadIcon

3600

本文展示了如何通过添加数量更新功能和不显眼的数据验证,使 Microsoft ASP.NET MVC 音乐商店 3.0 示例应用程序的购物车工作流程更实用,从而增强其功能。

引言

音乐商店 3.0 示例应用程序是 ASP.NET MVC 3.0 的一个出色教程。然而,其购物车并没有遵循实用的工作流程,并且缺少一些重要的功能。例如,我们需要返回并选择相同的专辑项目并将其添加到购物车以将数量增加一。每次点击“从购物车中移除”链接都会将数量减少一,而不是移除选定的购物车专辑项目。我们需要重复相同的操作多次才能添加或移除数量大于一的特定专辑项目。为了纠正这些问题,我们需要一个在页面上编辑和刷新数量字段的功能,这是任何真实在线商店购物车的_基本_要求。

由于我找不到任何关于音乐商店示例应用程序购物车数量更新问题的文章或讨论,我尝试根据下面列出的功能要求进行工作。

  • 点击“从购物车中移除”链接将移除购物车中选定的专辑项目,无论数量是多少。
  • 使数量字段可编辑。
  • 添加“刷新数量”链接以执行专辑项目计数更新,包括数据库操作。当特定专辑项目的数量字段为空或为零时,该链接也应执行相同的项目移除操作。
  • 为输入字段实现自定义的客户端 jQuery 不显眼验证和服务器端错误处理。

运行应用程序时,购物车屏幕如下所示。

1.png

“从购物车中移除”链接的更改

我们只需要移除 ShoppingCart 类中 RemoveFromCart() 函数的几行代码,以及购物车视图中 JavaScript 部分的 $(".RemoveLink").click(function ())。要移除的行如下所示,带有注释符号和双破折号。结果,每次减少一个计数的_不必要_行为从代码中移除。

Models\ShoppingCart.cs

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

Views\Index.cshtml

$(".RemoveLink").click(function () {
    // Get the id from the link 
    var recordToDelete = $(this).attr("data-id");
    if (recordToDelete != '') {
        clearUpdateMessage();
        // 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 + ')');
            });
    }

当我们为数量提供输入值为零或空值的“刷新数量”链接时,“从购物车中移除”链接可能就不那么重要了,因为它们执行相同的操作。

为数量字段添加一个输入文本框

购物车视图与 ShoppingCartViewModel 相关联,后者包含数据模型中 Cart 对象的集合。由于我们需要 CartItems 列表中每个购物车项目的输入字段,因此结构类似于带有可编辑列的数据网格。我们还需要一个索引号来定义单个购物车项目。在购物车视图上,我们可以将 @foreach() 循环更改为 for() 循环,但我只是想尽可能保持原始代码。

  1. @foreach() 行上方添加一个局部变量
  2. @{int ix = 0;}
  3. @foreach 循环中,将保存 Count 数据的 <td> 标签替换为 HTML Helper TextBoxFor()。我们还需要调整文本框宽度(_site.css_ 中设置的输入文本元素的默认值 300px)并将数字字符向右对齐。
  4. 旧代码

    <td id="item-count-@item.RecordId"> 
       @item.Count
    </td>

    新代码

    <td>
    @Html.TextBoxFor(model => model.CartItems[ix].Count, 
          new { style = "width:30px; text-align:right;" 
        })
    </td>
  5. foreach() 循环的最后一行,紧接在闭合大括号之前,添加匹配当前索引号的代码。
  6. i++;

添加“刷新数量”链接

除了购物车项目 ID 值外,我们还需要获取输入文本框 ID 值并将其传递给 AJAX 调用。默认情况下,Razor 引擎会自动以“_generic-list-object-name_ _index-number_ _object-date-property_”的格式渲染元素 ID 列表。请注意,索引号后面有两个下划线字符。在我们的例子中,第一个输入元素的 ID 值应该是“CartItem_0__Count”,第二个是“CartItem_1__Count”,等等。元素“name”属性也有特定的格式,但我们在这里不需要它。如果您想自定义元素 ID 值,您需要使用 @Html.TextBox() 而不是 @Html.TextBoxFor()。但在这种情况下,您需要在使用 @Html.TextBox() 的第三个参数中指定自定义属性,以便手动渲染不显眼的验证 HTML

我们还需要为“刷新数量”链接定义一个自定义属性“txt-id”,用于传递输入元素 ID。在“从购物车中移除”<a> 标签的行之前添加 <a> 标签

<a href="#" class="RefreshQuantity" data-id="@item.RecordId" 
   txt-id="CartItems_@(ix)__Count">Refresh quantity</a>&nbsp;|&nbsp;

添加一个用于刷新数量的 jQuery AJAX 函数

我们已经知道从自定义属性传递的数据:date-id 和 txt-id。创建用于刷新数量更新的客户端 AJAX 函数很容易。只需复制 RemoveLink 的点击函数并进行一些修改。

$(".RefreshQuantity").click(function () {
    // Get the id from the link 
    var recordToUpdate = $(this).attr("data-id");
    var countToUpdate = $("#" + $(this).attr("txt-id")).val();
    if (recordToUpdate != '') {        
        // Perform the ajax post 
        $.post("/ShoppingCart/UpdateCartCount", { "id": recordToUpdate, "cartCount": countToUpdate },
            function (data) {
                // Successful requests get here 
                // Update the page elements                        
                if (data.ItemCount == 0) {
                    $('#row-' + data. DeleteId).fadeOut('slow');
                }
                $('#update-message').text(data.Message);
                $('#cart-total').text(data.CartTotal);
                $('#cart-status').text('Cart (' + data.CartCount + ')');                
            });
    }

我们声明一个变量 countToUpdate,它保存用户输入的数据,然后将其作为第二个 JSON 参数“cartCount”传递给 AJAX post。处理回调数据与“从购物车中移除”链接的处理方式相同。

添加刷新数量的服务器端函数

这次我们不更改任何模型中的数据属性。我们只是添加函数将购物车中的专辑项目保存到数据库,然后以 JSON 格式返回数据用于消息传递。

Controllers\ShoppingCartController.cs

[HttpPost]
public ActionResult UpdateCartCount(int id, int cartCount)
{
    // Get 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;

    // Update the cart count 
    int itemCount = cart.UpdateCartCount(id, cartCount);

    //Prepare messages
    string msg = "The quantity of " + Server.HtmlEncode(albumName) +
            " has been refreshed in your shopping cart.";
    if (itemCount == 0) msg = Server.HtmlEncode(albumName) +
            " has been removed from your shopping cart.";
    //
    // Display the confirmation message 
    var results = new ShoppingCartRemoveViewModel
    {
        Message = msg,
        CartTotal = cart.GetTotal(),
        CartCount = cart.GetCount(),
        ItemCount = itemCount,
        DeleteId = id
    }; 
    return Json(results);
}

Models\ShoppingCart.cs

public int UpdateCartCount(int id, int cartCount)
{
    // Get the cart 
    var cartItem = storeDB.Carts.Single(
        cart => cart.CartId == ShoppingCartId
        && cart.RecordId == id);

    int itemCount = 0;

    if (cartItem != null)
    {
        if (cartCount > 0)
        {
            cartItem.Count = cartCount;
            itemCount = cartItem.Count;
        }
        else
        {
            storeDB.Carts.Remove(cartItem);
        }
        // Save changes 
        storeDB.SaveChanges();
    }
    return itemCount;
}

我们的数量输入字段接受零值。如果从 cartCount 参数传入零值,更新过程将移除购物车项目,这与“从购物车中移除”链接的操作相同。如果您不喜欢这种行为并希望禁止输入零值,您可以更改范围验证设置,如下一节所述。

到目前为止一切顺利。运行应用程序并从主页或流派菜单添加多个专辑项目到购物车时,我们将在购物车页面上获得数量可编辑列表。然后我们可以更改购物车中任何专辑项目的数量、刷新或删除,正如所需的功能(请参阅之前显示的屏幕截图)。

添加不显眼验证和服务器错误处理

在可编辑数量字段投入使用之前,我们需要根据以下规则添加数据验证方法。

  • 输入值必须是数字。
  • 值范围必须在 0 到 100 之间。
  • 允许空值,但将其视为值 0。“字段_显示名称_是必需的”消息将不会渲染。
  • 错误消息应显示在列表或网格的顶部,而不是其内部。

运行应用程序时,屏幕如下所示。

2.png

3.png

MVC 3.0 带有 jQuery 不显眼验证的强大功能,用于数据输入,但很少有示例应用程序展示了对相同模型对象(实体)列表的实现。让我们在这里为相同输入字段列表执行不显眼验证。

首先,将 ComponentModelDataAnnotations 命名空间引用和验证属性添加到 Cart.cs。文件中的代码应如下所示

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc; 

namespace MvcMusicStore.Models
{
    public class Cart
    {
        [Key]
        public int RecordId { get; set; }
        public string CartId { get; set; }
        public int AlbumId { get; set; }
        
        [Required(AllowEmptyStrings = true, ErrorMessage = " ")]
        [Range(0, 100, ErrorMessage = "Quantity must be between 0 and 100")]
        [DisplayName("Quantity")]
        public int Count { get; set; }        
        
        public System.DateTime DateCreated { get; set; }
        public virtual Album Album { get; set; }
    }
}

我们将 Required 属性的 AllowEmptyStrings 设置为 true。然后我们将 ErrorMessage 设置为包含一个空格字符。如果将其设置为空字符串(""),我们将收到错误“ErrorMessageString 或 ErrorMessageResourceName 必须设置,但不能同时设置”。这主要是由于 System.ComponentModel.DataAnnotations.RequiredAttribute 类中设置默认 ErrorMessage 的错误导致的。当为属性设置自定义消息时,它不允许空字符串。但是,在消息中放置一个空格字符可以达到目的并禁用消息显示。

然后我们将默认显示名称“Count”覆盖为“Quantity”,以便任何使用“Count”名称的默认错误消息都会显示“Quantity”。

设置 Range 属性时,MVC 还会设置数字检查器并自动渲染“字段_display-name_必须是数字”消息。

接下来,让我们更改 Views\ShoppingCart\index.cshtml 中的一些内容,使验证生效。

  1. 音乐商店 3.0 示例应用程序已经使用站点根目录 web.config 文件中的设置启用了不显眼 JavaScript,但 jQuery 库引用是在每个涉及的视图中本地指定的,而不是在全局模板 Shared\_Layout.cshtml 中。将 jQuery 库引用添加到 Views\ShoppingCart\index.cshtml,以便视图的开头应包含这些行
  2. @model MvcMusicStore.ViewModels.ShoppingCartViewModel           
    @{ 
        ViewBag.Title = "Shopping Cart";   
    } 
    
    <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
  3. 添加代码以将页面内容包含在 HTML 表单中。如果没有 HTML 表单,验证将无法工作。
  4. @using (Html.BeginForm())
    {
        @*All lines from <h3> tag to end are inside here*@
    }
  5. 在代码行 <div id="update-message"></div> 上方放置一个包含显示验证错误消息的代码的 <div> 标签。这将根据购物车中当前的专辑项目列表渲染错误消息标签列表。我们希望将错误消息显示在网格结构的顶部,而不是列中的每一行。幸运的是,Razor 引擎和 HTML Helper 允许我们将验证错误消息放置在其他位置,而不仅仅是输入字段区域。
  6. <div>
         @for (int i = 0; i < Model.CartItems.Count; i++)
         { 
            <p>
                 @Html.ValidationMessageFor(model => model.CartItems[i].Count)
            </p> 
         }
    </div>
  7. 将代码添加到 jQuery $(".RefreshQuantity").click(function ()) 中,以便在输入值为空时将数量设置为零。
  8. $(".RefreshQuantity").click(function () {
        // Get the id from the link 
        var recordToUpdate = $(this).attr("data-id");
        
        //Set quantity number to 0 if input value is empty
        var countToUpdate = 0;
        if ($("#" + $(this).attr("txt-id")).val() !== '') {
            countToUpdate = $("#" + $(this).attr("txt-id")).val();
        }
        
       if (recordToUpdate != '') {
       - - - -

最后,我们需要手动处理服务器端验证错误。当输入值超出范围的最大值并显示客户端错误消息时,第二次点击“刷新数量”链接将向服务器发送无效数据并导致服务器错误“一个或多个实体验证失败”。我们需要捕获错误并向客户端返回自定义错误消息。这通过在 ShoppingCartController.csUpdateCartCount() 函数中添加和修改代码来完成。修改后的函数如下所示。

[HttpPost]
public ActionResult UpdateCartCount(int id, int cartCount)
{
    ShoppingCartRemoveViewModel results = null;
    try
    {
        // Get 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;

        // Update the cart count 
        int itemCount = cart.UpdateCartCount(id, cartCount);

        //Prepare messages
        string msg = "The quantity of " + Server.HtmlEncode(albumName) +
                " has been refreshed in your shopping cart.";
        if (itemCount == 0) msg = Server.HtmlEncode(albumName) +
                " has been removed from your shopping cart.";
        //
        // Display the confirmation message 
        results = new ShoppingCartRemoveViewModel
        {
            Message = msg,
            CartTotal = cart.GetTotal(),
            CartCount = cart.GetCount(),
            ItemCount = itemCount,
            DeleteId = id
        };                
    }
    catch
    {
        results = new ShoppingCartRemoveViewModel
        {
            Message = "Error occurred or invalid input...",
            CartTotal = -1,
            CartCount = -1,
            ItemCount = -1,
            DeleteId = id
        };
    }
    return Json(results);
}

当发生服务器验证错误时,我们不需要在 AJAX 回调中传递数据,除了记录 ID(在这种情况下是 DeleteId),因此我们将所有其他数据字段的值设置为 -1。在 Views\ShoppingCart\index.cshtml 中的 AJAX 回调函数中,我们然后检查只有当 data.ItemCount 不为 -1 时,才应处理 cart-total 和 cart-status(用于购物车摘要局部视图)。

function (data) {
    // Successful requests get here 
    // Update the page elements                        
    if (data.ItemCount == 0) {
        $('#row-' + data.DeleteId).fadeOut('slow');
    }
    $('#update-message').text(data.Message);
    
    //Only process the callback data when no server error occurs
    if (data.ItemCount != -1) {
        $('#cart-total').text(data.CartTotal);
        $('#cart-status').text('Cart (' + data.CartCount + ')');
    }
}

如果我们继续点击“刷新数量”链接而不纠正超出范围的数据,自定义服务器错误消息将显示在购物车页面上。

4.png

购物车微调

此时,仍然有一些小问题需要解决。

  1. 当只输入一个或多个空格字符时,既没有验证错误消息显示,也没有类似于空数量的处理。我们需要添加一个 trim 函数使其成为空字符串。这样刷新购物车将移除专辑项目。jQuery 内置的 trim 函数对我的 IE 浏览器不起作用,所以我为 Views\ShoppingCart\index.cshtml 添加了一个自定义函数。
  2. if (typeof String.prototype.trim !== 'function') {
        String.prototype.trim = function () {
            return this.replace(/^\s+|\s+$/g, '');
        }
    }

    然后我们需要在 $(".RefreshQuantity").click(function ()) 中调用 trim() 函数来检查空字符串。更新后的代码如下所示

    var countToUpdate = 0;
    if ($("#" + $(this).attr("txt-id")).val().trim() !== '') {
        countToUpdate = $("#" + $(this).attr("txt-id")).val();
    }
  3. 当重新输入正确的数字到数量字段时,客户端验证消息可以自动清除。但是服务器端错误消息显示没有这种行为。我们可以在 Views\ShoppingCart\index.cshtml 中添加一个 JavaScript 函数。
  4. function clearUpdateMessage() {
        // Reset update-message area
        $('#update-message').text('');
    }

    该函数通过输入元素的 onkeyuponchange 事件触发器调用。我们只需将属性项添加到 @Html.TextBoxFor() 的第二个参数中。

    @Html.TextBoxFor(model => model.CartItems[ix].Count, 
               new { style = "width:30px; text-align:right;",
                    onkeyup = "clearUpdateMessage();",
                    onchange = "clearUpdateMessage();"                          
        })
  5. 当从 jQuery 代码渲染时,AJAX 调用返回的 HTML 编码消息字符串无法自动解码。我们需要手动处理一些特殊字符的问题,例如,第一个屏幕截图中显示的“Górecki”(否则将显示“G&#243;recki”)。我们可以使用一个简单的 JavaScript 函数来解码 HTML 实体。
  6. function htmlDecode(value) {
        if (value) {
            return $('<div />').html(value).text();
        }
        else {
            return '';
        }
    }

    然后我们可以在 $(".RefreshQuantity").click(function ()) 中调用 htmlDecode 函数来解码 data.message 字符串。

    $('#update-message').text(htmlDecode(data.Message));

结论

通过为 MVC 音乐商店示例应用程序的购物车添加数量更新功能,我们不仅使购物车的业务逻辑更加真实,而且还深入探讨了 jQuery/AJAX 数据更新和不显眼的输入验证,尤其是对于包含模型对象列表中的输入字段的 ViewModel。

快乐使用 ASP.NET MVC 编程!

© . All rights reserved.