在這個(gè)項(xiàng)目中,我們將允許用戶在沒(méi)有注冊(cè)登錄的情況下將專輯加入購(gòu)物車,但是,在完成結(jié)賬的時(shí)候必須完成注冊(cè)工作。購(gòu)物和結(jié)賬將會(huì)被分離到兩個(gè)控制器中:一個(gè) ShoppingCart 控制器,允許匿名用戶使用購(gòu)物車,另一個(gè) Checkout 控制器處理結(jié)賬。我們先從購(gòu)物車的控制器開(kāi)始,然后在下一部分來(lái)處理結(jié)帳。
10年積累的網(wǎng)站制作、成都網(wǎng)站制作經(jīng)驗(yàn),可以快速應(yīng)對(duì)客戶對(duì)網(wǎng)站的新想法和需求。提供各種問(wèn)題對(duì)應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先網(wǎng)站設(shè)計(jì)后付款的網(wǎng)站建設(shè)流程,更有都勻免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。在購(gòu)物車和結(jié)賬的處理中將會(huì)使用到一些新的類,在 Models 文件夾上右鍵,然后使用下面的代碼增加一個(gè)新的類 Cart.
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; }
}
}
這個(gè)類非常類似我們前面使用的類,除了 RecordId 屬性上的[Key] 標(biāo)注之外。我們的購(gòu)物車擁有一個(gè)字符串類型的名為 CartId 的標(biāo)識(shí),用來(lái)允許匿名用戶使用購(gòu)物車,但是,CartId 并不是表的主鍵,表的主鍵是整數(shù)類型的名為 RecordId的字段,根據(jù)約定,EF CodeFirst 將會(huì)認(rèn)為表的主鍵名為 CartId 或者 Id,不過(guò),如果需要的話,我們可以很容易地通過(guò)標(biāo)注或者代碼來(lái)重寫這個(gè)規(guī)則。這里例子演示了在使用 EF CodeFirst 的時(shí)候。當(dāng)我們的表不是約定的樣子時(shí),我們也不必被約定所局限。
下一步,使用下面的代碼增加訂單 Order 類。
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 OrderDetails { get; set; }
}
}
這個(gè)類跟蹤訂單的匯總和發(fā)貨信息,它的結(jié)構(gòu)也不復(fù)雜,訂單依賴我們這里還沒(méi)有定義的一個(gè)類,通過(guò) OrderDetails 屬性來(lái)表示訂單的明細(xì)。我們來(lái)定義一下這個(gè) OrderDetail 類。
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 更新一下,以便包含我們新定義的模型類,包括藝術(shù)家 Artist,更新之后的 MusicStoreEntities 如下所示。
using System.Data.Entity;
namespace MvcMusicStore.Models
{
public class MusicStoreEntities : DbContext
{
public DbSet Albums { get; set; }
public DbSet Genres { get; set; }
public DbSet Artists { get; set; }
public DbSet Carts { get; set; }
public DbSet Orders { get; set; }
public DbSet OrderDetails { get; set; }
}
}
下一步,我們?cè)?Models 文件夾中創(chuàng)建 ShoppingCart 類,ShoppingCart 模型類處理 Cart 表的數(shù)據(jù)訪問(wèn),另外,它還需要處理在購(gòu)物車中增加或者刪除項(xiàng)目的業(yè)務(wù)邏輯。
因?yàn)槲覀儾⒉幌M脩舯仨毜卿浵到y(tǒng)才可以使用購(gòu)物車,對(duì)于沒(méi)有登錄的用戶,我們需要為他們創(chuàng)建一個(gè)臨時(shí)的唯一標(biāo)識(shí),這里使用 GUID,或者被稱為全局唯一標(biāo)識(shí)符,對(duì)于已經(jīng)登錄的用戶,我們直接使用他們的名稱,這個(gè)表示我們保存在 Session 中。
注意:Session 會(huì)話可以很方便地存儲(chǔ)用戶的信息,在用戶離開(kāi)站點(diǎn)之后,這些信息將會(huì)過(guò)期,濫用 Session 信息會(huì)對(duì)大型站點(diǎn)產(chǎn)生影響,我們這里使用 Session 達(dá)到演示目的。
ShoppingCart 類提供了如下的方法:
AddToCart, 將專輯作為參數(shù)加入到購(gòu)物車中,在 Cart 表中跟蹤每個(gè)專輯的數(shù)量,在這個(gè)方法中,我們將會(huì)檢查是在表中增加一個(gè)新行,還是僅僅在用戶已經(jīng)選擇的專輯上增加數(shù)量。
RemoveFromCart,通過(guò)專輯的標(biāo)識(shí)從用戶的購(gòu)物車中將這個(gè)專輯的數(shù)量減少 1,如果用戶僅僅剩下一個(gè),那么就刪除這一行。
EmptyCart,刪除用戶購(gòu)物車中所有的項(xiàng)目。
GetCartItems,獲取購(gòu)物項(xiàng)目的列表用來(lái)顯示或者處理。
GetCount,獲取用戶購(gòu)物車中專輯的數(shù)量
GetTotal,獲取購(gòu)物車中商品的總價(jià)
CreateOrder,將購(gòu)物車轉(zhuǎn)換為結(jié)賬處理過(guò)程中的訂單。
GetCart ,這是一個(gè)靜態(tài)方法,用來(lái)獲取當(dāng)前用戶的購(gòu)物車對(duì)象,它使用 GetCartId 方法來(lái)讀取保存當(dāng)前 Session 中的購(gòu)物車標(biāo)識(shí),GetCartId 方法需要 HttpContextBase 以便獲取當(dāng)前的 Session。
實(shí)際的代碼如下:
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 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 orderDetail = 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);
storeDB.OrderDetails.Add(orderDetail);
}
// 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 控制器需要向視圖傳遞復(fù)雜的信息,這些信息與現(xiàn)有的模型并不完全匹配,我們不希望修改模型來(lái)適應(yīng)視圖的需要;模型類應(yīng)該表示領(lǐng)域信息,而不是用戶界面。一個(gè)解決方案是使用 ViewBag 來(lái)向視圖傳遞信息,就像我們?cè)?Store Manager 中的下列列表處理中那樣,但是通過(guò) ViewBag 來(lái)傳遞大量信息就不好管理了。
另外一個(gè)解決方案是使用視圖模型模式,使用這個(gè)模式,我們需要?jiǎng)?chuàng)建強(qiáng)類型的用于視圖場(chǎng)景的類來(lái)表示信息,這個(gè)類擁有視圖所需要的值或者內(nèi)容。我們的控制器填充信息,然后傳遞這種類的對(duì)象供視圖使用,這樣就可以得到強(qiáng)類型的、編譯時(shí)檢查支持,并且在視圖模板中帶有智能提示。
我們將會(huì)創(chuàng)建兩個(gè)視圖模型用于我們的 ShoppingCart 控制器:ShoppingCartViewModel 將會(huì)用于用戶的購(gòu)物車,而 ShoppingCartRemoveViewModel 會(huì)用于在購(gòu)物車中刪除內(nèi)容時(shí)的確認(rèn)提示信息。
首先在項(xiàng)目中創(chuàng)建 ViewModels 文件夾來(lái)組織我們的項(xiàng)目文件,在項(xiàng)目上點(diǎn)擊鼠標(biāo)的右鍵,然后選擇添加 –〉新文件夾。
命名為 ViewModels
下一步,在 ViewModels 文件夾中增加 ShoppingCartViewModel 類,它包括兩個(gè)屬性,一個(gè) CartItem 的列表,另外一個(gè)屬性是購(gòu)物中的總價(jià)。
using System.Collections.Generic;
using MvcMusicStore.Models;
namespace MvcMusicStore.ViewModels
{
public class ShoppingCartViewModel
{
public List CartItems { get; set; }
public decimal CartTotal { get; set; }
}
}
然后,增加 ShoppingCartRemoveViewModel 類,它包括五個(gè)屬性。
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 控制器有三個(gè)主要的目的:增加項(xiàng)目到購(gòu)物車,從購(gòu)物車中刪除項(xiàng)目,查看購(gòu)物車中的項(xiàng)目??刂破魇褂玫轿覀儎倓倓?chuàng)建的三個(gè)類:ShoppingCartViewModel,ShoppingCartRemoveViewModel 和 ShoppingCart,像 StoreController 和 StoreManagerController 一樣,我們?cè)诳刂破髦性黾右粋€(gè) MusicStoreEntities 字段來(lái)操作數(shù)據(jù)。
在項(xiàng)目中使用空的控制器模板創(chuàng)建 Shopping Cart 控制器
下面是已經(jīng)完成的控制器代碼,Index 和 Add 方法看起來(lái)非常熟悉。Remove 和 CartSummary 這兩個(gè) Action 方法處理兩種特定的場(chǎng)景,我們將在后面討論。
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");
}
}
}
下面我們將創(chuàng)建 Shopping Cart 的 Index Action 視圖,這個(gè)視圖使用強(qiáng)類型的 ShoppingCartViewModel ,像以前的視圖一樣,使用 List 視圖模板。
在這里,我們不使用 Html.ActionLink 從購(gòu)物車中刪除項(xiàng)目,我們將會(huì)使用 JQuery 來(lái)包裝客戶端使用 RemoveLink 的類所有超級(jí)鏈接元素的事件,不是提交表單,而是通過(guò)客戶端的事件向 RemoveFromCart 控制器方法發(fā)出 Ajax 請(qǐng)求,然后 RemoveFromCart 返回 JSON 格式的結(jié)果,這個(gè)結(jié)果被發(fā)送到我們?cè)?AjaxOptions 的 OnSucess 參數(shù)中創(chuàng)建的 JavaScript 函數(shù),在這里是 handleUpdate,handleUpdate 函數(shù)解析 JSON 格式的結(jié)果,然后通過(guò) jQuery 執(zhí)行下面的四個(gè)更新。
因?yàn)樵?Index 視圖中我們處理了刪除的場(chǎng)景,我們就不再需要為 RemoveFromCart 方法增加額外的視圖。下面是視圖的完整代碼。
@model MvcMusicStore.ViewModels.ShoppingCartViewModel
@{
ViewBag.Title = "Shopping Cart";
}
Review your cart:
Album Name
Price (each)
Quantity
@foreach (var item in Model.CartItems)
{
@Html.ActionLink(item.Album.Title, "Details", "Store", new { id = item.AlbumId }, null)
@item.Album.Price
@item.Count
Remove from cart
}
Total
@Model.CartTotal
為了測(cè)試一下,我們需要向購(gòu)物車中增加一些項(xiàng)目,更新 Store 的 Details 視圖包含添加到購(gòu)物車按鈕,在這里,我們還需要包含我們后來(lái)增加的專輯的一些額外信息,流派,藝術(shù)家,價(jià)格等等。更新后的視圖如下所示。
@model MvcMusicStore.Models.Album
@{
ViewBag.Title = "Album -" + Model.Title;
}
@Model.Title
Genre: @Model.Genre.Name
Artist: @Model.Artist.Name
Price: @String.Format("{0:F}", Model.Price)
現(xiàn)在,我們可以在商店中通過(guò)購(gòu)物車來(lái)購(gòu)買和刪除一些項(xiàng)目了。運(yùn)行程序,瀏覽 Store 控制器的 Index 。
然后,點(diǎn)擊某個(gè)分類來(lái)查看專輯的列表。
點(diǎn)擊某個(gè)專輯來(lái)顯示專輯的詳細(xì)內(nèi)容,現(xiàn)在已經(jīng)有了加入購(gòu)物車的按鈕。
點(diǎn)擊加入購(gòu)物車之后,可以在購(gòu)物車中看到。
在購(gòu)物車中,可以點(diǎn)擊從購(gòu)物車中刪除的鏈接,將會(huì)看到 Ajax 更新購(gòu)物車的效果。
現(xiàn)在的購(gòu)物車允許沒(méi)有注冊(cè)的用戶使用購(gòu)物車添加項(xiàng)目,在下一部分,我們將允許匿名用戶注冊(cè)和完成結(jié)賬的處理。