一步步打造簡單的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é)我們會(huì)完成整個(gè)購物車的流程,以及訂單處理。
該系列主要功能與知識(shí)點(diǎn)如下:
分類、產(chǎn)品瀏覽、購物車、結(jié)算、CRUD(增刪改查) 管理、發(fā)郵件、分頁、模型綁定、認(rèn)證過濾器和單元測試等(預(yù)計(jì)剩余兩篇,周三(因?yàn)橹芏簧习啵┫劝l(fā)布一篇)。
【備注】項(xiàng)目使用 VS2015 + C#6 進(jìn)行開發(fā),有問題請(qǐng)發(fā)表在留言區(qū)哦,還有,頁面長得比較丑,請(qǐng)見諒。
目錄
完成購物車
訂單結(jié)算
一、完成購物車
上一節(jié)其實(shí)已經(jīng)完成了移除購物車和清空購物車的方法,只是尚未將可供用戶操作的按鈕放在頁面區(qū)域。除了增加這兩個(gè)按鈕,也會(huì)在頁面頂部的位置增加購物車的摘要(用于顯示用戶的購物總額)。
下面是上一節(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 將會(huì)是 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()); }
對(duì)應(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>
對(duì)應(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)行,會(huì)出現(xiàn)以下頁面,如果信息不填的話會(huì)出現(xiàn)相關(guān)的錯(cuò)誤提示:
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> /// 賬號(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 = "您的訂單正在出庫..."; //設(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ā)一條信息,并且清空已有的購物車,然后跳轉(zhuǎn)到指定的一個(gè)新視圖頁:
新建 Thanks.cshtml,內(nèi)容如下:
Thanks
別忘了添加綁定哦,使用 DI 容器將兩者進(jìn)行綁定:
啟動(dòng)頁面,試試效果吧:
看來,好像成功了哦:
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(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-06Asp.net 連接MySQL的實(shí)現(xiàn)代碼[]
ASP.NET連接MySQL需要一個(gè)組件(.net本身不提供訪問MySQL的驅(qū)動(dòng))MySQL.Data.Dll,此為官方提供(純C#開發(fā),開源噢),有多個(gè)版本選擇,采用的數(shù)據(jù)訪問模式為ADO.NET,跟asp.net訪問sqlserver很像,非常簡單。2009-08-08ASP.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-09ASP.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-11Visual Studio 2015和 .NET Core安裝教程
這篇文章主要為大家詳細(xì)介紹了Visual Studio Community 2015和 .NET Core安裝圖文教程,感興趣的小伙伴們可以參考一下2016-07-07ASP.NET網(wǎng)頁打印(只打印相關(guān)內(nèi)容/自寫功能)
朋友要求在前段時(shí)間完成的新聞的網(wǎng)站上加上一個(gè)功能,就是在每篇新聞瀏覽的頁面, 加一個(gè)打印銨鈕。讓用戶一點(diǎn)打印,能把整篇文章打印2013-01-01ASP.NET WebService中使用ASP.NET_SessionId的問題說明
proxy.CookieContainer存儲(chǔ)了客戶端的 ASP.NET_SessionId。這樣以后每次通過webservice 方法調(diào)用時(shí),都會(huì)將ASP.NET_SessionId傳遞到服務(wù)器端。2011-09-09