接下來不囉嗦直接搞購物車。首先我們需要一個購物車的實體。
定義購物車實體
我們需要一個購物車實體來的模型域(Domain),因爲購物車是構成我們應用程序的 業務領域。接下我們要創建購物車實體領域(Domain),在們的域模型(Domian)項目"SportsStore.Domain"的Entities文件下創建我們的購物車實體域模型,如下圖1.
圖1.我們的Cart.cs(購物車的實體域模型)的代碼具體如下:
我們的前臺頁面需要一個"添加到購物車"的按鈕,爲了方便期間,還記得我們之前在展示商品信息的時候把展示商品信息的那部分有了一個局部視圖搞的,所以我們在展示商品信息的頁面就來添加這個按鈕。
我們的那個局部試圖頁面就在我們的web項目/Views/Shared/ProductSummary.cshtml頁面,修改ProductSummary.cshtml頁面如下:
@model SportsStore.Domain.Entities.Product @{ ViewBag.Title = "ProductSummary"; } <div class="item"> <h3>@Model.Name</h3> @Model.Description @using (Html.BeginForm("AddToCart","Cart")) { @Html.HiddenFor(h=>h.ProductID) @Html.Hidden("returnUrl",this.Request.Url.PathAndQuery) <input type="submit" value="+ Add to cart" /> } <h4>@Model.Price.ToString("C")</h4> </div>
注:上面代碼紅色加粗的代碼就是我們添加進來的按鈕,當我們的表單提交時,就會觸發Cart控制器裏的AddToCart Action(方法)來執行。下來我們就要實現購物車的控制器(Controller)包括,整,刪,改,查;下來在我們Web項目的的Controllers文件夾下創建我們的CartController(控制器),具體代碼如下:
有一個特性是會話(Session)狀態,他允許我們的數據關聯會話(session)。這一理想適合我們的Cart類,我們希望每個用戶都有自己的購物車,而且請求購物車的時間要長久,數據與會話(session)到期後要刪除會話狀態(session),這意味着我們不需要管理我們或存儲我們的購物車對象。添加一個會話(Session)狀態,我們只關注會話(Session)對象。
接下來我們需要顯示購物車的內容,我們在CartController控制器裏的AddToCart Action(方法)和RemoverFromCart Action(方法)都調用了RedirectToAction()方法,這個就重新定向了我們的URL連接到Index頁面,我們需要傳遞兩條跳消息到視圖顯示我們購物車的內容:購物車對象和URL連接,這也就是我們創建一個簡單模型的目的。在我們的Web項目的Models文件夾下創建CartIndexViewModel類,具體代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using SportsStore.Domain.Entities; namespace SportsStore.WebUI.Models { public class CartIndexViewModel { public Cart Cart { get; set; } public string ReturnUrl { get; set; } } }
既然我們的模型已經O了,接下來我就就可以實現CartControllr的Index,修改CartController具體代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using SportsStore.Domain.Abstract; using SportsStore.Domain.Entities; using SportsStore.WebUI.Models; namespace SportsStore.WebUI.Controllers { public class CartController : Controller { private IProductRepository repository; public CartController(IProductRepository repo) { this.repository = repo; } //添加購物車 public RedirectToRouteResult AddToCart(int productId, string returnUrl) { Product product = this.repository.Products.FirstOrDefault(h => h.ProductID == productId); if (product != null) { this.GetCart().AddItem(product, 1); } return this.RedirectToAction("Index", new { returnUrl }); } //移除商品 public RedirectToRouteResult RemoverFromCart(int productId, string returnUrl) { Product product = this.repository.Products.FirstOrDefault(h => h.ProductID == productId); if (productId != null) { this.GetCart().RemoveLine(product); } return this.RedirectToAction("Index", new { returnUrl }); } private Cart GetCart() { Cart cart = (Cart)Session["Cart"]; if (cart == null) { cart = new Cart(); this.Session["Cart"] = cart; } return cart; } public ViewResult Index(string returnUrl) { return this.View(new CartIndexViewModel { Cart = this.GetCart(), ReturnUrl = returnUrl }); } } }
接下來就是我們創建顯示購物車內容的視圖,右鍵我們的Index Action(方法)選擇添加視圖,選擇創建強類型視圖,並且選擇CartIndexViewModel模型類,具體如下圖2.
圖2.我們希望購物車希望購物車中的顯示物品的風格和其他頁面一致,所以們在這裏套用了_Layout.cshtml模版,創建好Index.cshtml修改如下:
@model SportsStore.WebUI.Models.CartIndexViewModel @{ ViewBag.Title = "Index"; } <h2>Your Cart</h2> <table width="90%" align="center"> <thead> <tr> <th align="center">Qunantity</th> <th align="left">Item</th> <th align="right">Price</th> <th align="right">Subtotal</th> </tr> </thead> <tbody> @foreach (var line in Model.Cart.Lines) { <tr> <td align="center">@line.Quantity</td> <td align="left">@line.Product.Name</td> <td align="right">@line.Product.Price.ToString("c")</td> <td align="right">@((line.Quantity*line.Product.Price).ToString("c"))</td> </tr> } </tbody> <tfoot> <tr> <td colspan="3">Total:</td> <td align="right">@Model.Cart.ComputeTotalValue().ToString("C")</td> </tr> </tfoot> </table> <p align="center" class="actionButtons"><a href="@Model.ReturnUrl">Continue shopping</a></p>
這個視圖看起來和之前相比,可能就稍微複雜一點。列舉了購物的行和新添加的行的HTML表單,隨着每一行的總成本構成購物車的總成本。我們現在一定有一個基本的購物車了,當我們在商品頁面添加商品到購物車,我們的購物車基本就會算出商品的總價格,如下圖3-圖4.
圖3.
圖4.接下來我們需要做更多的東西,比如用戶可以刪掉購物車裏不想要的商品,還有購物車的結算等等。
使用模型綁定:Asp.Net MVC框架使用一種稱爲模型綁定的機制將來自HTTP請求創建爲C#對象,這樣就能夠將它們作爲參數傳遞給Controller(控制器)的Action方法。這也是MVC處理Form表單的原理。譬如MVC框架尋找Action(方法)的參數作爲目標,使用模型綁定從Input元素獲取值,並會將這些值轉化爲Action方法裏面參數對應的類型以及相同的參數名稱。我們喜歡使用會話狀態(Session)特徵在購物車中的控制器(controller)來存儲和管理我們的購物車對象,我們將創建一個自定義的綁定來獲取包含在Session裏面的Cart對象,MVC框架會創建Cart對象並作爲參數傳遞給CartController的Action方法。接下來我們將創建一個自定義的模型綁定器,在我們web項目的裏新建一個文件夾(Binders)在該文件裏創建一個類文件,叫CartModelBinder,它的代碼具體如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using SportsStore.Domain.Entities; namespace SportsStore.WebUI.Binders { public class CartModelBinder : IModelBinder { private const string sessionKey = "Cart"; public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { //從session中取出購物車信息 Cart cart = (Cart)controllerContext.HttpContext.Session[sessionKey]; //如果從session中取出購物車爲空創建一個購物車 if (cart == null) { cart = new Cart(); controllerContext.HttpContext.Session[sessionKey] = cart; } //返回購物車對象 return cart; } } }
IModeBinder接口裏面定義一個方法:BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext).提供的兩個參數使我們創建域模型對象成爲可能。ControllerContext providers能夠訪問Controller類的所有信息,包括了來自瀏覽器的詳細請求信息;ModelBindingContext提供我們要創建模型對象的信息和工具來簡化我們的操作。
其實我們只需要關注ControllerContext類,因爲我們需要用他裏面的Session這個東東。
我們需要告訴MVC框架,它可以使用我們的CartModelBinder類來創建購物車類的實例,所以我們需要修改Global.asax,具體操作如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; using SportsStore.WebUI.Infrastructure; using SportsStore.WebUI.Binders; using SportsStore.Domain.Entities; namespace SportsStore.WebUI { // 注意: 有關啓用 IIS6 或 IIS7 經典模式的說明, // 請訪問 http://go.microsoft.com/?LinkId=9394801 public class MvcApplication : System.Web.HttpApplication { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); } public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( null, "", //只匹配空的URL new { controller = "Product", action = "List", Category = (string)null, page = 1 } ); routes.MapRoute(null, "Page{page}", //匹配像 /Page2,/page123,除了/PageXYZ new { controller = "Product", action = "List", category = (string)null }, new { page = @"\d+" }); //約束:頁面數值必須是數字 routes.MapRoute(null, "{category}", //匹配類別 new { controller = "Product", action = "List", page = 1 }); routes.MapRoute(null, "{category}/Page{page}", //配置 類別和分頁 new { controller = "Product", action = "List" }, new { page = @"\d+" }); //約束:頁面數值必須是數字 routes.MapRoute(null, "{controller}/{action}"); //添加一條路由 routes.MapRoute( null, "Page{page}", new { controller = "Product", action = "List" } ); routes.MapRoute( "Default", // 路由名稱 "{controller}/{action}/{id}", // 帶有參數的 URL new { controller = "Product", action = "List", id = UrlParameter.Optional } // 參數默認值 ); } protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); //註冊路由 ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory()); //註冊CartModelBinder類 ModelBinders.Binders.Add(typeof(Cart), new CartModelBinder()); } } }
注:上面紅色加粗的那句代碼就是註冊CartModelBinder類。我們現在可以修改我們的CartController類使用我們的模型綁定來代替之前的GetCart()方法,所以我們需要修改CartController如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using SportsStore.Domain.Abstract; using SportsStore.Domain.Entities; using SportsStore.WebUI.Models; namespace SportsStore.WebUI.Controllers { public class CartController : Controller { private IProductRepository repository; public CartController(IProductRepository repo) { this.repository = repo; } //添加購物車 public RedirectToRouteResult AddToCart(Cart cart, int productId, string returnUrl) { Product product = this.repository.Products.FirstOrDefault(h => h.ProductID == productId); if (product != null) { cart.AddItem(product, 1); } return this.RedirectToAction("Index", new { returnUrl }); } //移除商品 public RedirectToRouteResult RemoveFromCart(Cart cart, int productId, string returnUrl) { Product product = this.repository.Products.FirstOrDefault(h => h.ProductID == productId); if (product != null) { cart.RemoveLine(product); } return this.RedirectToAction("Index", new { cart = cart, returnUrl = returnUrl }); } private Cart GetCart() { Cart cart = (Cart)Session["Cart"]; if (cart == null) { cart = new Cart(); this.Session["Cart"] = cart; } return cart; } public ViewResult Index(Cart cart, string returnUrl) { return this.View(new CartIndexViewModel { Cart = cart, ReturnUrl = returnUrl }); } } }
AddToCart方法被調用時,它開始尋找Action方法的參數,它將會在可用的綁定列表裏面尋找,試圖找到一個能夠創建每一個參數類型的實例。我們自定義的綁定會被用來創建Cart對象,並且MVC是通過Session功能來實現的。在我們自定義的綁定和默認綁定之間,mvc能夠創建必備參數的集合來調用action方法。正是如此才允許我們重構Controller以至於我們不知道在請求被接收時Cart對象是怎樣被創立的。
使用自定義模型綁定有一下幾個好處:
- 我們將創建購物車的邏輯從Controller裏面分離出來,這樣就允許我們可以改變存儲Cart對象的方式而不必去更改Controller。
- 任何Controller想使用Cart對象,只需要在其Action方法裏面聲明一個Cart參數即可.
- 良好的但願測試
接下來,完善我們的購物車。給我們的購物車完善2個功能:1.允許用戶刪除購物商品項 2.在頁面頂端增加一個顯示購物車詳情(迷你購物車)
首先我們允許用戶刪除購物車內自己不想要或者不喜歡的商品,我們要做的就是在購物車的每行商品後暴露給用戶一個刪除按鈕,這樣就方便用戶刪除掉自己不喜歡的東西。修改我們web項目的Views/Cart/Index.cshtml頁面內容如下:
@model SportsStore.WebUI.Models.CartIndexViewModel @{ ViewBag.Title = "Cart Index"; } <h2>Your Cart</h2> <table width="90%" align="center"> <thead> <tr> <th align="center">Qunantity</th> <th align="left">Item</th> <th align="right">Price</th> <th align="right">Subtotal</th> </tr> </thead> <tbody> @foreach (var line in Model.Cart.Lines) { <tr> <td align="center">@line.Quantity</td> <td align="left">@line.Product.Name</td> <td align="right">@line.Product.Price.ToString("c")</td> <td align="right">@((line.Quantity*line.Product.Price).ToString("c"))</td> <td> @using (Html.BeginForm("RemoveFromCart","Cart")) { @Html.Hidden("ProductId",line.Product.ProductID) @Html.HiddenFor(h=>h.ReturnUrl) <input class="actionButtons" type="submit" value="Remove" /> } </td> </tr> } </tbody> <tfoot> <tr> <td colspan="3">Total:</td> <td align="right">@Model.Cart.ComputeTotalValue().ToString("C")</td> </tr> </tfoot> </table> <p align="center" class="actionButtons"><a href="@Model.ReturnUrl">Continue shopping</a></p>
搞玩刪除按鈕,運行一下我們的web應用程序,結果如下圖5所示。
圖5.可以看出我們的刪除購物車的商品項也已經是OK了,接下來完善我們的第二項功能,在頁面頂端增加一個顯示購物車詳情(迷你購物車)在我們CartController類裏添加這個功能的Action(執行方法),具體代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using SportsStore.Domain.Abstract; using SportsStore.Domain.Entities; using SportsStore.WebUI.Models; namespace SportsStore.WebUI.Controllers { public class CartController : Controller { private IProductRepository repository; public CartController(IProductRepository repo) { this.repository = repo; } //添加購物車 public RedirectToRouteResult AddToCart(Cart cart, int productId, string returnUrl) { Product product = this.repository.Products.FirstOrDefault(h => h.ProductID == productId); if (product != null) { cart.AddItem(product, 1); } return this.RedirectToAction("Index", new { returnUrl }); } //移除商品 public RedirectToRouteResult RemoveFromCart(Cart cart, int productId, string returnUrl) { Product product = this.repository.Products.FirstOrDefault(h => h.ProductID == productId); if (product != null) { cart.RemoveLine(product); } return this.RedirectToAction("Index", new { cart = cart, returnUrl = returnUrl }); } private Cart GetCart() { Cart cart = (Cart)Session["Cart"]; if (cart == null) { cart = new Cart(); this.Session["Cart"] = cart; } return cart; } public ViewResult Index(Cart cart, string returnUrl) { return this.View(new CartIndexViewModel { Cart = cart, ReturnUrl = returnUrl }); } //簡易的購物車 public ViewResult Summary(Cart cart) { return this.View(cart); } } }
上面代碼紅色加粗的部分就是簡易購物車的執行方法(Action),這個方法相當簡單就是獲取當前購物車的對象呈現出來渲染頁面,我們需要簡易的局部視圖來搞定他。在Symmary這個Action上右鍵,選擇添加視圖,如下圖5所示.
圖5.這裏還是選擇的強類型視圖。創建好Summary局部視圖後,修改他的代碼如下所示:
@model SportsStore.Domain.Entities.Cart @{ this.Layout = null; } <div id="cart"> <span class="caption"> <b>Your cart:</b> @Model.Lines.Sum(h=>h.Quantity) item(s), @Model.ComputeTotalValue().ToString("c") </span> @Html.ActionLink("Checkout", "Index", "Cart", new { returnUrl = this.Request.Url.PathAndQuery }, null) </div>
OK,搞完這個頂端的簡易購物車,我們一定定義了返回這個局部視圖的方法,而且我們需要在每個頁面都呈現它,所以就要在我們的模版(_Layout.cshtml)裏給它分配一塊地方,具體修改我們的模版如下:
<!DOCTYPE html> <html> <head> <title>@ViewBag.Title</title> <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script> </head> <body> <div id="header"> @{Html.RenderAction("Summary", "Cart");} <div class="title">SPOPTS STORE</div> </div> <div id="categories">@{Html.RenderAction("Menu", "Nav");}</div> <div id="content"> @RenderBody() </div> </body> </html>
注:上面紅色加粗部分就是給我們的簡易購物車爭取到的一塊呈現的地方,注意這裏Html.RenderAction();後面的分號不能去掉,如忘記寫分號會編譯出錯。那現在運行一下我們的程序看看我們建議的購物車有沒有達到預期的目的,運行如下圖6所示。
圖6.說明我們的也已經實現了頂部的簡易購物車。購物裏的東西需要提交支付纔會生成訂單,所以接下來也就是我們這個簡易項目的最後一個階段訂單部分了,今天關於購物車就大致分享到這裏,後續繼續完善我們的項目