侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

一步步打造简单的MVC电商网站BooksStore(2)

php  /  管理员 发布于 8年前   193

一步步打造一个简单的 MVC 电商网站 - BooksStore(二)

本系列的 GitHub地址:https://github.com/liqingwen2015/Wen.BooksStore

《一步步打造一个简单的 MVC 电商网站 - BooksStore(一)》

《一步步打造一个简单的 MVC 电商网站 - BooksStore(二)》

《一步步打造一个简单的 MVC 电商网站 - BooksStore(三)》

《一步步打造一个简单的 MVC 电商网站 - BooksStore(四)》

简介

上一次我们尝试了:创建项目架构、创建域模型实体、创建单元测试、创建控制器与视图、创建分页和加入样式,而这一节我们会完成两个功能,分类导航与购物车。

主要功能与知识点如下:

分类、产品浏览、购物车、结算、CRUD(增删改查) 管理、发邮件、分页、模型绑定、认证过滤器和单元测试等(预计剩余两篇,预计明天(因为周六不放假)和周三(因为周二不上班)发布)。

【备注】项目使用 VS2015 + C#6 进行开发,有问题请发表在留言区哦,还有,页面长得比较丑,请见谅。

目录

添加分类导航

加入购物车

创建一个分部视图 Partial View

一、添加分类导航

上一次我们把网页划分成了三个模块,其中左侧栏的部分尚未完成,左侧栏拥有将书籍分类展示的功能。

图 1

1.回到之前的BookDetailsViewModels 视图模型,我们额外再添加一个新的属性用作分类(CurrentCategory):

/// <summary> /// 书籍详情视图模型 /// </summary> public class BookDetailsViewModels : PagingInfo { public IEnumerable<Book> Books { get; set; } /// <summary> /// 当前分类 /// </summary> public string CurrentCategory { get; set; } }

2.修改完视图模型,现在就应该修改对应的 BookController 中的Details 方法

/// <summary> /// 详情 /// </summary> /// <param name="category">分类</param> /// <param name="pageIndex">页码</param> /// <returns></returns> public ActionResult Details(string category, int pageIndex = 1) {  var model = new BookDetailsViewModels  {  Books =   _bookRepository.Books.Where(x => category == null || x.Category == category)   .OrderBy(x => x.Id)   .Skip((pageIndex - 1) * PageSize)   .Take(PageSize),  CurrentCategory = category,  PageSize = PageSize,  PageIndex = pageIndex,  TotalItems = _bookRepository.Books.Count(x => category == null || x.Category == category)  };  return View(model); }

BookController.cs 

namespace Wen.BooksStore.WebUI.Controllers{ public class BookController : Controller { private readonly IBookRepository _bookRepository; public int PageSize = 5; public BookController(IBookRepository bookRepository) {  _bookRepository = bookRepository; } /// <summary> /// 详情 /// </summary> /// <param name="category">分类</param> /// <param name="pageIndex">页码</param> /// <returns></returns> public ActionResult Details(string category, int pageIndex = 1) {  var model = new BookDetailsViewModels  {  Books =   _bookRepository.Books.Where(x => category == null || x.Category == category)   .OrderBy(x => x.Id)   .Skip((pageIndex - 1) * PageSize)   .Take(PageSize),  CurrentCategory = category,  PageSize = PageSize,  PageIndex = pageIndex,  TotalItems = _bookRepository.Books.Count(x => category == null || x.Category == category)  };  return View(model); } }}

参数增加了一个 category,用于获取分类的字符串,对应 Books 中的属性的赋值语句改为_bookRepository.Books.Where(x => category == null || x.Category == category),这里的 Lambda 表达式x => category == null || x.Category ==category 的意思是,分类字符串为空就取库中所有的 Book 实体,不为空时根据分类进行对集合进行筛选过滤。

还要对属性 CurrentCategory 进行赋值。

别忘了,因为分页是根据 TotalItems 属性进行的,所以还要修改地方_bookRepository.Books.Count(x => category == null || x.Category == category),通过 LINQ 统计不同分类情况的个数。

3.该控制器对应的 Details.cshtml 中的分页辅助器也需要修改,添加新的路由参数:

<div class="pager"> @Html.PageLinks(Model, x => Url.Action("Details", new { pageIndex = x, category = Model.CurrentCategory }))</div>

Details.cshtml

@model Wen.BooksStore.WebUI.Models.BookDetailsViewModels@{ ViewBag.Title = "Books";}@foreach (var item in Model.Books){ <div class="item"> <h3>@item.Name</h3> @item.Description <h4>@item.Price.ToString("C")</h4> <br /> <hr /> </div>}<div class="pager"> @Html.PageLinks(Model, x => Url.Action("Details", new { pageIndex = x, category = Model.CurrentCategory }))</div>

4.路由区域也应当修改一下

RouteConfig.cs

public static void RegisterRoutes(RouteCollection routes) {  routes.IgnoreRoute("{resource}.axd/{*pathInfo}");  routes.MapRoute(  name: "Default",  url: "{controller}/{action}",  defaults: new { controller = "Book", action = "Details" }  );  routes.MapRoute(  name: null,  url: "{controller}/{action}/{category}",  defaults: new { controller = "Book", action = "Details" }  );  routes.MapRoute(  name: null,  url: "{controller}/{action}/{category}/{pageIndex}",  defaults: new { controller = "Book", action = "Details", pageIndex = UrlParameter.Optional }  ); }

5.现在新建一个名为 NavController 的控制器,并添加一个名为Sidebar 的方法,专门用于渲染左侧边栏。

不过返回的 View 视图类型变成 PartialView 分部视图类型:

public PartialViewResult Sidebar(string category = null) {  var categories = _bookRepository.Books.Select(x => x.Category).Distinct().OrderBy(x => x);  return PartialView(categories); }

在方法体在右键,添加一个视图,勾上创建分部视图。

Sidebar.cshtml 修改为:

@model IEnumerable<string><ul> <li>@Html.ActionLink("所有分类", "Details", "Book")</li> @foreach (var item in Model) { <li>@Html.RouteLink(item, new { controller = "Book", action = "Details", category = item, pageIndex = 1 }, new { @class = item == ViewBag.CurrentCategory ? "selected" : null })</li> }</ul>

MVC 框架具有一种叫作“子动作(Child Action)”的概念,可以适用于重用导航控件之类的东西,使用类似 RenderAction() 的方法,在当前的视图中输出指定的动作方法。

因为需要在父视图中呈现另一个 Action 中的分部视图,所以原来的_Layout.cshtml布局页修改如下:

现在,启动的结果应该和图 1 是一样的,尝试点击左侧边栏的分类,观察主区域的变化情况。

二、加入购物车

图 2

界面的大体功能如图 2,在每本图书的区域新增一个链接(添加到购物车),会跳转到一个新的页面,显示购物车的详细信息 - 购物清单,也可以通过“结算”链接跳转到一个新的页面。

购物车是应用程序业务域的一部分,因此,购物车实体应该为域模型。

1.添加两个类:

Cart.cs 有添加、移除、清空和统计功能:

/// <summary> /// 购物车 /// </summary> public class Cart { private readonly List<CartItem> _cartItems = new List<CartItem>(); /// <summary> /// 获取购物车的所有项目 /// </summary> public IList<CartItem> GetCartItems => _cartItems; /// <summary> /// 添加书模型 /// </summary> /// <param name="book"></param> /// <param name="quantity"></param> public void AddBook(Book book, int quantity) {  if (_cartItems.Count == 0)  {  _cartItems.Add(new CartItem() { Book = book, Quantity = quantity });  return;  }  var model = _cartItems.FirstOrDefault(x => x.Book.Id == book.Id);  if (model == null)  {  _cartItems.Add(new CartItem() { Book = book, Quantity = quantity });  return;  }  model.Quantity += quantity; } /// <summary> /// 移除书模型 /// </summary> /// <param name="book"></param> public void RemoveBook(Book book) {  var model = _cartItems.FirstOrDefault(x => x.Book.Id == book.Id);  if (model == null)  {  return;  }  _cartItems.RemoveAll(x => x.Book.Id == book.Id); } /// <summary> /// 清空购物车 /// </summary> public void Clear() {  _cartItems.Clear(); } /// <summary> /// 统计总额 /// </summary> /// <returns></returns> public decimal ComputeTotalValue() {  return _cartItems.Sum(x => x.Book.Price * x.Quantity); } }

CartItem.cs 表示购物车中的每一项:

/// <summary> /// 购物车项 /// </summary> public class CartItem { /// <summary> /// 书 /// </summary> public Book Book { get; set; } /// <summary> /// 数量 /// </summary> public int Quantity { get; set; } }

2.修改一下之前的 Details.cshtml,增加“添加到购物车”的按钮:

@model Wen.BooksStore.WebUI.Models.BookDetailsViewModels@{ ViewBag.Title = "Books";}@foreach (var item in Model.Books){ <div class="item"> <h3>@item.Name</h3> @item.Description <h4>@item.Price.ToString("C")</h4> @using (Html.BeginForm("AddToCart", "Cart")) {  var id = item.Id;  @Html.HiddenFor(x => id);  @Html.Hidden("returnUrl", Request.Url.PathAndQuery)  <input type="submit" value="+ 添加到购物车" /> } <br /> <hr /> </div>}<div class="pager"> @Html.PageLinks(Model, x => Url.Action("Details", new { pageIndex = x, category = Model.CurrentCategory }))</div>

【备注】@Html.BeginForm() 方法默认会创建一个 Post 请求方法的表单,为什么不直接使用 Get 请求呢,HTTP 规范要求,会引起数据变化时不要使用 Get 请求,将产品添加到一个购物车明显会出现新的数据变化,所以,这种情形不应该使用 Get 请求,直接显示页面或者列表数据,这种请求才应该使用 Get。

3.先修改下 css 中的样式

body {}#header, #content, #sideBar { display: block;}#header { background-color: green; border-bottom: 2px solid #111; color: White;}#header, .title { font-size: 1.5em; padding: .5em;}#sideBar { float: left; width: 8em; padding: .3em;}#content { border-left: 2px solid gray; margin-left: 10em; padding: 1em;}.pager { text-align: right; padding: .5em 0 0 0; margin-top: 1em;} .pager A { font-size: 1.1em; color: #666; padding: 0 .4em 0 .4em; } .pager A:hover {  background-color: Silver; } .pager A.selected {  background-color: #353535;  color: White; }.item input { float: right; color: White; background-color: green;}.table { width: 100%; padding: 0; margin: 0;} .table th { font: bold 12px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif; color: #4f6b72; border-right: 1px solid #C1DAD7; border-bottom: 1px solid #C1DAD7; border-top: 1px solid #C1DAD7; letter-spacing: 2px; text-transform: uppercase; text-align: left; padding: 6px 6px 6px 12px; background: #CAE8EA no-repeat; } .table td { border-right: 1px solid #C1DAD7; border-bottom: 1px solid #C1DAD7; background: #fff; font-size: 14px; padding: 6px 6px 6px 12px; color: #4f6b72; } .table td.alt {  background: #F5FAFA;  color: #797268; } .table th.spec, td.spec { border-left: 1px solid #C1DAD7; }

4.再添加一个 CartController

/// <summary> /// 购物车 /// </summary> public class CartController : Controller { private readonly IBookRepository _bookRepository; public CartController(IBookRepository bookRepository) {  _bookRepository = bookRepository; } /// <summary> /// 首页 /// </summary> /// <param name="returnUrl"></param> /// <returns></returns> public ViewResult Index(string returnUrl) {  return View(new CartIndexViewModel()  {  Cart = GetCart(),  ReturnUrl = returnUrl  }); } /// <summary> /// 添加到购物车 /// </summary> /// <param name="id"></param> /// <param name="returnUrl"></param> /// <returns></returns> public RedirectToRouteResult AddToCart(int id, string returnUrl) {  var book = _bookRepository.Books.FirstOrDefault(x => x.Id == id);  if (book != null)  {  GetCart().AddBook(book, 1);  }  return RedirectToAction("Index", new { returnUrl }); } /// <summary> /// 从购物车移除 /// </summary> /// <param name="id"></param> /// <param name="returnUrl"></param> /// <returns></returns> public RedirectToRouteResult RemoveFromCart(int id, string returnUrl) {  var book = _bookRepository.Books.FirstOrDefault(x => x.Id == id);  if (book != null)  {  GetCart().RemoveBook(book);  }  return RedirectToAction("Index", new { returnUrl }); } /// <summary> /// 获取购物车 /// </summary> /// <returns></returns> private Cart GetCart() {  var cart = (Cart)Session["Cart"];  if (cart != null) return cart;  cart = new Cart();  Session["Cart"] = cart;  return cart; } }

【备注】这里的购物车是通过 Session 会话状态进行保存用户的 Cart 对象。当会话过期(典型的情况是用户很长时间没有对服务器发起任何请求),与该会话关联的数据就会被删除,这就意味着不需要对 Cart 对象进行生命周期的管理。

【备注】RedirectToAction() 方法:将一个 HTTP 重定向的指令发给客户端浏览器,要求浏览器请求一个新的 Url。

5.在 Index 方法中选择右键新建视图,专门用于显示购物清单:

Index.cshtml 中的代码:

@model Wen.BooksStore.WebUI.Models.CartIndexViewModel<h2>我的购物车</h2><table class="table"> <thead> <tr>  <th>书名</th>  <th>价格</th>  <th>数量</th>  <th>总计</th> </tr> </thead> <tbody> @foreach (var item in Model.Cart.GetCartItems) {  <tr>  <td>@item.Book.Name</td>  <td>@item.Book.Price</td>  <td>@item.Quantity</td>  <td>@((item.Book.Price * item.Quantity).ToString("C"))</td>  </tr> } <tr>  <td> </td>  <td> </td>  <td>总计:</td>  <td>@Model.Cart.ComputeTotalValue().ToString("C")</td> </tr> </tbody></table><p> <a href="https:/article/@Model.ReturnUrl">继续购物</a></p>

我想,这一定是一个令人激动的时刻,因为我们已经完成了这个基本的添加到购物车的功能。

三、创建一个分部视图 Partial View

分部视图,是嵌入在另一个视图中的一个内容片段,并且可以跨视图重用,这有助于减少重复,尤其需要在多个地方需要重复使用相同的数据时。

在 Shared 内部新建一个名为_BookSummary.cshtml 的视图,并且把之前Details.cshtml 的代码进行整理。

修改后的两个视图:

Details.cshtml

@model Wen.BooksStore.WebUI.Models.BookDetailsViewModels@{ ViewBag.Title = "Books";}@foreach (var item in Model.Books){ Html.RenderPartial("_BookSummary", item);}<div class="pager"> @Html.PageLinks(Model, x => Url.Action("Details", new { pageIndex = x, category = Model.CurrentCategory }))</div>

_BookSummary.cshtml

@model Wen.BooksStore.Domain.Entities.Book<div class="item"> <h3>@Model.Name</h3> @Model.Description <h4>@Model.Price.ToString("C")</h4> @using (Html.BeginForm("AddToCart", "Cart")) { var id = Model.Id; @Html.HiddenFor(x => id); @Html.Hidden("returnUrl", Request.Url.PathAndQuery) <input type="submit" value="+ 添加到购物车" /> } <br /> <hr /></div>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


  • 上一条:
    一步步打造简单的MVC电商网站BooksStore(3)
    下一条:
    一步步打造简单的MVC电商网站BooksStore(1)
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • 用Time Warden监控PHP中的代码处理时间(0个评论)
    • 在PHP中使用array_pop + yield实现读取超大型目录功能示例(0个评论)
    • Property Hooks RFC在PHP 8.4中越来越接近现实(0个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-06
    • 2017-07
    • 2017-08
    • 2017-09
    • 2017-11
    • 2017-12
    • 2018-01
    • 2018-02
    • 2018-03
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-09
    • 2021-02
    • 2021-03
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    • 2024-05
    • 2024-06
    • 2024-07
    • 2024-09
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客