欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-11-30 08:33:04
一步步打造一个简单的 mvc 电商网站 - booksstore(二) 本系列的 github地址:https://github.com/liqingwen2015/we...

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

本系列的 github地址:https://github.com/liqingwen2015/wen.booksstore

一步步打造一个简单的 mvc 电商网站 - booksstore(一)

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

一步步打造一个简单的 mvc 电商网站 - booksstore(三)

一步步打造一个简单的 mvc 电商网站 - booksstore(四)

简介

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

主要功能与知识点如下:

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

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

目录

添加分类导航

加入购物车

创建一个分部视图 partial view

一、添加分类导航

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

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

图 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 方法

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

/// <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 的方法,专门用于渲染左侧边栏。

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

不过返回的 view 视图类型变成 partialview 分部视图类型:

public partialviewresult sidebar(string category = null)
 {
  var categories = _bookrepository.books.select(x => x.category).distinct().orderby(x => x);
  return partialview(categories);
 }

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

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

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布局页修改如下:

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

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

二、加入购物车

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

图 2

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

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

一步步打造简单的MVC电商网站BooksStore(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

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

/// <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 方法中选择右键新建视图,专门用于显示购物清单:

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

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>

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

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

三、创建一个分部视图 partial view

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

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

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

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

修改后的两个视图:

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>

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