我們允許使用者在沒注冊的情況下把唱片放進他們的購物車,但是當他們要結算時就必須注冊為會員。購物和結算将使用兩個不同的控制 器: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;
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):
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;
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;
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);
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
// 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;
// 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;
模型視圖
我 們的ShoppingCart需要傳遞一些複合資訊給它的視圖,這些資訊并沒有清晰地映射到模型類。我們不想為了視圖而修改模型,模型類應當代表我們的領 域,而不是使用者界面。一種解決方法是使用ViewBag傳遞相關資訊,就象之前在StoreManager中為下拉框傳遞資料一樣。但是通過 ViewBag傳遞太多資訊會變得難以管理。
另一種方案是使用視圖模型(ViewModels),這種方式可以為指定的視圖建立最優的強類型類,并且在視圖模闆中為所需的值/内容公開屬性。控制器可以填充和傳遞這些“視圖優化”類給視圖模闆使用,而且在視圖模闆中支援類型安全、編譯檢查和智能感應器。
我們建立兩個視圖模型供ShoppingCartController使用:ShoppingCartViewModel儲存使用者購物車的内容,ShoppingCartRemoveViewModel在使用者從購物車移除物品時顯示确認資訊。
在項目中添加新檔案夾ViewModels。
接下來在ViewModels檔案夾中添加類ShoppingCartViewModel,它有兩個屬性:購物車物品集合和購物車所有物品的總金額。
using MvcMusicStore.Models;
namespace MvcMusicStore.ViewModels
public class ShoppingCartViewModel
public List<Cart> CartItems { get; set; }
public decimal CartTotal { get; set; }
在ViewModels檔案夾下添加ShoppingCartRemoveViewModel類:
public class ShoppingCartRemoveViewModel
public string Message { 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控制器:
下面是完整的ShoppingCartController的代碼。Index和Add控制器操作看起來非常眼熟。Remove和CartSummary控制器操作處理兩個特殊的事件,我們在下一章讨論它們。
using System.Linq;
using MvcMusicStore.ViewModels;
namespace MvcMusicStore.Controllers
public class ShoppingCartController : Controller
//
// 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
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
// 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()
ViewData["CartCount"] = cart.GetCount();
return PartialView("CartSummary");
使用Ajax.ActionLink實作Ajax更新
為ShoppingCartViewModel類建立一個強類型的Shopping Cart Index頁面,并且象以前一樣使用List視圖模闆。
(注:這裡實際上不能在“支架模闆”中選擇List,否則生成視圖後直接運作會出錯。因為Index方法傳回的是單個ShoppingCartViewModel對象而不是集合。當然你可以直接複制網頁上的代碼來覆寫自動生成的代碼,不過你還是要明白這一點)
使用Ajax.ActionLink代替Html.ActionLink來生成從購物車移除物品的連結。
@Ajax.ActionLink("Remove from cart",
"RemoveFromCart",
new { id = item.RecordId }, new AjaxOptions { OnSuccess = "handleUpdate"
})
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>
Price (each)
Quantity
<th></th>
</tr>
@foreach (var item in
Model.CartItems)
<tr id="[email protected]">
<td>
@Html.ActionLink(item.Album.Title,
"Details", "Store", new { id = item.AlbumId }, null)
</td>
@item.Album.Price
<td id="[email protected]">
@item.Count
<a href="#" class="RemoveLink" data-id="@item.RecordId">Remove from cart</a>
</tr>
<td>
Total
</td>
<td id="cart-total">
@Model.CartTotal
</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" />
<div id="album-details">
<p>
<em>Genre:</em>
@Model.Genre.Name
</p>
<em>Artist:</em>
@Model.Artist.Name
<em>Price:</em>
@String.Format("{0:F}",
Model.Price)
<p class="button">
@Html.ActionLink("Add to
cart", "AddToCart",
"ShoppingCart", new { id = Model.AlbumId }, "")
</div>
現在我們可以通過點選“Store”來測試對購物車添加和移除物品,運作應用程式并通路/Store:
點選某個類别檢視唱片清單:
點選唱片标題進入更新後的Store Details視圖:
點選“Add to cart”按紐,在Shopping Cart的首頁中顯示購物車清單:
購物車被加載之後,你可以點選“Remove from cart”連結檢視Ajax的更新效果。
我們建立了購物車,讓使用者無需注冊就可以往購物車中添加物品,下一章,我們将允許他們注冊以完成結算過程。