1. 程式人生 > >跟我學ASP.NET MVC之七:SportsStrore購物車

跟我學ASP.NET MVC之七:SportsStrore購物車

repos ras img sports collect dev PC RM VC

摘要:

SportsStore應用程序進展很順利,但是我不能銷售產品直到設計了一個購物車。在這篇文章裏,我就將創建一個購物車。

在目錄下的每個產品旁邊添加一個添加到購物車按鈕。點擊這個按鈕將顯示客戶到目前為止選擇的產品摘要,包含總價格。這時候,用戶可以點擊繼續購物按鈕返回產品目錄,或者點擊現在下單按鈕完成訂單結束購物過程。

定義Cart實體類

在SportsStore.Domain工程的Entities文件夾下,創建代碼文件Cart.cs。

 1 using System.Collections.Generic;
 2 using System.Linq;
 3 
 4 namespace
SportsStore.Domain.Entities 5 { 6 public class Cart 7 { 8 private List<CartLine> lineCollection = new List<CartLine>(); 9 public void AddItem(Product product, int quantity) 10 { 11 CartLine line = lineCollection.Where(p => p.Product.ProductID == product.ProductID).FirstOrDefault();
12 if (line == null) 13 { 14 lineCollection.Add(new CartLine 15 { 16 Product = product, 17 Quantity = quantity 18 }); 19 } 20 else { 21 line.Quantity += quantity;
22 } 23 } 24 25 public void RemoveLine(Product product) 26 { 27 lineCollection.RemoveAll(l => l.Product.ProductID == product.ProductID); 28 } 29 30 public decimal ComputeTotalValue() 31 { 32 return lineCollection.Sum(e => e.Product.Price * e.Quantity); 33 } 34 35 public void Clear() 36 { 37 lineCollection.Clear(); 38 } 39 40 public IEnumerable<CartLine> CartLines 41 { 42 get { return lineCollection; } 43 } 44 } 45 46 public class CartLine 47 { 48 public Product Product { get; set; } 49 public int Quantity { get; set; } 50 } 51 }

Cart類使用了CartLine類,他們定義在同一個代碼文件內,保存一個客戶選擇的產品,以及客戶想買的數量。我定義了添加條目到購物車的方法,從購物車刪除之前已經添加的條目的方法,計算購物車內條目總價格,以及刪除所有條目清空購物車的方法。我還提供了一個通過IEnumrable<CartLine>訪問購物車內容的屬性。這些都很直觀,通過一點點LINQ很容易用C#實施。

定義視圖模型類

在SportsStore.WebUI工程的Models文件夾內,創建代碼文件CartIndexViewModel。

 1 using SportsStore.Domain.Entities;
 2 
 3 namespace SportsStore.WebUI.Models
 4 {
 5     public class CartIndexViewModel
 6     {
 7         public Cart Cart { get; set; }
 8         public string ReturnUrl { get; set; }
 9     }
10 }

該模型類有兩個屬性。Cart屬性保存了購物車信息,ReturnUrl保存了產品目錄的URL,需要這個信息是因為,客戶可以隨時點擊繼續購物按鈕,返回之前的產品目錄URL。

添加購物車控制器CartController

 1 using SportsStore.Domain.Abstract;
 2 using SportsStore.Domain.Entities;
 3 using SportsStore.WebUI.Models;
 4 using System.Linq;
 5 using System.Web.Mvc;
 6 
 7 namespace SportsStore.WebUI.Controllers
 8 {
 9     public class CartController : Controller
10     {
11         private IProductRepository repository;
12 
13         public CartController(IProductRepository productRepository)
14         {
15             repository = productRepository;
16         }
17 
18         public ActionResult Index(string returnUrl)
19         {
20             return View(new CartIndexViewModel
21             {
22                 Cart = GetCart(),
23                 ReturnUrl = returnUrl
24             });
25         }
26 
27         public RedirectToRouteResult AddToCart(int productId, string returnUrl)
28         {
29             Product product = repository.Products.FirstOrDefault(p => p.ProductID == productId);
30             if (product != null)
31             {
32                 GetCart().AddItem(product, 1);
33             }
34             return RedirectToAction("Index", new { returnUrl = returnUrl });
35         }
36 
37         public RedirectToRouteResult RemoveFromCart(int productId, string returnUrl)
38         {
39             Product product = repository.Products.FirstOrDefault(p => p.ProductID == productId);
40             if (product != null)
41             {
42                 GetCart().RemoveLine(product);
43             }
44             return RedirectToAction("Index", new { returnUrl });
45         }
46 
47         private Cart GetCart()
48         {
49             Cart cart = (Cart)Session["Cart"];
50             if (cart == null)
51             {
52                 cart = new Cart();
53                 Session["Cart"] = cart;
54             }
55             
56            return cart;
57         }
58     }
59 }

該控制器的一些解釋:

  • GetCart方法:從Session裏獲取購物車對象,如果該對象為空,則創建這個對象,添加到Session,並返回該對象。
  • Index方法:傳入returnUrl參數,返回購物車摘要信息視圖。該視圖的模型類是CartIndexViewModel,模型類對象的Cart屬性通過調用方法GetCart返回,ReturnUrl屬性使用方法參數賦值。
  • AddToCart方法:傳入productId參數和returnUrl參數,添加產品到購物車,並返回重定向的購物車摘要信息視圖。方法的返回類型是RedirectToRouteResult,該類的基類是ActionResult。
  • RemoveFromCart方法:傳入productId參數和returnUrl參數,從購物車中刪除產品,並返回重定向的購物車摘要信息視圖。
  • AddToCart方法和RemoveFromCart方法都是通過調用Controller基類的RedirectToAction方法,返回重定向視圖類RedirectToRouteResult的對象。
  • RedirectToAction方法的第一個參數是Action名稱,第二個無類型對象參數提供傳入Action的參數值。這裏將重定向到Cart控制器的Index方法。

添加到購物車按鈕

修改ProductSummary.cshtml視圖,添加Add to Cart按鈕。

 1 @model SportsStore.Domain.Entities.Product
 2 
 3 <div class="well">
 4     <h3>
 5         <strong>@Model.Name</strong>
 6         <span class="pull-right label label-primary">@Model.Price.ToString("c")</span>
 7     </h3>
 8     @using (Html.BeginForm("AddToCart", "Cart"))
 9     {
10         <div class="pull-right">
11             @Html.HiddenFor(x => x.ProductID)
12             @Html.Hidden("returnUrl", Request.Url.PathAndQuery)
13             <input type="submit" class="btn btn-success" value="Add to cart" />
14         </div>
15     }
16     <span class="lead"> @Model.Description</span>
17 </div>
  • 使用Html.BeginForm幫助方法,生成AddToCart表單。方法的第一個參數是Action名稱AddToCart,第二個參數是控制器名稱Cart。
  • 使用Html.HiddenFor幫助方法,生成表單的hidden html元素,該元素的name屬性是字符串ProductID,值是該產品的ProductID值。
  • 使用Html.Hidden幫助方法,生成表單的hidden html元素,該元素的name屬性是字符串returnUrl,值是當前頁面的Url。
  • 控制器的AddToCart方法將通過表單元素的名稱,獲取要傳入該方法的參數productID和returnUrl的值(大小寫不敏感)。

添加購物車詳細信息視圖

在Views文件夾的Cart文件夾內,添加Index.cshtml。

 1 @model SportsStore.WebUI.Models.CartIndexViewModel
 2 
 3 @{
 4     ViewBag.Title = "Sports Store: Your Cart";
 5 }
 6 <style>
 7     #cartTable td {
 8         vertical-align: middle;
 9     }
10 </style>
11 <h2>Your cart</h2>
12 <table id="cartTable" class="table">
13     <thead>
14         <tr>
15             <th>Quantity</th>
16             <th>Item</th>
17             <th class="text-right">Price</th>
18             <th class="text-right">Subtotal</th>
19         </tr>
20     </thead>
21     <tbody>
22         @foreach (var line in Model.Cart.CartLines)
23         {
24             <tr>
25                 <td class="text-center">@line.Quantity</td>
26                 <td class="text-left">@line.Product.Name</td>
27                 <td class="text-right">
28                     @line.Product.Price.ToString("c")
29                 </td>
30                 <td class="text-right">
31                     @((line.Quantity * line.Product.Price).ToString("c"))
32                 </td>
33                 <td>
34                     @using (Html.BeginForm("RemoveFromCart", "Cart"))
35                     {
36                         @Html.Hidden("ProductId", line.Product.ProductID)
37                         @Html.HiddenFor(x => x.ReturnUrl)
38                         <input class="btn btn-sm btn-warning" type="submit" value="Remove" />
39                     }
40                 </td>
41             </tr>
42         }
43     </tbody>
44     <tfoot>
45         <tr>
46             <td colspan="3" class="text-right">Total:</td>
47             <td class="text-right">
48                 @Model.Cart.ComputeTotalValue().ToString("c")
49             </td>
50         </tr>
51     </tfoot>
52 </table>
53 <div class="text-center">
54     <a class="btn btn-primary" href="@Model.ReturnUrl">Continue shopping</a>
55 </div>
  • 這個視圖以表格的形式,展示了購物車摘要產品信息,包含了產品名稱、購買數量、單價、價格信息。
  • 每個產品條目後面,添加刪除表單和刪除按鈕,這裏的表單和按鈕,同之前添加到購物車按鈕一樣。
  • 表格底部,調用ComputeTotalValue方法,返回總價格。
  • 頁面底部中間,顯示一個Continue Shopping按鈕,ReturnUrl屬性指向之前的產品目錄Url,點擊後返回產品目錄頁面。

運行程序,得到運行結果。

技術分享圖片

這裏我選擇了Chess目錄,瀏覽器地址欄上的URL變成了:http://localhost:17596/Chess

如果我點擊Human Chess Board產品的Add To Cart按鈕,得到頁面:

技術分享圖片

註意這時候的瀏覽器地址欄的地址變成了:http://localhost:17596/Cart/Index?returnUrl=%2FChess,包含的購物車的Cart/Index,以及以問號?開始的參數?returnUrl=%2FChess。returnUrl的值就是剛才的頁面地址。

如果再點擊Continue Shoppinga按鈕,將返回到returnUrl指向的頁面,既是剛才的頁面:http://localhost:17596/Chess

添加購物車摘要視圖

我還需要添加一個顯示購物車摘要信息的小部件,可以在所有應用程序頁面上都能看到,點擊後返回購物車詳細信息。這個小部件和導航條目類似,需要使用返回PartialViewResult的Action方法,在_Layout.cshtml視圖中,使用Html.Action方法嵌入這個視圖。

首先修改CartController控制器,添加Summary方法。

1         public PartialViewResult Summary()
2         {
3             return PartialView(GetCart());
4         }

然後,添加Summary視圖。

1 @model SportsStore.Domain.Entities.Cart
2 
3 <div class="navbar-text navbar-right">
4     <b>Your cart:</b>
5     @Model.CartLines.Sum(x => x.Quantity) item(s),
6     @Model.ComputeTotalValue().ToString("c")
7 </div>

最後,修改_Layout.cshtml文件,調用Html幫助類方法Action,嵌入這個視圖到頭部導航欄內。

 1 <!DOCTYPE html>
 2 
 3 <html>
 4 <head>
 5     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 6     <link href="~/Content/bootstrap.css" rel="stylesheet" />
 7     <link href="~/Content/bootstrap-theme.css" rel="stylesheet" />
 8     <title>@ViewBag.Title</title>
 9     <style>
10         .navbar-right {
11             float: right !important;
12             margin-right: 15px;
13             margin-left: 15px;
14         }
15     </style>
16 </head>
17 <body>
18     <div class="navbar navbar-inverse" role="navigation">
19         <a class="navbar-brand" href="#">SPORTS STORE</a>
20         @Html.Action("Summary", "Cart")
21     </div>
22     <div class="row panel">
23         <div id="categories" class="col-xs-3">
24             @Html.Action("Menu", "Nav")
25         </div>
26         <div class="col-xs-8">
27             @RenderBody()
28         </div>
29     </div>
30 </body>
31 </html>

這裏添加頁面樣式navbar-right,使得購物車摘要信息部件在頭部導航欄內靠右顯示。

運行程序,得到運行結果。

技術分享圖片

使用模板綁定

MVC使用一個名叫模板綁定的系統,為了傳參數給行為方法,它從HTTP請求創建C#對象並作為參數傳給行為方法。MVC框架就是這樣來處理表單的。它看到目標行為方法的參數,然後使用模板綁定得到瀏覽器發送過來的表單裏元素的值,然後根據名稱轉化成對應類型的相同名稱的參數,傳給行為方法。

模板綁定可以從請求裏的任何信息中創建C#類型。這是MVC框架的核心特征之一。我將創建一個客戶的模板綁定來改進CartController控制器。

在SportsStore.WebUI工程的Infrastructure文件夾內創建子文件夾Binders,並在子文件夾下創建代碼文件CartModelBinder.cs。

 1 using SportsStore.Domain.Entities;
 2 using System.Web.Mvc;
 3 
 4 namespace SportsStore.WebUI.Infrastructure.Binders
 5 {
 6     public class CartModelBinder : IModelBinder
 7     {
 8         private const string sessionKey = "Cart";
 9 
10         public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
11         {
12             // get the Cart from the session
13             Cart cart = null;
14             if (controllerContext.HttpContext.Session != null)
15             {
16                 cart = (Cart)controllerContext.HttpContext.Session[sessionKey];
17             }
18             // create the Cart if there wasn‘t one in the session data
19             if (cart == null)
20             {
21                 cart = new Cart();
22                 if (controllerContext.HttpContext.Session != null)
23                 {
24                     controllerContext.HttpContext.Session[sessionKey] = cart;
25                 }
26             }
27             // return the cart
28             return cart;
29         }
30     }
31 }
  • CartModelBinder類繼承接口IModelBinder,並實現接口的方法BindModel。
  • 接口方法BindModel提供兩個參數,參數類型ControllerContext:controllerContext獲取控制器上下文環境信息,參數類型ModelBindingContext:bindingContext獲取綁定的上下文信息。
  • 接口方法BindModel返回類型是object,他返回的對象的值就是Action方法參數的值。
  • 參數controllerContext對象的HttpContext屬性保存了HTTP請求中的信息,通過controllerContext.HttpContext.Session獲取HTTP請求中的Session信息。
  • 我的BindMode方法的方法體代碼和CartController控制器的GetCart方法相同。都是從Session裏獲取購物車對象,如果該對象為空,則創建這個對象,添加到Session,並返回該對象。

有了模板綁定方法後,還需要將模板方法在Global.asax.cs代碼的事件Application_Start內,通過調用ModelBinders.Binders.Add方法,註冊到MVC應用程序裏。

修改Global.asax代碼。

 1 using SportsStore.Domain.Entities;
 2 using SportsStore.WebUI.Infrastructure.Binders;
 3 using System;
 4 using System.Collections.Generic;
 5 using System.Linq;
 6 using System.Web;
 7 using System.Web.Mvc;
 8 using System.Web.Routing;
 9 
10 namespace SportsStore
11 {
12     public class MvcApplication : System.Web.HttpApplication
13     {
14         protected void Application_Start()
15         {
16             AreaRegistration.RegisterAllAreas();
17             RouteConfig.RegisterRoutes(RouteTable.Routes);
18 
19             ModelBinders.Binders.Add(typeof(Cart), new CartModelBinder());
20         }
21     }
22 }
ModelBinders.Binders是ModelBinders的靜態屬性,它是一個繼承自IDictionary類型的對象,它的Add方法提供兩個參數完成模板綁定類型的綁定。第一個參數是返回類型參數,第二個參數實例化一個繼承自IModelBinder類型的對象。
這樣,我現在可以修改CartController控制器的各個Action方法,添加Cart類型參數,並使用模板綁定方式獲得Cart對象參數的值。
 1 using SportsStore.Domain.Abstract;
 2 using SportsStore.Domain.Entities;
 3 using SportsStore.WebUI.Models;
 4 using System.Linq;
 5 using System.Web.Mvc;
 6 
 7 namespace SportsStore.WebUI.Controllers
 8 {
 9     public class CartController : Controller
10     {
11         private IProductRepository repository;
12 
13         public CartController(IProductRepository productRepository)
14         {
15             repository = productRepository;
16         }
17 
18         public ActionResult Index(Cart cart, string returnUrl)
19         {
20             return View(new CartIndexViewModel
21             {
22                 Cart = cart,
23                 ReturnUrl = returnUrl
24             });
25         }
26 
27         public RedirectToRouteResult AddToCart(Cart cart, int productId, string returnUrl)
28         {
29             Product product = repository.Products.FirstOrDefault(p => p.ProductID == productId);
30             if (product != null)
31             {
32                 cart.AddItem(product, 1);
33             }
34             return RedirectToAction("Index", new { returnUrl = returnUrl });
35         }
36 
37         public RedirectToRouteResult RemoveFromCart(Cart cart, int productId, string returnUrl)
38         {
39             Product product = repository.Products.FirstOrDefault(p => p.ProductID == productId);
40             if (product != null)
41             {
42                 cart.RemoveLine(product);
43             }
44             return RedirectToAction("Index", new { returnUrl });
45         }
46 
47         public PartialViewResult Summary(Cart cart)
48         {
49             return PartialView(cart);
50         }
51     }
52 }

控制器方法的參數Cart:cart將從模板綁定類的方法BindModel中,返回cart對象。



跟我學ASP.NET MVC之七:SportsStrore購物車