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

一步步打造一个简单的 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="@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>

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

(0)

相关推荐

  • MVC4制作网站教程第四章 浏览栏目4.2

    序 一.用户 二.用户组 三.栏目 3.1添加栏目 3.2浏览栏目 浏览栏目这块做个一个树形列表,添加栏目的左侧部分只写了句"左侧列表"就是指这个树形列表,等我们写完替换一下就可以了. 先在[CategoryController]里面添加[ManagePartialTree]action,这里的Partial用来说明是分部视图 /// <summary> /// 栏目列表局部树视图 /// </summary> /// <returns></r

  • MVC+EasyUI+三层新闻网站建立 主页布局的方法(五)

    MVC新闻网站建立,实现主页布局. 首先建立Home控制器,然后再建立Index视图. 和前面登录页面一样也需要引入几个文件. 接着就是找到你下载的easyui文件夹里面的demo里面的layout里面的full 浏览器运行后右键查看代码把里面的body里面的代码复制到Index视图的body里面去.没找到的可以直接复制下面的代码. <body class="easyui-layout"> <div data-options="region:'north',

  • MVC+EasyUI+三层新闻网站建立 tabs标签制作方法(六)

    MVC新闻网站建立,完成tabs标签的制作. 首先对 Center 进行一个简单的布局 <!--------------中间布局开始----------------> <div data-options="region:'center',title:'Center'" > <div class="easyui-tabs" style="width:700px;height:250px" fit="true&

  • MVC4制作网站教程第四章 添加栏目4.1

    好几天没时间写了.今天有写时间在学一点. 今天状态也不是很好,晕晕沉沉的写吧. 序 一.用户 二.用户组 三.栏目 3.1添加栏目 首先添加[CategoryController]控制器, 那么我想我的视图里,首先显示的应该是栏目类型,这里应该是一个下拉框,用户可以选择"一般栏目","单页栏目","外部链接".那么首先应该在[CategoryController]添加一个属性,用来返回栏目类型列表. #region Attribute publi

  • MVC+EasyUI+三层新闻网站建立 后台登录界面的搭建(二)

    新闻网站建立,后台登录界面的搭建 首先我们在Controllers里面新添加一个控制器就叫LoginController,右键点击Controllers添加控制器就可以了(注意后面一定是Controller结尾,这是一种约定) 其次:右键点击index建立Index视图.(不选择使用母版页) 现在我们就可以在Index视图中设计我们的登录页面了. 这里需要我们引入几个Css样式和JS文件 简单的进行登录界面的布局 <html> <head> <meta name="

  • MVC4制作网站教程第四章 更新栏目4.3

    序  一.用户  二.用户组  三.栏目 3.1添加栏目 3.2浏览栏目  3.3更新栏目 上次在树形列表里面点击栏目名称后跳转到详细信息页面~/Category/ManageDetails/id.在详细页面里点修改,来完成栏目资料修改. 先打开[CategoryController]添加[ManageDetails(int id)]action /// <summary> /// 栏目详细资料 /// </summary> /// <param name="id&

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

    一步步打造一个简单的 MVC 电商网站 - BooksStore(一) 本系列的 GitHub地址:https://github.com/liqingwen2015/Wen.BooksStore <一步步打造一个简单的 MVC 电商网站 - BooksStore(一)> <一步步打造一个简单的 MVC 电商网站 - BooksStore(二)> <一步步打造一个简单的 MVC 电商网站 - BooksStore(三)> <一步步打造一个简单的 MVC 电商网站 -

  • MVC+EasyUI+三层新闻网站建立 建站准备工作(一)

    这一次的项目是建立一个小型的新闻发布网站,所以就不需要用一些高大上的框架.三层+MVC+easyUI就足够了. 首先:搭建起项目框架 其次:到easyui官网去下载easyUI 我这里用的是我在很久的项目中用过的版本jquery-easyui-1.5.2 下载链接:http://www.jeasyui.com/download/list.php 最后就是把easyui引入到项目中去. 现在Content文件夹下建立一个叫EasyUi的文件夹,把需要用到的文件复制到其中,具体如下图所示: 以上就是

  • MVC+EasyUI+三层新闻网站建立 验证码生成(三)

    我们在项目中的NewWeb.Common类库里面建立一个类用来存放生成验证码的代码. 这里我类的名字叫 ValidateCode 生成验证码(ValidateCode)类的代码: using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; using Syste

  • MVC+EasyUI+三层新闻网站建立 实现登录功能(四)

    MVC新闻网站建立,实现登录功能 首先在数据库中建立一张UserInfo表. 注:以下讲的这些可以用动软代码生成器直接生成,但是对于新手来说还是动手敲一下的好,了解以下实现的过程. 然后在Model中建立UserInfo的实体层. public class UserInfo { public int Id { get; set; } public string UserName { get; set; } public string UserPwd { get; set; } public st

随机推荐