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

Index.cshtml
@model Wen.BooksStore.WebUI.Models.CartIndexViewModel
<h2>我的購物車</h2>
<table class="table">
<thead>
<tr>
<th>書名</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="清空購物車" />
}
</td>
</tr>
</tbody>
</table>
【備注】@Html.Hidden("id", item.Book.Id) 是用于生成隱藏的字段,如果直接使用@Html.HiddenFor(),生成的 name 將會是 item.Book.Id ,將和 CartController 中 RemoveFromCart(int id, string return) 的參數(shù)不匹配。
顯示的效果如下:

2.添加摘要:我們在購物車存放了許多東西,通過摘要,可以顯示購物總額的縮略圖,我們選擇的位置在頂部右上角的一個(gè)比較顯眼的位置進(jìn)行顯示它,當(dāng)然,還需要有點(diǎn)擊的跳轉(zhuǎn)按鈕方便顯示所有的購物清單頁面。
繼續(xù)在 CartController 下新增一個(gè) Action,名為 Summary,返回值是一個(gè)分部視圖:
/// <summary>
/// 摘要
/// </summary>
/// <returns></returns>
public PartialViewResult Summary()
{
return PartialView(GetCart());
}
對應(yīng)的Summary.cshtml
@model Wen.BooksStore.Domain.Entities.Cart
<div class="bookSummary">
你的購物車:@Model.ComputeTotalValue()
<span>@Html.ActionLink("結(jié)算", "Checkout", "Cart", new { retunUrl = Request.Url.PathAndQuery }, null)</span>
</div>
對應(yīng)的布局頁_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">圖書商城</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é)算
購物完畢就是結(jié)算頁面了,這里的訂單結(jié)算并不涉及支付接口的調(diào)用,只是使用郵件的形式進(jìn)行通知而已。
這里,我設(shè)計(jì)結(jié)算的時(shí)候需要要求用戶輸入一些信息,如姓名、地址和郵箱等信息,在點(diǎn)擊確定時(shí)我再將這些輸入的信息與購物清單的信息從系統(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 布局頁需要引入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>
嘗試運(yùn)行,會出現(xiàn)以下頁面,如果信息不填的話會出現(xiàn)相關(guān)的錯誤提示:

2.接下來,要進(jìn)入“提交”后的流程了。
現(xiàn)在還需要一個(gè)組件用于處理訂單,創(chuàng)建一個(gè)用于訂單處理的接口,和一個(gè)該接口的實(shí)現(xiàn),再通過 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í)體類,這里并不是調(diào)用支付接口,而是簡單通過 BCL 中的進(jìn)行郵件的發(fā)送。

EmailOrderProcessor.cs:
/// <summary>
/// 郵件訂單處理器
/// </summary>
public class EmailOrderProcessor : IOrderProcessor
{
/// <summary>
/// 發(fā)送人
/// </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($"聯(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 = "您的訂單正在出庫...";
//設(shè)置郵件內(nèi)容
message.Body = sb.ToString();
//設(shè)置郵件發(fā)送服務(wù)器,服務(wù)器根據(jù)你使用的郵箱而不同,可以到相應(yīng)的 郵箱管理后臺查看,下面是QQ的
var client = new SmtpClient("smtp.qq.com", 25)
{
Credentials = new NetworkCredential(Sender.Account, Sender.Password),
EnableSsl = true
};
//設(shè)置發(fā)送人的郵箱賬號和密碼
//啟用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í),會調(diào)用接口發(fā)一條信息,并且清空已有的購物車,然后跳轉(zhuǎn)到指定的一個(gè)新視圖頁:

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

啟動頁面,試試效果吧:

看來,好像成功了哦:

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 一步步打造簡單的MVC電商網(wǎng)站BooksStore(4)
- 一步步打造簡單的MVC電商網(wǎng)站BooksStore(1)
- MVC4制作網(wǎng)站教程第四章 更新欄目4.3
- MVC4制作網(wǎng)站教程第四章 添加欄目4.1
- asp.net mvc驗(yàn)證碼類使用
- MVC使用極驗(yàn)驗(yàn)證制作登錄驗(yàn)證碼學(xué)習(xí)筆記7
- ASP.NET MVC驗(yàn)證碼功能實(shí)現(xiàn)代碼
- ASP.NET?MVC5網(wǎng)站開發(fā)之添加、刪除、重置密碼、修改密碼、列表瀏覽管理員篇2(六)
- ASP.NET MVC5網(wǎng)站開發(fā)之登錄、驗(yàn)證和注銷管理員篇1(六)
- MVC+EasyUI+三層新聞網(wǎng)站建立 建站準(zhǔn)備工作(一)
相關(guān)文章
asp.net Gridview數(shù)據(jù)列中實(shí)現(xiàn)鼠標(biāo)懸浮變色
Gridview一般朋友們都比較常用,因?yàn)樗梢苑奖憧旖莸膶?shí)現(xiàn)我們所需的很多功能,代碼也比較簡潔。平時(shí)的項(xiàng)目中這個(gè)控件我也比較常用,其中有個(gè)功能用到的頻率也比較多。所以記錄下備忘。2010-06-06
Asp.net 連接MySQL的實(shí)現(xiàn)代碼[]
ASP.NET連接MySQL需要一個(gè)組件(.net本身不提供訪問MySQL的驅(qū)動)MySQL.Data.Dll,此為官方提供(純C#開發(fā),開源噢),有多個(gè)版本選擇,采用的數(shù)據(jù)訪問模式為ADO.NET,跟asp.net訪問sqlserver很像,非常簡單。2009-08-08
ASP.NET MVC5網(wǎng)站開發(fā)概述(一)
這篇文章主要內(nèi)容是ASP.NET MVC5網(wǎng)站開發(fā)實(shí)踐的整體概述,分析了開發(fā)環(huán)境、使用的技術(shù)以及項(xiàng)目的整體結(jié)構(gòu),感興趣的小伙伴們可以參考一下2015-09-09
ASP.NET2.0使用Enter Key作為默認(rèn)提交問題分析(附源碼)
這篇文章主要介紹了ASP.NET2.0使用Enter Key作為默認(rèn)提交,結(jié)合實(shí)例形式分析了ASP.NET2.0使用Enter Key默認(rèn)提交的注意事項(xiàng)與相關(guān)實(shí)現(xiàn)技巧,并附上源碼供讀者參考,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
Visual Studio 2015和 .NET Core安裝教程
這篇文章主要為大家詳細(xì)介紹了Visual Studio Community 2015和 .NET Core安裝圖文教程,感興趣的小伙伴們可以參考一下2016-07-07
ASP.NET網(wǎng)頁打印(只打印相關(guān)內(nèi)容/自寫功能)
朋友要求在前段時(shí)間完成的新聞的網(wǎng)站上加上一個(gè)功能,就是在每篇新聞瀏覽的頁面, 加一個(gè)打印銨鈕。讓用戶一點(diǎn)打印,能把整篇文章打印2013-01-01
ASP.NET WebService中使用ASP.NET_SessionId的問題說明
proxy.CookieContainer存儲了客戶端的 ASP.NET_SessionId。這樣以后每次通過webservice 方法調(diào)用時(shí),都會將ASP.NET_SessionId傳遞到服務(wù)器端。2011-09-09

