ASP.NET MVC Music Store教程(8):购物车和AJAX更新

简介: 转自 http://firechun.blog.163.com/blog/static/3180452220110314454425/我们允许用户在没注册的情况下把唱片放进他们的购物车,但是当他们要结算时就必须注册为会员。

转自 http://firechun.blog.163.com/blog/static/3180452220110314454425/

我们允许用户在没注册的情况下把唱片放进他们的购物车,但是当他们要结算时就必须注册为会员。购物和结算将使用两个不同的控制 器:ShoppingCartController允许匿名用户把商品放进购物车,CheckoutController则处理结算过程。这一章我们从购 物车开始,下一章我们创建结算。

添加Cart、Order和OrderDetails模型类

购物和结算都要使用某些新的类,在Models文件夹上单击右键添加Cart类(Card.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]特性外,这个类与目前我们使用的其它类非常相似。Cart有一个叫CartId的字符串标识,以便允许匿名者购物,但 是RecordId是表的整形主键。习惯上,实体框架“代码优先”会认为Cart表的主键是CartId或ID,而如果需要的话,我们很容易通过注释或代 码忽略这一点。上面就是一个例子,当实体框架“代码优先”的约定适合我们时,我们就使用,反之,我们也不会被这些约定限制。

接下来添中Order类(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; } 
    } 
}

这个类记录订单摘要和交货信息,它还不能编译,因为里面有一个OrderDetails导航属性依赖的类我们还没创建,让我们添加一个OrderDetails类(OrderDetails.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类中添加这些新模型类的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; } 
    } 
}

管理购物车的业务逻辑

下面我们在Models文件夹中创建一个ShoppingCart类,ShoppingCart模型处理对Cart表的数据访问,并且还处理往购物车添加和移除商品的业务逻辑。

我们不想让用户仅仅为了往购物车内放一个商品就去申请帐号,当他们访问购物车时,我们给他们分配一个临时的唯一标识(使用GUID,全球唯一标识符),使用ASP.NET的Session来保存这个ID。

注: ASP.NET Session 能够方便地保存用户特定信息,并且在用户离开网站后自动过期。虽然滥用Sission会对较大站点产生影响,为了演示目的的轻量级使用没有任何问题。

ShoppingCart类公开以下方法:

AddToCart:以一个Album对象为参数,并把它添加到用户的购物车中。因为Cart表记录每种唱片的数量,方法中包括在需要时创建一个新行或者当用户拿取已有的唱片时增加该唱片的数量的业务逻辑。

RemoveFromCart:以Album ID为参数,并从用户的购物车中把它移除。如果用户的购物车中该唱片只有一份,则删除一行。

EmptyCart:从用户的购物车中移除所有商品

GetCartItems:返回购物车中的商品列表

GetCount:返回用户购物车中的唱片的总数

GetTotal:计算用户购物车中商品的总金额

CreateOrder:在结算阶段从购物车生成一张订单

GetCart:这是一个静态方法,允许控制器获得一个Cart对象。它调用GetCartId方法从用户的Session中读取CartId。GetCartId需要HttpContextBase以从用户的Sission中读取CartId。

下面是完整的ShoppingCart类的代码:

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 orderDetails = 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); 
            } 
            // 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传递相关信息,就象之前在StoreManager中为下拉框传递数据一样。但是通过 ViewBag传递太多信息会变得难以管理。

另一种方案是使用视图模型(ViewModels),这种方式可以为指定的视图创建最优的强类型类,并且在视图模板中为所需的值/内容公开属性。控制器可以填充和传递这些“视图优化”类给视图模板使用,而且在视图模板中支持类型安全、编译检查和智能感应器。

我们创建两个视图模型供ShoppingCartController使用:ShoppingCartViewModel保存用户购物车的内容,ShoppingCartRemoveViewModel在用户从购物车移除物品时显示确认信息。

在项目中添加新文件夹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; } 
    } 
}

在ViewModels文件夹下添加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控制器:

ASP.NET MVC Music Store教程(8):购物车和AJAX更新 - firechun - firechun的博客

 下面是完整的ShoppingCartController的代码。Index和Add控制器操作看起来非常眼熟。Remove和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"); 
        } 
    } 
}

使用Ajax.ActionLink实现Ajax更新

为ShoppingCartViewModel类创建一个强类型的Shopping Cart Index页面,并且象以前一样使用List视图模板。

ASP.NET MVC Music Store教程(8):购物车和AJAX更新 - firechun - firechun的博客

 (注:这里实际上不能在“支架模板”中选择List,否则生成视图后直接运行会出错。因为Index方法返回的是单个ShoppingCartViewModel对象而不是集合。当然你可以直接复制网页上的代码来覆盖自动生成的代码,不过你还是要明白这一点)

使用Ajax.ActionLink代替Html.ActionLink来生成从购物车移除物品的链接。

@Ajax.ActionLink("Remove from cart", 
"RemoveFromCart",  
    new { id = item.RecordId }, new AjaxOptions { OnSuccess = "handleUpdate" 
})

Ajax.ActionLink 和Html.ActionLink方法非常相似,不同的是表单提交时为RemoveFromCart方法生成一个Ajax回调。 RemoveFromCart方法返回一个序列化的JsonResult对象(关于JsonResult对象,请参见:http://msdn.microsoft.com/zh-cn/library/system.web.mvc.jsonresult.aspx) ,它被自动传递给AjaxOptions的OnSuccess参数指定的JavaScript方法,在本例中,这个方法是handleUpdate。handleUpdate使用JQuery为页面执行四个快速更新:

  • 1.从列表中移除删掉的唱片
  • 2.更新标题中的数量
  • 3.为用户显示更新信息
  • 4.更新购物车总金额

由于Index视图使用Ajax回调来处理“移除”情况,我们不需要为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视图,添加一个“Add to cart”按纽,我们还可以为唱片追加一些相关信息: Genre, Artist, Price, t和Album Art。更新后的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”来测试对购物车添加和移除物品,运行应用程序并访问/Store:

 ASP.NET MVC Music Store教程(8):购物车和AJAX更新 - firechun - firechun的博客

点击某个类别查看唱片列表:

ASP.NET MVC Music Store教程(8):购物车和AJAX更新 - firechun - firechun的博客

 点击唱片标题进入更新后的Store Details视图:

ASP.NET MVC Music Store教程(8):购物车和AJAX更新 - firechun - firechun的博客

 点击“Add to cart”按纽,在Shopping Cart的首页中显示购物车清单:

ASP.NET MVC Music Store教程(8):购物车和AJAX更新 - firechun - firechun的博客

 购物车被加载之后,你可以点击“Remove from cart”链接查看Ajax的更新效果。

ASP.NET MVC Music Store教程(8):购物车和AJAX更新 - firechun - firechun的博客

 我们建立了购物车,让用户无需注册就可以往购物车中添加物品,下一章,我们将允许他们注册以完成结算过程。

 
目录
相关文章
|
3月前
|
开发框架 JSON JavaScript
ASP.NET Core3.1实战教程---基于Jquery单文件上传
ASP.NET Core3.1实战教程---基于Jquery单文件上传
26 0
|
3月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
38 0
|
25天前
|
开发框架 .NET 物联网
.NET从入门到精通,零基础也能搞定的基础知识教程
.NET从入门到精通,零基础也能搞定的基础知识教程
19 0
|
30天前
|
开发框架 前端开发 .NET
进入ASP .net mvc的世界
进入ASP .net mvc的世界
28 0
|
1月前
|
开发框架 前端开发 .NET
C# .NET面试系列六:ASP.NET MVC
<h2>ASP.NET MVC #### 1. MVC 中的 TempData\ViewBag\ViewData 区别? 在ASP.NET MVC中,TempData、ViewBag 和 ViewData 都是用于在控制器和视图之间传递数据的机制,但它们有一些区别。 <b>TempData:</b> 1、生命周期 ```c# TempData 的生命周期是短暂的,数据只在当前请求和下一次请求之间有效。一旦数据被读取,它就会被标记为已读,下一次请求时就会被清除。 ``` 2、用途 ```c# 主要用于在两个动作之间传递数据,例如在一个动作中设置 TempData,然后在重定向到另
95 5
|
1月前
|
移动开发 前端开发 安全
Ajax跨域的所有方法(最详细带使用教程!!!)
Ajax跨域的所有方法(最详细带使用教程!!!)
|
8月前
|
存储 开发框架 .NET
ASP.NET Core SignalR系列之Hub教程
ASP.NET Core SignalR系列之Hub教程
100 0
|
4月前
|
定位技术 C#
.NET微信网页开发相关文章教程
.NET微信网页开发相关文章教程
|
4月前
|
XML 数据可视化 数据库
VB.NET—DataGridView控件教程详解
VB.NET—DataGridView控件教程详解
135 0
|
5月前
|
开发框架 自然语言处理 前端开发
基于ASP.NET MVC开发的、开源的个人博客系统
基于ASP.NET MVC开发的、开源的个人博客系统
51 0

相关实验场景

更多