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

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

  《一步步打造一个简单的 MVC 电商网站 - BooksStore(一)》(发布时间:2017-03-30 )

一步步打造一个简单的 MVC 电商网站 - BooksStore(二)》(发布时间:2017-03-31)

  《一步步打造一个简单的 MVC 电商网站 - BooksStore(三)》(发布时间:2017-04-01)

  《一步步打造一个简单的 MVC 电商网站 - BooksStore(四)》(发布时间:2017-04-05)

简介

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

  主要功能与知识点如下:

    分类、产品浏览、购物车、结算、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 = )
{
var model = new BookDetailsViewModels
{
Books =
_bookRepository.Books.Where(x => category == null || x.Category == category)
.OrderBy(x => x.Id)
.Skip((pageIndex - ) * PageSize)
.Take(PageSize),
CurrentCategory = category,
PageSize = PageSize,
PageIndex = pageIndex,
TotalItems = _bookRepository.Books.Count(x => category == null || x.Category == category)
}; return View(model);
}
namespace Wen.BooksStore.WebUI.Controllers
{
public class BookController : Controller
{
private readonly IBookRepository _bookRepository;
public int PageSize = ; 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 = )
{
var model = new BookDetailsViewModels
{
Books =
_bookRepository.Books.Where(x => category == null || x.Category == category)
.OrderBy(x => x.Id)
.Skip((pageIndex - ) * PageSize)
.Take(PageSize),
CurrentCategory = category,
PageSize = PageSize,
PageIndex = pageIndex,
TotalItems = _bookRepository.Books.Count(x => category == null || x.Category == category)
}; return View(model);
}
}
}

BookController.cs

  

  参数增加了一个 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>
 @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>

Details.cshtml

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

        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 }
);
}

RouteConfig.cs

  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 = }, 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 == )
{
_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:;
margin:;
} .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;
}

Site.css

  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, );
} 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>

【博主】反骨仔

【原文】http://www.cnblogs.com/liqingwen/p/6647538.html

【参考】《精通 ASP.NET MVC ...》

[.NET] 一步步打造一个简单的 MVC 电商网站 - BooksStore(二)的更多相关文章

  1. [.NET] 一步步打造一个简单的 MVC 电商网站 - BooksStore(三)

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

  2. [.NET] 一步步打造一个简单的 MVC 电商网站 - BooksStore(四)

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

  3. [.NET] 一步步打造一个简单的 MVC 电商网站 - BooksStore(一)

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

  4. [.NET] 一步步打造一个简单的 MVC 电商网站 - BooksStore(一) (转)

    http://www.cnblogs.com/liqingwen/p/6640861.html 一步步打造一个简单的 MVC 电商网站 - BooksStore(一) 本系列的 GitHub地址:ht ...

  5. [.NET] 一步步打造一个简单的 MVC 网站 - BooksStore(一)

    一步步打造一个简单的 MVC 网站 - BooksStore(一) 本系列的 GitHub地址:https://github.com/liqingwen2015/Wen.BooksStore 简介 主 ...

  6. 如何一步一步用DDD设计一个电商网站(二)—— 项目架构

    阅读目录 前言 六边形架构 终于开始建项目了 DDD中的3个臭皮匠 CQRS(Command Query Responsibility Segregation) 结语 一.前言 上一篇我们讲了DDD的 ...

  7. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(二)数据库初始化、基本登录页面以及授权逻辑的建立

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  8. 如何一步一步用DDD设计一个电商网站(九)—— 小心陷入值对象持久化的坑

    阅读目录 前言 场景1的思考 场景2的思考 避坑方式 实践 结语 一.前言 在上一篇中(如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成),有一行注释的代码: public interfa ...

  9. 如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成

    阅读目录 前言 建模 实现 结语 一.前言 前面几篇已经实现了一个基本的购买+售价计算的过程,这次再让售价丰满一些,增加一个会员价的概念.会员价在现在的主流电商中,是一个不大常见的模式,其带来的问题是 ...

随机推荐

  1. Swift 面向对象解析(一)

    面向对象总体概括: Swift 不仅能够面向过程编程,也能够面向对象编程(OOP).面向对象其实就是“以对象为核心”,把我们的客观世界想着是由一个个对象组成的,面向对象编程则为对象提供了属性和方法,属 ...

  2. bootstrap_下拉菜单+头部

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. React 国际化

    前言 React 做国际化,我推荐使用 React-intl , 这个库提供了 React 组件和Api两种方式来格式化日期,数字和字符串等.知道这个库了,那让我们开始使用它 组件用法 为了和Reac ...

  4. HttpClient构造文件上传

    在项目中我们有时候需要使用到其他第三方的api,而有些api要求我们上传文件,search一下,下面将结果记录一下喽! 含义 ENCTYPE="multipart/form-data&quo ...

  5. java学习书籍推荐

    1. Java 语言基础 谈到Java 语言基础学习的书籍,大家肯定会推荐Bruce Eckel 的<Thinking in Java >.它是一本写的相当深刻的技术书籍,Java 语言基 ...

  6. oracle系列笔记(1)---查询数据

    查询数据 1. 查询(select .. form ..)    (1)普通查询 select * from employees --代表查询employees表中所有数据 select last_n ...

  7. [Hadoop] - Cannot run program "cmake"

    在编译hadoop的过程中,遇到缺少cmake命令的异常,异常信息为:Cannot run program "cmake" (in directory "/opt/wor ...

  8. Kafka概述与设计原理

    kafka是一种高吞吐量的分布式发布订阅消息系统,有如下特性: 1. 通过O(1)的磁盘数据结构提供消息的持久化,这种结构对于即使数以TB的消息存储也能够保持长时间的稳定性能. 2 .高吞吐量:即使是 ...

  9. 初探Lambda表达式/Java多核编程【4】Lambda变量捕获

    这周开学,上了两天感觉课好多,学校现在还停水,宿舍网络也还没通,简直爆炸,感觉能静下心看书的时间越来越少了...寒假还有些看过书之后的存货,现在写一点发出来.加上假期两个月左右都过去了书才看了1/7都 ...

  10. php 实现购物车功能,以大苹果购物网为例,上图上代码。。。。

    首先是几个简单的登录页面 <body> <form action="chuli.php" method="post"> <div ...