一步步打造一个简单的 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 进行开发,有问题请发表在留言区哦,还有,页面长得比较丑,请见谅。

目录

  • 完成购物车
  • 订单结算

一、完成购物车

  上一节其实已经完成了移除购物车和清空购物车的方法,只是尚未将可供用户操作的按钮放在页面区域。除了增加这两个按钮,也会在页面顶部的位置增加购物车的摘要(用于显示用户的购物总额)。

  下面是上一节已经写好的 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;
}
}

CartController.cs

  1.加入移除书籍和清空购物车的功能

@model Wen.BooksStore.WebUI.Models.CartIndexViewModel

<h2>我的购物车</h2>

<table class="table">
<thead>
<tr>
<th>书名</th>
<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>
<td>
@using (Html.BeginForm("RemoveFromCart", "Cart"))
{
@Html.Hidden("id", item.Book.Id)
@Html.HiddenFor(x => x.ReturnUrl)
<input type="submit" value="- 移除" />
}
</td>
</tr>
}
<tr>
<td> </td>
<td> </td>
<td>总计:</td>
<td>@Model.Cart.ComputeTotalValue().ToString("C")</td>
<td>
@using (Html.BeginForm("Clear", "Cart"))
{
@Html.HiddenFor(x => x.ReturnUrl)
<input type="submit" value="清空购物车" />
}
</td>
</tr>
</tbody> </table>

Index.cshtml

  【备注】@Html.Hidden("id", item.Book.Id) 是用于生成隐藏的字段,如果直接使用 @Html.HiddenFor(),生成的 name 将会是 item.Book.Id ,将和 CartController 中 RemoveFromCart(int id, string return) 的参数不匹配。

  显示的效果如下:

  

  2.添加摘要:我们在购物车存放了许多东西,通过摘要,可以显示购物总额的缩略图,我们选择的位置在顶部右上角的一个比较显眼的位置进行显示它,当然,还需要有点击的跳转按钮方便显示所有的购物清单页面。

  继续在 CartController 下新增一个 Action,名为 Summary,返回值是一个分部视图:

        /// <summary>
/// 摘要
/// </summary>
/// <returns></returns>
public PartialViewResult Summary()
{
return PartialView(GetCart());
}

  

  对应的 Summary.cshtml

@model Wen.BooksStore.Domain.Entities.Cart

<div class="bookSummary">
你的购物车:@Model.ComputeTotalValue()
<span>@Html.ActionLink("结算", "Checkout", "Cart", new { retunUrl = Request.Url.PathAndQuery }, null)</span>
</div>

  对应的布局页 _Layout.cshtml 修改的地方为:

<!DOCTYPE html>

<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
<link href="~/Contents/Site.css" rel="stylesheet" />
</head>
<body>
<div id="header">
@{ Html.RenderAction("Summary", "Cart");}
<div class="title">图书商城</div>
</div>
<div id="sideBar">
@{ Html.RenderAction("Sidebar", "Nav"); }
</div>
<div id="content">
@RenderBody()
</div>
</body>
</html>

_Layout.cshtml

  添加了新的东西,css 也要进行修改:

body {
} #header, #content, #sideBar {
display: block;
} #header {
background-color: green;
border-bottom: 2px solid #;
color: White;
} #header, .title {
font-size: .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 ;
margin-top: 1em;
} .pager A {
font-size: .1em;
color: #;
padding: .4em .4em;
} .pager A:hover {
background-color: Silver;
} .pager A.selected {
background-color: #;
color: White;
} .item input {
float: right;
color: White;
background-color: green;
} .table {
width: %;
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: #;
} .table th.spec, td.spec {
border-left: 1px solid #C1DAD7;
} .bookSummary {
width: %;
float: right;
margin-top: 1.5%;
}

Site.css

二、订单结算

  购物完毕就是结算页面了,这里的订单结算并不涉及支付接口的调用,只是使用邮件的形式进行通知而已。

  这里,我设计结算的时候需要要求用户输入一些信息,如姓名、地址和邮箱等信息,在点击确定时我再将这些输入的信息与购物清单的信息从系统的邮箱发到你所输入的邮箱当中。一个比较直观的图:

  

  1.在 Entities 中添加一个域模型 Contact.cs 表示联系人的信息。

    /// <summary>
/// 联系信息
/// </summary>
public class Contact
{
[Required(ErrorMessage = "姓名不能为空")]
public string Name { get; set; } [Required(ErrorMessage = "地址不能为空")]
public string Address { get; set; } [Required(ErrorMessage = "邮箱不能为空")]
[RegularExpression(@"(\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\w\w)", ErrorMessage = "输入的邮箱地址不合法")]
public string Email { get; set; }
}

  CartController.cs 添加一个用于结算的 Action:

        /// <summary>
/// 结算
/// </summary>
/// <returns></returns>
public ViewResult Checkout()
{
return View(new Contact());
}

  Checkout.cshtml 中的:

@model Wen.BooksStore.Domain.Entities.Contact

<div>
@using (Html.BeginForm())
{
<div class="error">@Html.ValidationSummary()</div>
<div>姓名: @Html.TextBoxFor(x => x.Name)</div>
<div>地址: @Html.TextBoxFor(x => x.Address)</div>
<div>邮箱: @Html.TextBoxFor(x => x.Email)</div>
<div><input type="submit" value="提交" /></div>
} </div>

  这里使用的是模型校验,_Layout.cshtml 布局页需要引入 js:

    <script src="~/Scripts/jquery-1.10.2.js"></script>
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
<!DOCTYPE html>

<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
<link href="~/Contents/Site.css" rel="stylesheet" />
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
</head>
<body>
<div id="header">
@{ Html.RenderAction("Summary", "Cart");}
<div class="title">图书商城</div>
</div>
<div id="sideBar">
@{ Html.RenderAction("Sidebar", "Nav"); }
</div>
<div id="content">
@RenderBody()
</div>
</body>
</html>

_Layout.cshtml

  尝试运行,会出现以下页面,如果信息不填的话会出现相关的错误提示:

  2.接下来,要进入“提交”后的流程了。

  现在还需要一个组件用于处理订单,创建一个用于订单处理的接口,和一个该接口的实现,再通过 Ninject 进行两者的绑定:

    /// <summary>
/// 订单处理
/// </summary>
public interface IOrderProcessor
{
/// <summary>
/// 处理订单
/// </summary>
/// <param name="cart"></param>
/// <param name="contact"></param>
void ProcessOrder(Cart cart, Contact contact);
}

  建立一个实现该接口用于处理订单的实体类,这里并不是调用支付接口,而是简单通过 BCL 中的进行邮件的发送。

  

  EmailOrderProcessor.cs:

    /// <summary>
/// 邮件订单处理器
/// </summary>
public class EmailOrderProcessor : IOrderProcessor
{
/// <summary>
/// 发送人
/// </summary>
public static class Sender
{
/// <summary>
/// 账号
/// </summary>
public static string Account = "你的@qq.com"; /// <summary>
/// 密码
/// </summary>
public static string Password = "xxx";
} /// <summary>
/// 处理订单
/// </summary>
/// <param name="cart"></param>
/// <param name="contact"></param>
public void ProcessOrder(Cart cart, Contact contact)
{
if (string.IsNullOrEmpty(contact.Email))
{
throw new Exception("Email 不能为空!");
} var sb = new StringBuilder();
foreach (var item in cart.GetCartItems)
{
sb.AppendLine($"《{item.Book.Name}》:{item.Book.Price} * {item.Quantity} = {item.Book.Price * item.Quantity}");
} sb.AppendLine($"总额:{cart.GetCartItems.Sum(x => x.Quantity * x.Book.Price)}");
sb.AppendLine();
sb.AppendLine($"联系人:{contact.Name} {contact.Address}"); //设置发件人,发件人需要与设置的邮件发送服务器的邮箱一致
var fromAddr = new MailAddress(Sender.Account);
var message = new MailMessage { From = fromAddr }; //设置收件人,可添加多个,添加方法与下面的一样
message.To.Add(contact.Email);
//设置抄送人
message.CC.Add(Sender.Account);
//设置邮件标题
message.Subject = "您的订单正在出库...";
//设置邮件内容
message.Body = sb.ToString();
//设置邮件发送服务器,服务器根据你使用的邮箱而不同,可以到相应的 邮箱管理后台查看,下面是QQ的 var client = new SmtpClient("smtp.qq.com", )
{
Credentials = new NetworkCredential(Sender.Account, Sender.Password),
EnableSsl = true
}; //设置发送人的邮箱账号和密码
//启用ssl,也就是安全发送
//发送邮件
client.Send(message);
}

  CartController 也需要稍作调整:

  还要在 CartController 中额外添加一个带 [HttPost] 特性的名为 Checkout 方法:

        /// <summary>
/// 结算
/// </summary>
/// <param name="contact"></param>
/// <returns></returns>
[HttpPost]
public ViewResult Checkout(Contact contact)
{
if (!ModelState.IsValid)
return View(contact); var cart = GetCart();
_orderProcessor.ProcessOrder(cart, contact);
cart.Clear();
return View("Thanks");
}

  当校验成功时,会调用接口发一条信息,并且清空已有的购物车,然后跳转到指定的一个新视图页:

  

  新建 Thanks.cshtml,内容如下:

Thanks

  别忘了添加绑定哦,使用 DI 容器将两者进行绑定:

  启动页面,试试效果吧:

  看来,好像成功了哦:


【博主】反骨仔

【出处】http://www.cnblogs.com/liqingwen/p/6652564.html

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

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

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

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

  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设计一个电商网站(一)—— 先理解核心概念),得知我们这个电商网站的核心域就是销售子域.因为电子商务是以信息网络 ...

  7. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(三)密码修改以及密码重置

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

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

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

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

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

随机推荐

  1. gridView 编辑单元格获取单元格焦点位置(位于单元格的焦点位置)

    1.主要代码: private void Form1_Load(object sender, EventArgs e) { DataTable dt = new DataTable(); dt.Col ...

  2. 快速记录 IE8 下三个问题

    快速记录 IE8 下三个问题 昨天 pc 端网站上灰度,发现多个在 IE8 下的问题,描述和解决方案如下: 第一个问题是 css 文件过大 现象 把项目所有的 css 打包成单个文件,在现代的浏览器下 ...

  3. 2017-2-19 C#基础 数据类型

    数据类型分为基本数据类型和引用类型.基本数据类型分为两大类,值类型,字符型(char)和布尔型(bool).其中值类型分为整型和浮点型.整型分为byte,short,int,long.常用的是int( ...

  4. storyboard页面跳转传值

    受学姐的影响,习惯纯代码编程,这次要修改别人的代码,很多编程风格还不习惯. 在此之前,页面跳转我都用的是Navigation,故事板上的页面跳转带传值,让我卡了好半天. 页面跳转: [self per ...

  5. Java 集合的简单实现 (ArrayList & LinkedList & Queue & Stack)

    ArrayList 就是数组实现的啦,没什么好说的,如果数组不够了就扩容到原来的1.5倍 实现了迭代器 package com.wenr.collection; import java.io.Seri ...

  6. 关于Dapper.NET的相关论述

    年少时,为何不为自己的梦想去拼搏一次呢?纵使头破血流,也不悔有那年少轻狂.感慨很多,最近事情也很多,博客也很少更新了,毕竟每个人都需要为自己的生活去努力. 最近在一个群里遇到一个人说的话,在这里不再赘 ...

  7. 使用java.util.Properties类读写配置文件

    J2SE 1.5 以前的版本要求直接使用 XML 解析器来装载配置文件并存储设置,虽说也并非难事,相比 java.util.Properties却要做额外的解析工作.而java.util.Proper ...

  8. Android--带你一点点封装项目 MVP+BaseActivity+Retrofit+Dagger+RxJava(三)

    1,这一篇博客是和大家一起来封装我们最后的Dagger2,其实之前也写过关于简单的Dagger2,这里是地址,完全没了解的同学可以先去看一下这篇,感谢很多小伙伴一直在耐心的等待这一篇 2,Dagger ...

  9. Selenium 切换句柄

    最近用了网络上别人的一段切换窗口的code每次成功了,不错,学习 // 根据Title切换新窗口 public boolean switchToWindow_Title(WebDriver drive ...

  10. 关于IE低版本兼容问题

    1,元素浮动之后,能设置宽度的话就给元素加宽度.如果需要宽度是内容撑开,就给它里边的块元素加上浮动: 解决方案:给需要宽度由内容撑开的元素加上浮动 css样式: <style> .box{ ...