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

Time:2021-11-22

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

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

In the previous section, we completed two main functions: adding to shopping cart and category navigation. In this section, we will complete the whole shopping cart process and order processing.

The main functions and knowledge points of this series 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 (it is expected that the remaining two articles will be released first on Wednesday (because they are not working 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

Complete shopping cart

Order settlement

1、 Complete shopping cart

In the previous section, the methods of removing and emptying shopping carts have been completed, but the buttons available for user operation have not been placed in the page area. In addition to adding these two buttons, the summary of the shopping cart (used to display the user’s total shopping amount) will also be added at the top of the page.

The following is the cartcontroller code written in the previous section.

/// <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;
 }
 }

1. Add the function of removing books and emptying shopping cart

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 >
  <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 = "- remove" / >
   }
  </td>
  </tr>
 }
 <tr>
  <td> </td>
  <td> </td>
  <td>Total:</td>
  <td>@Model.Cart.ComputeTotalValue().ToString("C")</td>
  <td>
  @using (Html.BeginForm("Clear", "Cart"))
  {
   @Html.HiddenFor(x => x.ReturnUrl)
   < input type = "submit" value = "empty shopping cart" / >
  }
  </td>
 </tr>
 </tbody>

</table>

[note] @ HTML. Hidden (“Id”, item. Book. ID) is used to generate hidden fields. If you directly use @ HTML. Hiddenfor(), the generated name will be item. Book. ID, which will not match the parameters of removefromcart (int ID, string return) in cartcontroller.

The effects displayed are as follows:

2. Add summary: we store a lot of things in the shopping cart. Through the summary, we can display the thumbnail of the total shopping amount. The selected position is displayed in a more prominent position in the upper right corner of the top. Of course, we also need to click the jump button to display all shopping list pages.

Continue to add an action under cartcontroller, named summary, and the return value is a partial view:

/// <summary>
 ///Abstract
 /// </summary>
 /// <returns></returns>
 public PartialViewResult Summary()
 {
  return PartialView(GetCart());
 }

Corresponding summary.cshtml

@model Wen.BooksStore.Domain.Entities.Cart

<div>
 Your shopping cart: @ model. Computetotalvalue() 
 <span>@HTML. Actionlink ("settlement", "checkout", "cart", new {returnurl = request. URL. Pathandquery}, null)</span>
</div>

Corresponding layout page_ Layout.cshtml is modified as follows:

_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>
 @{ Html.RenderAction("Summary", "Cart");}
 < div > Book Mall < / div >
 </div>
 <div>
 @{ Html.RenderAction("Sidebar", "Nav"); }
 </div>
 <div>
 @RenderBody()
 </div>
</body>
</html>

If new things are added, CSS should also be modified:

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%;
}

2、 Order settlement

After shopping, it is the settlement page. The order settlement here does not involve the call of the payment interface, but only the notification in the form of e-mail.

Here, when I design the settlement, I need to ask the user to input some information, such as name, address and email. When I click OK, I will send these input information and shopping list information from the system email to the email you entered. A more intuitive diagram:

1. Add a domain model contact.cs in entities to represent the contact information.

/// <summary>
 ///Contact information
 /// </summary>
 public class Contact
 {
 [required (ErrorMessage = "name cannot be empty")]
 public string Name { get; set; }

 [required (ErrorMessage = "address cannot be empty")]
 public string Address { get; set; }

 [required (ErrorMessage = "mailbox cannot be empty")]
 [regularexpression (@ "(\ W + ([- +.] \ W +) * @ \ W + ([-.] \ W +) * \. \ W + ([-.] \ W +) * \ w \ w)", ErrorMessage = "the email address entered is illegal")]
 public string Email { get; set; }
 }

Cartcontroller.cs adds an action for settlement:

/// <summary>
 ///Settlement
 /// </summary>
 /// <returns></returns>
 public ViewResult Checkout()
 {
  return View(new Contact());
 }

In checkout.cshtml:

@model Wen.BooksStore.Domain.Entities.Contact

<div>
 @using (Html.BeginForm())
 {
 <div>@Html.ValidationSummary()</div>
 < div > Name: @ HTML. Textboxfor (x = > x.name) < / div >
 < div > address: @ HTML. Textboxfor (x = > x.address) < / div >
 < div > email: @ HTML. Textboxfor (x = > x.email) < / div >
 < div > < input type = "submit" value = "submit" / > < / div >
 }

</div>

Model verification is used here_ Layout.cshtml layout page needs to introduce 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>
 @{ Html.RenderAction("Summary", "Cart");}
 < div > Book Mall < / div >
 </div>
 <div>
 @{ Html.RenderAction("Sidebar", "Nav"); }
 </div>
 <div>
 @RenderBody()
 </div>
</body>
</html>

When trying to run, the following page will appear. If the information is not filled in, relevant error prompts will appear:

2. Next, go to the process after “submit”.

Now you also need a component to process orders, create an interface for order processing and an implementation of the interface, and then bind the two through ninject:

/// <summary>
 ///Order processing
 /// </summary>
 public interface IOrderProcessor
 {
 /// <summary>
 ///Process order
 /// </summary>
 /// <param name="cart"></param>
 /// <param name="contact"></param>
 void ProcessOrder(Cart cart, Contact contact);
 }

Establish an entity class that implements this interface to process orders. Instead of calling the payment interface, it simply sends mail through the in BCL.

EmailOrderProcessor.cs:

/// <summary>
 ///Mail order processor
 /// </summary>
 public class EmailOrderProcessor : IOrderProcessor
 {
 /// <summary>
 ///Sender
 /// </summary>
 public static class Sender
 {
  /// <summary>
  ///Account number
  /// </summary>
  Public static string account = "your @ QQ. Com";

  /// <summary>
  ///Code
  /// </summary>
  public static string Password = "xxx";
 }

 /// <summary>
 ///Process order
 /// </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 cannot be empty!");
  }

  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 ($"total amount: {cart. Getcartitems. Sum (x = > x.quantity * x.book. Price)}");
  sb.AppendLine();
  Sb. Appendline ($"contact: {contact. Name} {contact. Address}");

  //Set the sender. The sender must be consistent with the mailbox of the set mail sending server
  var fromAddr = new MailAddress(Sender.Account);
  var message = new MailMessage { From = fromAddr };

  //You can add multiple recipients in the same way as below
  message.To.Add(contact.Email);
  //Set CC
  message.CC.Add(Sender.Account);
  //Set message header
  Message. Subject = "your order is being delivered...";
  //Set message content
  message.Body = sb.ToString();
  //Set the mail sending server. The server is different according to the mailbox you use. You can view it in the corresponding mailbox management background. Here is the QQ

  var client = new SmtpClient("smtp.qq.com", 25)
  {
  Credentials = new NetworkCredential(Sender.Account, Sender.Password),
  EnableSsl = true
  };

  //Set the sender's email account and password
  //Enable SSL, that is, secure sending
  //Send mail
  client.Send(message);
 }

Cartcontroller also needs to be slightly adjusted:

Also, add an additional method named checkout with the [httpost] feature to the cartcontroller:

/// <summary>
 ///Settlement
 /// </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");
 }

When the verification is successful, the interface will be called to send a message, empty the existing shopping cart, and then jump to a specified new view page:

Create a new thanks.cshtml as follows:

Thanks

Don’t forget to add binding. Use Di container to bind the two:

Launch the page and try the effect:

It seems to have succeeded:

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.