一步步打造簡(jiǎn)單的MVC電商網(wǎng)站BooksStore(3)
一步步打造一個(gè)簡(jiǎn)單的 MVC 電商網(wǎng)站 - BooksStore(三)
本系列的 GitHub地址:https://github.com/liqingwen2015/Wen.BooksStore
《一步步打造一個(gè)簡(jiǎn)單的 MVC 電商網(wǎng)站 - BooksStore(一)》
《一步步打造一個(gè)簡(jiǎn)單的 MVC 電商網(wǎng)站 - BooksStore(二)》
《一步步打造一個(gè)簡(jiǎn)單的 MVC 電商網(wǎng)站 - BooksStore(三)》
《一步步打造一個(gè)簡(jiǎn)單的 MVC 電商網(wǎng)站 - BooksStore(四)》
簡(jiǎn)介
上一節(jié)我們完成了兩個(gè)主要功能:添加到購(gòu)物車(chē)和分類(lèi)導(dǎo)航,這一節(jié)我們會(huì)完成整個(gè)購(gòu)物車(chē)的流程,以及訂單處理。
該系列主要功能與知識(shí)點(diǎn)如下:
分類(lèi)、產(chǎn)品瀏覽、購(gòu)物車(chē)、結(jié)算、CRUD(增刪改查) 管理、發(fā)郵件、分頁(yè)、模型綁定、認(rèn)證過(guò)濾器和單元測(cè)試等(預(yù)計(jì)剩余兩篇,周三(因?yàn)橹芏簧习啵┫劝l(fā)布一篇)。
【備注】項(xiàng)目使用 VS2015 + C#6 進(jìn)行開(kāi)發(fā),有問(wèn)題請(qǐng)發(fā)表在留言區(qū)哦,還有,頁(yè)面長(zhǎng)得比較丑,請(qǐng)見(jiàn)諒。
目錄
完成購(gòu)物車(chē)
訂單結(jié)算
一、完成購(gòu)物車(chē)
上一節(jié)其實(shí)已經(jīng)完成了移除購(gòu)物車(chē)和清空購(gòu)物車(chē)的方法,只是尚未將可供用戶操作的按鈕放在頁(yè)面區(qū)域。除了增加這兩個(gè)按鈕,也會(huì)在頁(yè)面頂部的位置增加購(gòu)物車(chē)的摘要(用于顯示用戶的購(gòu)物總額)。
下面是上一節(jié)已經(jīng)寫(xiě)好的 CartController 代碼。
/// <summary>
/// 購(gòu)物車(chē)
/// </summary>
public class CartController : Controller
{
private readonly IBookRepository _bookRepository;
public CartController(IBookRepository bookRepository)
{
_bookRepository = bookRepository;
}
/// <summary>
/// 首頁(yè)
/// </summary>
/// <param name="returnUrl"></param>
/// <returns></returns>
public ViewResult Index(string returnUrl)
{
return View(new CartIndexViewModel()
{
Cart = GetCart(),
ReturnUrl = returnUrl
});
}
/// <summary>
/// 添加到購(gòu)物車(chē)
/// </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>
/// 從購(gòu)物車(chē)移除
/// </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>
/// 獲取購(gòu)物車(chē)
/// </summary>
/// <returns></returns>
private Cart GetCart()
{
var cart = (Cart)Session["Cart"];
if (cart != null) return cart;
cart = new Cart();
Session["Cart"] = cart;
return cart;
}
}
1.加入移除書(shū)籍和清空購(gòu)物車(chē)的功能

Index.cshtml
@model Wen.BooksStore.WebUI.Models.CartIndexViewModel
<h2>我的購(gòu)物車(chē)</h2>
<table class="table">
<thead>
<tr>
<th>書(shū)名</th>
<th>價(jià)格</th>
<th>數(shù)量</th>
<th>總計(jì)</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>總計(jì):</td>
<td>@Model.Cart.ComputeTotalValue().ToString("C")</td>
<td>
@using (Html.BeginForm("Clear", "Cart"))
{
@Html.HiddenFor(x => x.ReturnUrl)
<input type="submit" value="清空購(gòu)物車(chē)" />
}
</td>
</tr>
</tbody>
</table>
【備注】@Html.Hidden("id", item.Book.Id) 是用于生成隱藏的字段,如果直接使用@Html.HiddenFor(),生成的 name 將會(huì)是 item.Book.Id ,將和 CartController 中 RemoveFromCart(int id, string return) 的參數(shù)不匹配。
顯示的效果如下:

2.添加摘要:我們?cè)谫?gòu)物車(chē)存放了許多東西,通過(guò)摘要,可以顯示購(gòu)物總額的縮略圖,我們選擇的位置在頂部右上角的一個(gè)比較顯眼的位置進(jìn)行顯示它,當(dāng)然,還需要有點(diǎn)擊的跳轉(zhuǎn)按鈕方便顯示所有的購(gòu)物清單頁(yè)面。
繼續(xù)在 CartController 下新增一個(gè) Action,名為 Summary,返回值是一個(gè)分部視圖:
/// <summary>
/// 摘要
/// </summary>
/// <returns></returns>
public PartialViewResult Summary()
{
return PartialView(GetCart());
}
對(duì)應(yīng)的Summary.cshtml
@model Wen.BooksStore.Domain.Entities.Cart
<div class="bookSummary">
你的購(gòu)物車(chē):@Model.ComputeTotalValue()
<span>@Html.ActionLink("結(jié)算", "Checkout", "Cart", new { retunUrl = Request.Url.PathAndQuery }, null)</span>
</div>
對(duì)應(yīng)的布局頁(yè)_Layout.cshtml 修改的地方為:

_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">圖書(shū)商城</div>
</div>
<div id="sideBar">
@{ Html.RenderAction("Sidebar", "Nav"); }
</div>
<div id="content">
@RenderBody()
</div>
</body>
</html>
添加了新的東西,css 也要進(jìn)行修改:
Site.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;
}
.bookSummary {
width: 15%;
float: right;
margin-top: 1.5%;
}

二、訂單結(jié)算
購(gòu)物完畢就是結(jié)算頁(yè)面了,這里的訂單結(jié)算并不涉及支付接口的調(diào)用,只是使用郵件的形式進(jìn)行通知而已。
這里,我設(shè)計(jì)結(jié)算的時(shí)候需要要求用戶輸入一些信息,如姓名、地址和郵箱等信息,在點(diǎn)擊確定時(shí)我再將這些輸入的信息與購(gòu)物清單的信息從系統(tǒng)的郵箱發(fā)到你所輸入的郵箱當(dāng)中。一個(gè)比較直觀的圖:

1.在 Entities 中添加一個(gè)域模型 Contact.cs 表示聯(lián)系人的信息。

/// <summary>
/// 聯(lián)系信息
/// </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 添加一個(gè)用于結(jié)算的 Action:
/// <summary>
/// 結(jié)算
/// </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>
這里使用的是模型校驗(yàn),_Layout.cshtml 布局頁(yè)需要引入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">圖書(shū)商城</div>
</div>
<div id="sideBar">
@{ Html.RenderAction("Sidebar", "Nav"); }
</div>
<div id="content">
@RenderBody()
</div>
</body>
</html>
嘗試運(yùn)行,會(huì)出現(xiàn)以下頁(yè)面,如果信息不填的話會(huì)出現(xiàn)相關(guān)的錯(cuò)誤提示:

2.接下來(lái),要進(jìn)入“提交”后的流程了。
現(xiàn)在還需要一個(gè)組件用于處理訂單,創(chuàng)建一個(gè)用于訂單處理的接口,和一個(gè)該接口的實(shí)現(xiàn),再通過(guò) Ninject 進(jìn)行兩者的綁定:

/// <summary>
/// 訂單處理
/// </summary>
public interface IOrderProcessor
{
/// <summary>
/// 處理訂單
/// </summary>
/// <param name="cart"></param>
/// <param name="contact"></param>
void ProcessOrder(Cart cart, Contact contact);
}
建立一個(gè)實(shí)現(xiàn)該接口用于處理訂單的實(shí)體類(lèi),這里并不是調(diào)用支付接口,而是簡(jiǎn)單通過(guò) BCL 中的進(jìn)行郵件的發(fā)送。

EmailOrderProcessor.cs:
/// <summary>
/// 郵件訂單處理器
/// </summary>
public class EmailOrderProcessor : IOrderProcessor
{
/// <summary>
/// 發(fā)送人
/// </summary>
public static class Sender
{
/// <summary>
/// 賬號(hào)
/// </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($"聯(lián)系人:{contact.Name} {contact.Address}");
//設(shè)置發(fā)件人,發(fā)件人需要與設(shè)置的郵件發(fā)送服務(wù)器的郵箱一致
var fromAddr = new MailAddress(Sender.Account);
var message = new MailMessage { From = fromAddr };
//設(shè)置收件人,可添加多個(gè),添加方法與下面的一樣
message.To.Add(contact.Email);
//設(shè)置抄送人
message.CC.Add(Sender.Account);
//設(shè)置郵件標(biāo)題
message.Subject = "您的訂單正在出庫(kù)...";
//設(shè)置郵件內(nèi)容
message.Body = sb.ToString();
//設(shè)置郵件發(fā)送服務(wù)器,服務(wù)器根據(jù)你使用的郵箱而不同,可以到相應(yīng)的 郵箱管理后臺(tái)查看,下面是QQ的
var client = new SmtpClient("smtp.qq.com", 25)
{
Credentials = new NetworkCredential(Sender.Account, Sender.Password),
EnableSsl = true
};
//設(shè)置發(fā)送人的郵箱賬號(hào)和密碼
//啟用ssl,也就是安全發(fā)送
//發(fā)送郵件
client.Send(message);
}
CartController 也需要稍作調(diào)整:

還要在 CartController 中額外添加一個(gè)帶[HttPost] 特性的名為 Checkout 方法:

/// <summary>
/// 結(jié)算
/// </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");
}
當(dāng)校驗(yàn)成功時(shí),會(huì)調(diào)用接口發(fā)一條信息,并且清空已有的購(gòu)物車(chē),然后跳轉(zhuǎn)到指定的一個(gè)新視圖頁(yè):

新建 Thanks.cshtml,內(nèi)容如下:
Thanks
別忘了添加綁定哦,使用 DI 容器將兩者進(jìn)行綁定:

啟動(dòng)頁(yè)面,試試效果吧:

看來(lái),好像成功了哦:

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 一步步打造簡(jiǎn)單的MVC電商網(wǎng)站BooksStore(4)
- 一步步打造簡(jiǎn)單的MVC電商網(wǎng)站BooksStore(1)
- MVC4制作網(wǎng)站教程第四章 更新欄目4.3
- MVC4制作網(wǎng)站教程第四章 添加欄目4.1
- asp.net mvc驗(yàn)證碼類(lèi)使用
- MVC使用極驗(yàn)驗(yàn)證制作登錄驗(yàn)證碼學(xué)習(xí)筆記7
- ASP.NET MVC驗(yàn)證碼功能實(shí)現(xiàn)代碼
- ASP.NET?MVC5網(wǎng)站開(kāi)發(fā)之添加、刪除、重置密碼、修改密碼、列表瀏覽管理員篇2(六)
- ASP.NET MVC5網(wǎng)站開(kāi)發(fā)之登錄、驗(yàn)證和注銷(xiāo)管理員篇1(六)
- MVC+EasyUI+三層新聞網(wǎng)站建立 建站準(zhǔn)備工作(一)
相關(guān)文章
Asp.net?MVC中的Http管道事件為什么要以Application_開(kāi)頭(原因解析)
在ASP.NET?MVC中,為了在API請(qǐng)求結(jié)束時(shí)釋放數(shù)據(jù)庫(kù)鏈接,避免連接池被爆掉,可以通過(guò)在Global.asax.cs文件中定義并實(shí)現(xiàn)Application_EndRequest方法來(lái)實(shí)現(xiàn),本文介紹Asp.net?MVC中的Http管道事件為什么要以Application_開(kāi)頭,感興趣的朋友一起看看吧2024-12-12
C#實(shí)現(xiàn)EXCEL數(shù)據(jù)到TXT文檔的轉(zhuǎn)換
C#實(shí)現(xiàn)EXCEL數(shù)據(jù)到TXT文檔的轉(zhuǎn)換,需要的朋友可以參考一下2013-02-02
ASP.NET2.0中數(shù)據(jù)源控件之異步數(shù)據(jù)訪問(wèn)
ASP.NET2.0中數(shù)據(jù)源控件之異步數(shù)據(jù)訪問(wèn)...2006-09-09
關(guān)于.NET/C#/WCF/WPF 打造IP網(wǎng)絡(luò)智能視頻監(jiān)控系統(tǒng)的介紹
本篇文章小編將為大家介紹,關(guān)于.NET/C#/WCF/WPF 打造IP網(wǎng)絡(luò)智能視頻監(jiān)控系統(tǒng)的介紹。需要的朋友參考下2013-04-04
Asp.NET 隨機(jī)碼生成基類(lèi)(隨機(jī)字母,隨機(jī)數(shù)字,隨機(jī)字母+數(shù)字)
對(duì)于需要用asp.net 字母,隨機(jī)數(shù)字,隨機(jī)字母+數(shù)字生成隨機(jī)碼的朋友用的到2008-11-11
使用pdfbox實(shí)現(xiàn)pdf文本提取和合并功能示例
這篇文章主要介紹了使用pdfbox實(shí)現(xiàn)pdf文本提取和合并功能示例,大家參考使用吧2014-01-01

