摘要:
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 }
该控制器的一些解释:
添加到购物车按钮
修改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>
添加购物车详细信息视图
在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>
运行程序,得到运行结果。
这里我选择了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 }
有了模板绑定方法后,还需要将模板方法在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购物车
原文:https://www.cnblogs.com/uncle_danny/p/9052938.html