Build a simple MVC e-commerce website booksstore step by step (2)

Time:2021-11-23

Build a simple MVC e-commerce website – booksstore step by step (II)

GitHub address of this series:https://github.com/liqingwen2015/Wen.BooksStore

Build a simple MVC e-commerce website – booksstore step by step (I)

Build a simple MVC e-commerce website – booksstore step by step (II)

Build a simple MVC e-commerce website – booksstore step by step (III)

Build a simple MVC e-commerce website – booksstore step by step (IV)

brief introduction

Last time, we tried to: create project architecture, create domain model entities, create unit tests, create controllers and views, create paging and add styles. In this section, we will complete two functions, classification navigation and shopping cart.

The main functions and knowledge points are as follows:

Classification, product browsing, shopping cart, settlement, CRUD (addition, deletion, modification and query) management, e-mail, paging, model binding, authentication filter and unit test (the remaining two are expected to be released tomorrow (because there is no holiday on Saturday) and Wednesday (because there is no work on Tuesday).

[remarks] the project uses vs2015 + c#6 for development. If you have any questions, please post them in the message area. In addition, the page is ugly. Please forgive me.

catalogue

Add category navigation

add to cart

Create a partial view partial view

1、 Add category navigation

Last time, we divided the web page into three modules, of which the left column has not been completed, and the left column has the function of classifying and displaying books.

Figure 1

1. Back to the previous bookdetailsviewmodels view model, we add a new attribute as the current category:

/// <summary>
 ///Book detail view model
 /// </summary>
 public class BookDetailsViewModels : PagingInfo
 {
 public IEnumerable<Book> Books { get; set; }

 /// <summary>
 ///Current classification
 /// </summary>
 public string CurrentCategory { get; set; }
 }

2. After modifying the view model, you should modify the details method in the corresponding bookcontroller now

/// <summary>
 ///Details
 /// </summary>
 ///< param name = "category" > category < / param >
 ///< param name = "PageIndex" > page < / 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>
 ///Details
 /// </summary>
 ///< param name = "category" > category < / param >
 ///< param name = "PageIndex" > page < / 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);
 }
 }
}

A category is added to the parameter to obtain the classified string. The assignment statement corresponding to the attribute in books is changed to_ Bookrepository.books.where (x = > category = = null | x.category = = category). The lambda expression x = > category = = null | x.category = = category here means that if the classification string is empty, all book entities in the library will be retrieved. If it is not empty, the collection will be filtered according to the classification.

Also assign a value to the property currentcategory.

Don’t forget, because pagination is based on the totalitems property, you have to modify the location_ Bookrepository.books.count (x = > category = = null | x.category = = category) counts the number of different categories through LINQ.

3. The paging assistant in the details.cshtml corresponding to the controller also needs to be modified to add new routing parameters:


<div>
 @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>
 <h3>@item.Name</h3>
 @item.Description
 <h4>@item.Price.ToString("C")</h4>
 <br />
 <hr />
 </div>
}

<div>
 @Html.PageLinks(Model, x => Url.Action("Details", new { pageIndex = x, category = Model.CurrentCategory }))
</div>

4. The routing area should also be modified

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. Now create a new controller named navcontroller and add a method named sidebar to render the left sidebar.

However, the returned view type becomes partialview partial view type:


public PartialViewResult Sidebar(string category = null)
 {
  var categories = _bookRepository.Books.Select(x => x.Category).Distinct().OrderBy(x => x);
  return PartialView(categories);
 }

Right click the method body, add a view, and check create partial view.

Sidebar.cshtml is modified to:

@model IEnumerable<string>

<ul>
 <li>@HTML. Actionlink ("all categories", "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 framework has a concept called “child action”, which can be applied to reuse navigation controls and other things. It uses a method similar to renderaction() to output the specified action method in the current view.

Because the partial view in another action needs to be rendered in the parent view, the original_ The layout.cshtml layout page is modified as follows:

Now, the startup result should be the same as that in Figure 1. Try to click the classification in the left sidebar to observe the changes in the main area.

2、 Add shopping cart

Figure 2

The general function of the interface is shown in Figure 2. Add a link in the area of each book (add to the shopping cart), and it will jump to a new page to display the details of the shopping cart – shopping list, or jump to a new page through the “settlement” link.

The shopping cart is part of the business domain of the application, so the shopping cart entity should be a domain model.

1. Add two classes:

Cart.cs has the functions of adding, removing, emptying and Statistics:

/// <summary>
 ///Shopping cart
 /// </summary>
 public class Cart
 {
 private readonly List<CartItem> _cartItems = new List<CartItem>();

 /// <summary>
 ///Get all items in the shopping cart
 /// </summary>
 public IList<CartItem> GetCartItems => _cartItems;

 /// <summary>
 ///Add book model
 /// </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>
 ///Remove book model
 /// </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>
 ///Empty shopping cart
 /// </summary>
 public void Clear()
 {
  _cartItems.Clear();
 }

 /// <summary>
 ///Total statistics
 /// </summary>
 /// <returns></returns>
 public decimal ComputeTotalValue()
 {
  return _cartItems.Sum(x => x.Book.Price * x.Quantity);
 }
 }

Cartitem.cs represents each item in the shopping cart:

/// <summary>
 ///Shopping cart item
 /// </summary>
 public class CartItem
 {
 /// <summary>
 ///Book
 /// </summary>
 public Book Book { get; set; }

 /// <summary>
 ///Quantity
 /// </summary>
 public int Quantity { get; set; }
 }

2. Modify the previous details.cshtml and add the “add to shopping cart” button:

@model Wen.BooksStore.WebUI.Models.BookDetailsViewModels

@{
 ViewBag.Title = "Books";
}

@foreach (var item in Model.Books)
{
 <div>
 <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 = "+ add to cart" / >
 }

 <br />
 <hr />
 </div>
}

<div>
 @Html.PageLinks(Model, x => Url.Action("Details", new { pageIndex = x, category = Model.CurrentCategory }))
</div>

[note] @ HTML. Beginform() method will create a form of post request method by default. Why not directly use get request? HTTP specification requires that do not use get request when data changes will occur. Adding products to a shopping cart will obviously cause new data changes. Therefore, get request should not be used in this case, Get should only be used for requests that directly display page or list data.

3. Modify the style in CSS first


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. Add another cartcontroller

/// <summary>
 ///Shopping cart
 /// </summary>
 public class CartController : Controller
 {
 private readonly IBookRepository _bookRepository;

 public CartController(IBookRepository bookRepository)
 {
  _bookRepository = bookRepository;
 }

 /// <summary>
 ///Front page
 /// </summary>
 /// <param name="returnUrl"></param>
 /// <returns></returns>
 public ViewResult Index(string returnUrl)
 {
  return View(new CartIndexViewModel()
  {
  Cart = GetCart(),
  ReturnUrl = returnUrl
  });
 }

 /// <summary>
 ///Add to cart
 /// </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>
 ///Remove from cart
 /// </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>
 ///Get shopping cart
 /// </summary>
 /// <returns></returns>
 private Cart GetCart()
 {
  var cart = (Cart)Session["Cart"];
  if (cart != null) return cart;

  cart = new Cart();
  Session["Cart"] = cart;

  return cart;
 }
 }

[note] the shopping cart here is the cart object that saves the user through the session state. When the session expires (typically, the user does not initiate any request to the server for a long time), the data associated with the session will be deleted, which means that there is no need to manage the life cycle of the cart object.

[remarks] redirecttoaction() method: send an HTTP redirection instruction to the client browser and ask the browser to request a new URL.

5. Right click in the index method to create a new view, which is specially used to display the shopping list:

Code in index.cshtml:

@model Wen.BooksStore.WebUI.Models.CartIndexViewModel

<h2>My shopping cart</h2>

<table>
 <thead>
 <tr>
  < th > Title < / th >
  < th > price < / th >
  < th > quantity < / th >
  < th > total < / 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>Total:</td>
  <td>@Model.Cart.ComputeTotalValue().ToString("C")</td>
 </tr>
 </tbody>

</table>

<p>
 < a href = "@ model. Returnurl" > continue shopping</a>
</p>

I think this must be an exciting moment because we have completed this basic function of adding to the shopping cart.

3、 Create a partial view partial view

A partial view is a piece of content embedded in another view and can be reused across views, which helps to reduce duplication, especially when the same data needs to be reused in multiple places.

Create a new one inside shared called_ Booksummary.cshtml view, and sort out the previous details.cshtml code.

Two modified Views:

Details.cshtml


@model Wen.BooksStore.WebUI.Models.BookDetailsViewModels

@{
 ViewBag.Title = "Books";
}

@foreach (var item in Model.Books)
{
 Html.RenderPartial("_BookSummary", item);
}

<div>
 @Html.PageLinks(Model, x => Url.Action("Details", new { pageIndex = x, category = Model.CurrentCategory }))
</div>

_BookSummary.cshtml

@model Wen.BooksStore.Domain.Entities.Book

<div>
 <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 = "+ add to cart" / >
 }

 <br />
 <hr />
</div>

The above is the whole content of this article. I hope it will be helpful to your study, and I hope you can support developpaer.