一步步打造簡(jiǎn)單的MVC電商網(wǎng)站BooksStore(2)
一步步打造一個(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)介
上一次我們嘗試了:創(chuàng)建項(xiàng)目架構(gòu)、創(chuàng)建域模型實(shí)體、創(chuàng)建單元測(cè)試、創(chuàng)建控制器與視圖、創(chuàng)建分頁(yè)和加入樣式,而這一節(jié)我們會(huì)完成兩個(gè)功能,分類(lèi)導(dǎo)航與購(gòu)物車(chē)。
主要功能與知識(shí)點(diǎn)如下:
分類(lèi)、產(chǎn)品瀏覽、購(gòu)物車(chē)、結(jié)算、CRUD(增刪改查) 管理、發(fā)郵件、分頁(yè)、模型綁定、認(rèn)證過(guò)濾器和單元測(cè)試等(預(yù)計(jì)剩余兩篇,預(yù)計(jì)明天(因?yàn)橹芰环偶伲┖椭苋ㄒ驗(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)諒。
目錄
添加分類(lèi)導(dǎo)航
加入購(gòu)物車(chē)
創(chuàng)建一個(gè)分部視圖 Partial View
一、添加分類(lèi)導(dǎo)航
上一次我們把網(wǎng)頁(yè)劃分成了三個(gè)模塊,其中左側(cè)欄的部分尚未完成,左側(cè)欄擁有將書(shū)籍分類(lèi)展示的功能。
圖 1
1.回到之前的BookDetailsViewModels 視圖模型,我們額外再添加一個(gè)新的屬性用作分類(lèi)(CurrentCategory):
/// <summary> /// 書(shū)籍詳情視圖模型 /// </summary> public class BookDetailsViewModels : PagingInfo { public IEnumerable<Book> Books { get; set; } /// <summary> /// 當(dāng)前分類(lèi) /// </summary> public string CurrentCategory { get; set; } }
2.修改完視圖模型,現(xiàn)在就應(yīng)該修改對(duì)應(yīng)的 BookController 中的Details 方法
/// <summary> /// 詳情 /// </summary> /// <param name="category">分類(lèi)</param> /// <param name="pageIndex">頁(yè)碼</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">分類(lèi)</param> /// <param name="pageIndex">頁(yè)碼</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); } } }
參數(shù)增加了一個(gè) category,用于獲取分類(lèi)的字符串,對(duì)應(yīng) Books 中的屬性的賦值語(yǔ)句改為_(kāi)bookRepository.Books.Where(x => category == null || x.Category == category),這里的 Lambda 表達(dá)式x => category == null || x.Category ==category 的意思是,分類(lèi)字符串為空就取庫(kù)中所有的 Book 實(shí)體,不為空時(shí)根據(jù)分類(lèi)進(jìn)行對(duì)集合進(jìn)行篩選過(guò)濾。
還要對(duì)屬性 CurrentCategory 進(jìn)行賦值。
別忘了,因?yàn)榉猪?yè)是根據(jù) TotalItems 屬性進(jìn)行的,所以還要修改地方_bookRepository.Books.Count(x => category == null || x.Category == category),通過(guò) LINQ 統(tǒng)計(jì)不同分類(lèi)情況的個(gè)數(shù)。
3.該控制器對(duì)應(yīng)的 Details.cshtml 中的分頁(yè)輔助器也需要修改,添加新的路由參數(shù):
<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.路由區(qū)域也應(yīng)當(dāng)修改一下
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.現(xiàn)在新建一個(gè)名為 NavController 的控制器,并添加一個(gè)名為Sidebar 的方法,專(zhuān)門(mén)用于渲染左側(cè)邊欄。
不過(guò)返回的 View 視圖類(lèi)型變成 PartialView 分部視圖類(lèi)型:
public PartialViewResult Sidebar(string category = null) { var categories = _bookRepository.Books.Select(x => x.Category).Distinct().OrderBy(x => x); return PartialView(categories); }
在方法體在右鍵,添加一個(gè)視圖,勾上創(chuàng)建分部視圖。
Sidebar.cshtml 修改為:
@model IEnumerable<string> <ul> <li>@Html.ActionLink("所有分類(lèi)", "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 框架具有一種叫作“子動(dòng)作(Child Action)”的概念,可以適用于重用導(dǎo)航控件之類(lèi)的東西,使用類(lèi)似 RenderAction() 的方法,在當(dāng)前的視圖中輸出指定的動(dòng)作方法。
因?yàn)樾枰诟敢晥D中呈現(xiàn)另一個(gè) Action 中的分部視圖,所以原來(lái)的_Layout.cshtml布局頁(yè)修改如下:
現(xiàn)在,啟動(dòng)的結(jié)果應(yīng)該和圖 1 是一樣的,嘗試點(diǎn)擊左側(cè)邊欄的分類(lèi),觀察主區(qū)域的變化情況。
二、加入購(gòu)物車(chē)
圖 2
界面的大體功能如圖 2,在每本圖書(shū)的區(qū)域新增一個(gè)鏈接(添加到購(gòu)物車(chē)),會(huì)跳轉(zhuǎn)到一個(gè)新的頁(yè)面,顯示購(gòu)物車(chē)的詳細(xì)信息 - 購(gòu)物清單,也可以通過(guò)“結(jié)算”鏈接跳轉(zhuǎn)到一個(gè)新的頁(yè)面。
購(gòu)物車(chē)是應(yīng)用程序業(yè)務(wù)域的一部分,因此,購(gòu)物車(chē)實(shí)體應(yīng)該為域模型。
1.添加兩個(gè)類(lèi):
Cart.cs 有添加、移除、清空和統(tǒng)計(jì)功能:
/// <summary> /// 購(gòu)物車(chē) /// </summary> public class Cart { private readonly List<CartItem> _cartItems = new List<CartItem>(); /// <summary> /// 獲取購(gòu)物車(chē)的所有項(xiàng)目 /// </summary> public IList<CartItem> GetCartItems => _cartItems; /// <summary> /// 添加書(shū)模型 /// </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> /// 移除書(shū)模型 /// </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> /// 清空購(gòu)物車(chē) /// </summary> public void Clear() { _cartItems.Clear(); } /// <summary> /// 統(tǒng)計(jì)總額 /// </summary> /// <returns></returns> public decimal ComputeTotalValue() { return _cartItems.Sum(x => x.Book.Price * x.Quantity); } }
CartItem.cs 表示購(gòu)物車(chē)中的每一項(xiàng):
/// <summary> /// 購(gòu)物車(chē)項(xiàng) /// </summary> public class CartItem { /// <summary> /// 書(shū) /// </summary> public Book Book { get; set; } /// <summary> /// 數(shù)量 /// </summary> public int Quantity { get; set; } }
2.修改一下之前的 Details.cshtml,增加“添加到購(gòu)物車(chē)”的按鈕:
@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="+ 添加到購(gòu)物車(chē)" /> } <br /> <hr /> </div> } <div class="pager"> @Html.PageLinks(Model, x => Url.Action("Details", new { pageIndex = x, category = Model.CurrentCategory })) </div>
【備注】@Html.BeginForm() 方法默認(rèn)會(huì)創(chuàng)建一個(gè) Post 請(qǐng)求方法的表單,為什么不直接使用 Get 請(qǐng)求呢,HTTP 規(guī)范要求,會(huì)引起數(shù)據(jù)變化時(shí)不要使用 Get 請(qǐng)求,將產(chǎn)品添加到一個(gè)購(gòu)物車(chē)明顯會(huì)出現(xiàn)新的數(shù)據(jù)變化,所以,這種情形不應(yīng)該使用 Get 請(qǐng)求,直接顯示頁(yè)面或者列表數(shù)據(jù),這種請(qǐng)求才應(yīng)該使用 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.再添加一個(gè) 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; } }
【備注】這里的購(gòu)物車(chē)是通過(guò) Session 會(huì)話狀態(tài)進(jìn)行保存用戶(hù)的 Cart 對(duì)象。當(dāng)會(huì)話過(guò)期(典型的情況是用戶(hù)很長(zhǎng)時(shí)間沒(méi)有對(duì)服務(wù)器發(fā)起任何請(qǐng)求),與該會(huì)話關(guān)聯(lián)的數(shù)據(jù)就會(huì)被刪除,這就意味著不需要對(duì) Cart 對(duì)象進(jìn)行生命周期的管理。
【備注】RedirectToAction() 方法:將一個(gè) HTTP 重定向的指令發(fā)給客戶(hù)端瀏覽器,要求瀏覽器請(qǐng)求一個(gè)新的 Url。
5.在 Index 方法中選擇右鍵新建視圖,專(zhuān)門(mén)用于顯示購(gòu)物清單:
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> </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>總計(jì):</td> <td>@Model.Cart.ComputeTotalValue().ToString("C")</td> </tr> </tbody> </table> <p> <a href="@Model.ReturnUrl">繼續(xù)購(gòu)物</a> </p>
我想,這一定是一個(gè)令人激動(dòng)的時(shí)刻,因?yàn)槲覀円呀?jīng)完成了這個(gè)基本的添加到購(gòu)物車(chē)的功能。
三、創(chuàng)建一個(gè)分部視圖 Partial View
分部視圖,是嵌入在另一個(gè)視圖中的一個(gè)內(nèi)容片段,并且可以跨視圖重用,這有助于減少重復(fù),尤其需要在多個(gè)地方需要重復(fù)使用相同的數(shù)據(jù)時(shí)。
在 Shared 內(nèi)部新建一個(gè)名為_(kāi)BookSummary.cshtml 的視圖,并且把之前Details.cshtml 的代碼進(jìn)行整理。
修改后的兩個(gè)視圖:
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="+ 添加到購(gòu)物車(chē)" /> } <br /> <hr /> </div>
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- MVC+EasyUI+三層新聞網(wǎng)站建立 建站準(zhǔn)備工作(一)
- MVC+EasyUI+三層新聞網(wǎng)站建立 主頁(yè)布局的方法(五)
- MVC+EasyUI+三層新聞網(wǎng)站建立 實(shí)現(xiàn)登錄功能(四)
- MVC+EasyUI+三層新聞網(wǎng)站建立 后臺(tái)登錄界面的搭建(二)
- MVC+EasyUI+三層新聞網(wǎng)站建立 驗(yàn)證碼生成(三)
- 一步步打造簡(jiǎn)單的MVC電商網(wǎng)站BooksStore(1)
- MVC4制作網(wǎng)站教程第四章 更新欄目4.3
- MVC4制作網(wǎng)站教程第四章 瀏覽欄目4.2
- MVC4制作網(wǎng)站教程第四章 添加欄目4.1
- MVC+EasyUI+三層新聞網(wǎng)站建立 tabs標(biāo)簽制作方法(六)
相關(guān)文章
關(guān)于前臺(tái)調(diào)用后臺(tái)事件__doPostBack函數(shù)
關(guān)于前臺(tái)調(diào)用后臺(tái)事件__doPostBack函數(shù)...2007-04-04Asp.Net Core WebAPI使用Swagger時(shí)API隱藏和分組詳解
這篇文章主要給大家介紹了關(guān)于Asp.Net Core WebAPI使用Swagger時(shí)API隱藏和分組的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Asp.Net Core具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04asp.net 無(wú)重復(fù)隨機(jī)數(shù)代碼
asp.net產(chǎn)生無(wú)重復(fù)隨機(jī)數(shù)的實(shí)現(xiàn)代碼2008-11-11asp.net GridView導(dǎo)出到Excel代碼
asp.net GridView導(dǎo)出到Excel代碼,需要的朋友可以參考下。2009-07-07.net 中按.(點(diǎn))無(wú)法智能提示的bug解決方案
IDE按.無(wú)法智能提示,但是可以編譯并正常使用,在修改別人代碼bug時(shí)遇到的,接下來(lái)為你提供詳細(xì)解決方法,感興趣的你可以參考下哈2013-03-03ASP.NET MVC 4使用PagedList.Mvc分頁(yè)的實(shí)現(xiàn)代碼
本篇文章主要介紹了ASP.NET MVC 4使用PagedList.Mvc分頁(yè)的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07詳解ASP.NET Core3.0 配置的Options模式
這篇文章主要介紹了詳解ASP.NET Core3.0 配置的Options模式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08VS2012下QT creator登錄對(duì)話框設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了VS2012下QT creator登錄對(duì)話框的設(shè)計(jì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06