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

Time:2021-11-21

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

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 last section, we completed two main functions: completing the whole shopping cart process and order processing (sending e-mail for notification). Today, let’s learn the most basic addition, deletion, modification and query, as well as login authentication filter and adding anti CSRF attack. This series is over.

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, etc.

[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

Basic addition, deletion, modification and query CRUD

Login authorization authentication filtering

Basic addition, deletion, modification and query CRUD

We create a new controller to add, delete, modify and query, AdminController, and add a method to display all data:

/// <summary>
 ///Background management controller
 /// </summary>
 public class AdminController : Controller
 {
 private readonly IBookRepository _bookRepository;

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

 /// <summary>
 ///Front page
 /// </summary>
 /// <returns></returns>
 public ActionResult Index()
 {
  return View(_bookRepository.Books);
 }
 }

Instead of using the previous layout page, create a new layout page_ AdmindLayout.cshtml:


<!DOCTYPE html>

<html>
<head>
 <meta name="viewport" content="width=device-width" />
 <title>@ViewBag.Title</title>
 <link href="~/Contents/admin/Site.css" rel="stylesheet" />
</head>
<body>
 <div>
 @RenderBody()
 </div>
</body>
</html>

Site.css


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

Corresponding index.cshtml:

@model IEnumerable<Wen.BooksStore.Domain.Entities.Book>

@{
 Layout = "~/Views/Shared/_AdminLayout.cshtml";
}

<p>
 @Html.actionlink ("new", "Edit")
</p>
<table>
 <tr>
 <th>
  name
 </th>
 <th>
  describe
 </th>
 <th>
  Price
 </th>
 <th>
  classification
 </th>
 <th></th>
 </tr>

 @foreach (var item in Model)
 {
 <tr>
  <td>
  @Html.DisplayFor(modelItem => item.Name)
  </td>
  <td>
  @Html.DisplayFor(modelItem => item.Description)
  </td>
  <td>
  @Html.DisplayFor(modelItem => item.Price)
  </td>
  <td>
  @Html.DisplayFor(modelItem => item.Category)
  </td>
  <td>
  @HTML. Actionlink ("Edit", "Edit", new {id = item. ID})
  @using (Html.BeginForm("Delete", "Admin", FormMethod.Post, new { style = "display:inline;" }))
  {
   @Html.Hidden("id", item.Id)
   < input type = "submit" value = "delete" / >
  }
  </td>
 </tr>
 }

</table>

For editing, I put the new and edited positions together and use ID to distinguish them. If id = 0, it means the new information.

Add methods about editing in adminctroller

/// <summary>
 ///Edit
 /// </summary>
 /// <param name="id"></param>
 /// <returns></returns>
 public ActionResult Edit(int id = 0)
 {
  if (id == 0)
  {
  return View(new Book());
  }

  var model = _bookRepository.Books.FirstOrDefault(x => x.Id == id);
  return View(model);
 }

 /// <summary>
 ///Edit
 /// </summary>
 /// <param name="book"></param>
 /// <returns></returns>
 [HttpPost]
 public ActionResult Edit(Book book)
 {
  if (!ModelState.IsValid)
  {
  return View(book);
  }

  _bookRepository.SaveBook(book);
  return RedirectToAction("Index");
 }

Update methods in the repository:

IBookRepository.cs

/// <summary>
 ///Book repository interface
 /// </summary>
 public interface IBookRepository
 {
 /// <summary>
 ///Book model set
 /// </summary>
 IQueryable<Book> Books { get; }

 /// <summary>
 ///Preservation book
 /// </summary>
 /// <param name="book"></param>
 /// <returns></returns>
 int SaveBook(Book book);

 /// <summary>
 ///Delete book
 /// </summary>
 /// <param name="id"></param>
 /// <returns></returns>
 Book DeleteBook(int id);
 }

EfBookRepository.cs

/// <summary>
 ///Book repository
 /// </summary>
 public class EfBookRepository : IBookRepository
 {
 private readonly EfDbContext _context = new EfDbContext();

 /// <summary>
 ///Book model set
 /// </summary>
 public IQueryable<Book> Books => _context.Books;

 /// <summary>
 ///Preservation book
 /// </summary>
 /// <param name="book"></param>
 /// <returns></returns>
 public int SaveBook(Book book)
 {
  if (book.Id == 0)
  {
  _context.Books.Add(book);
  }
  else
  {
  var model = _context.Books.Find(book.Id);

  if (model==null)
  {
   return 0;
  }

  model.Category = book.Category;
  model.Description = book.Description;
  model.Name = book.Name;
  model.Price = book.Price;
  }

  return _context.SaveChanges();
 }

 /// <summary>
 ///Delete book
 /// </summary>
 /// <param name="id"></param>
 /// <returns></returns>
 public Book DeleteBook(int id)
 {
  var model = _context.Books.Find(id);

  if (model == null)
  {
  return null;
  }

  _context.Books.Remove(model);
  _context.SaveChanges();

  return model;
 }
 }

You need to add verification features to the book model:

[Table("Book")]
 public class Book
 {
 /// <summary>
 ///Identification
 /// </summary>
 public int Id { get; set; }

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

 /// <summary>
 ///Description
 /// </summary>
 [required (ErrorMessage = "description cannot be empty")]
 public string Description { get; set; }

 /// <summary>
 ///Price
 /// </summary>
 [required (ErrorMessage = "price cannot be empty")]
 [range (0.01, double.maxvalue, ErrorMessage = "please fill in the appropriate price")]
 public decimal Price { get; set; }

 /// <summary>
 ///Classification
 /// </summary>
 [required (ErrorMessage = "classification cannot be empty")]
 public string Category { get; set; }
 }

_ Adminlayout.cshtml needs to introduce JS (client authentication) for authentication:


<script src="~/Scripts/jquery-1.10.2.js"></script>
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>

Edit.cshtml

@model Wen.BooksStore.Domain.Entities.Book

@{
 Layout = "~/Views/Shared/_AdminLayout.cshtml";
}

<h2>Edit</h2>

<div>
 @Html.ValidationSummary()

 <div>
 @using (Html.BeginForm())
 {
  @Html.HiddenFor(x => x.Id)
  <table>
  <tr>
   <td>Name</td>
   <td>@Html.TextBoxFor(x => x.Name)</td>
  </tr>
  <tr>
   <td>Price</td>
   <td>@Html.TextBoxFor(x => x.Price)</td>
  </tr>
  <tr>
   <td>Classification</td>
   <td>@Html.TextBoxFor(x => x.Category)</td>
  </tr>
  <tr>
   <td>Description</td>
   <td>@Html.TextAreaFor(x => x.Description)</td>
  </tr>
  </table>
  < input type = "submit" value = "submit" / >
 }
 </div>
</div>

Figure: error message

delete

/// <summary>
 ///Delete
 /// </summary>
 /// <param name="id"></param>
 /// <returns></returns>
 [HttpPost]
 public ActionResult Delete(int id)
 {
  _bookRepository.DeleteBook(id);
  return RedirectToAction("Index");
 }

Add prompt. We should add necessary prompt information when adding, editing and deleting, and use tempdata.

/Under admin / index.cshtml, you should also add:

Execution effect:

[note] tempdata temporary data stores a piece of information. It is a “key / value” dictionary, similar to session and viewbag. The difference between tempdata and session is that it will be deleted after the HTTP request ends. Because redirecttoaction is used here, a redirection instruction will tell the browser that the redirection request is to a new address. At this time, viewbag cannot be used. Viewbag is used to transfer data between the controller and the view, but its data retention time cannot be longer than the current HTTP request. Redirection means that the user is cross request, Viewbag cannot be used to pass data across requests.

Login authorization authentication filtering

The above is a background management operation of admin. Not every user can enter the management, so now add the login authorization authentication function. You can enter the management interface only after success.

First add in the configuration file webconfig.cs


<authentication mode="Forms">
 <forms loginUrl="~/Account/Login" timeout="2880">
 <credentials passwordFormat="Clear">
  <user name="admin" password="123"/>
 </credentials>
 </forms>
</authentication>

WebConfig.cs


<?xml version="1.0" encoding="utf-8"?>
<!--
 For more information on how to configure your ASP.NET application, please visit
 http://go.microsoft.com/fwlink/?LinkId=301880
 -->
<configuration>
 <connectionStrings>
 <add name="EfDbContext" connectionString="server=.;database=TestDb;uid=sa;pwd=123" providerName="System.Data.SqlClient"/>
 </connectionStrings>

 <appSettings>
 <add key="webpages:Version" value="3.0.0.0"/>
 <add key="webpages:Enabled" value="false"/>
 <add key="ClientValidationEnabled" value="true"/>
 <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
 <add key="SendEmailName" value="[email protected]"/>
 </appSettings>
 <system.web>
 <authentication mode="Forms">
 <forms loginUrl="~/Account/Login" timeout="2880">
 <credentials passwordFormat="Clear">
  <user name="admin" password="123"/>
 </credentials>
 </forms>
 </authentication>
 <compilation debug="true" targetFramework="4.6.1"/>
 <httpRuntime targetFramework="4.6.1"/>
 <httpModules>
 <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web"/>
 </httpModules>
 </system.web>
 <runtime>
 <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
 <dependentAssembly>
 <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35"/>
 <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0"/>
 </dependentAssembly>
 <dependentAssembly>
 <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35"/>
 <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0"/>
 </dependentAssembly>
 <dependentAssembly>
 <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35"/>
 <bindingRedirect oldVersion="1.0.0.0-5.2.3.0" newVersion="5.2.3.0"/>
 </dependentAssembly>
 </assemblyBinding>
 </runtime>
 <system.codedom>
 <compilers>
 <compiler language="c#;cs;csharp" extension=".cs"
 type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
 warningLevel="4" compilerOptions="/langversion:6 /nowarn:1659;1699;1701"/>
 <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb"
 type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
 warningLevel="4" compilerOptions="/langversion:14 /nowarn:41008 /define:_MYTYPE=\&quot;Web\&quot; /optionInfer+"/>
 </compilers>
 </system.codedom>
 <system.webServer>

 <validation validateIntegratedModeConfiguration="false"/>
 <modules>
 <remove name="ApplicationInsightsWebTracking"/>
 <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web"
 preCondition="managedHandler"/>
 </modules>
 </system.webServer>
</configuration>

The authorization authentication mode used here is form authentication. In order to simplify the interaction with the database, it adopts the form of hard coding. If you have not been authenticated, you will jump to the account / login address and ask the administrator to log in first. Timeout indicates that the login (i.e. authentication) is successful for 2880 minutes (i.e. 48 hours), while name indicates the user name and password indicates the login password.

The authorization authentication filter is used here. We need to add a feature [authorize] to the controller to be authenticated before entering, that is, to the AdminController.

Create a new form authentication provider, an interface and an implementation:

IAuthProvider.cs:

public interface IAuthProvider
 {
 /// <summary>
 ///Authentication
 /// </summary>
 /// <param name="userName"></param>
 /// <param name="password"></param>
 /// <returns></returns>
 bool Auth(string userName, string password);
 }

FormsAuthProvider.cs:

/// <summary>
 ///Form authentication provider
 /// </summary>
 public class FormsAuthProvider:IAuthProvider
 {
 /// <summary>
 ///Authentication
 /// </summary>
 /// <param name="userName"></param>
 /// <param name="password"></param>
 /// <returns></returns>
 public bool Auth(string userName, string password)
 {
  var result = FormsAuthentication.Authenticate(userName, password);

  if (result)
  {
  //Set authentication cookie
  FormsAuthentication.SetAuthCookie(userName, false);
  }

  return result;
 }
 }

Register in addbindings() method:

/// <summary>
 ///Add binding
 /// </summary>
 private void AddBindings()
 {
  _kernel.Bind<IBookRepository>().To<EfBookRepository>();
  _kernel.Bind<IOrderProcessor>().To<EmailOrderProcessor>();
  _kernel.Bind<IAuthProvider>().To<FormsAuthProvider>();
 }

/// <summary>
 ///Login view model
 /// </summary>
 public class LoginViewModel
 {
 [required (ErrorMessage = "user name cannot be empty")]
 public string UserName { get; set; }

 [required (ErrorMessage = "password cannot be empty")]
 [DataType(DataType.Password)]
 public string Password { get; set; }
 }

New accountcontroller

public class AccountController : Controller
 {
 private readonly IAuthProvider _authProvider;

 public AccountController(IAuthProvider authProvider)
 {
  _authProvider = authProvider;
 }

 /// <summary>
 ///Login
 /// </summary>
 /// <returns></returns>
 public ActionResult Login()
 {
  return View();
 }

 /// <summary>
 ///Login
 /// </summary>
 /// <param name="model"></param>
 /// <returns></returns>
 [HttpPost]
 [ValidateAntiForgeryToken]
 public ActionResult Login(LoginViewModel model)
 {
  if (!ModelState.IsValid)
  {
  return View(new LoginViewModel());
  }

  var result = _authProvider.Auth(model.UserName, model.Password);
  if (result) return RedirectToAction("Index", "Admin");

  Modelstate. Addmodelerror ("", "wrong account or user name");
  return View(new LoginViewModel());
 }
 }

Login.cshtml login page:

@model Wen.BooksStore.WebUI.Models.LoginViewModel
@{
 Layout = null;
}

<!DOCTYPE html>
<html lang="zh">
<head>
 <meta charset="UTF-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 < title > login < / Title >
 @*<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">*@
 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
 @*<link href="~/Contents/Login/css/htmleaf-demo.css" rel="stylesheet" />*@
 <style type="text/css">
 @@import url(https://fonts.googleapis.com/css?family=Roboto:300);

 .login-page {
  margin: auto;
  padding: 8% 0 0;
  width: 360px;
 }

 .form {
  background: #FFFFFF;
  box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);
  margin: 0 auto 100px;
  max-width: 360px;
  padding: 45px;
  position: relative;
  text-align: center;
  z-index: 1;
 }

 .form input {
  background: #f2f2f2;
  border: 0;
  box-sizing: border-box;
  font-family: "Roboto", sans-serif;
  font-size: 14px;
  margin: 0 0 15px;
  outline: 0;
  padding: 15px;
  width: 100%;
 }

 .form button {
  -webkit-transition: all 0.3 ease;
  background: #4CAF50;
  border: 0;
  color: #FFFFFF;
  cursor: pointer;
  font-family: "Microsoft YaHei", "Roboto", sans-serif;
  font-size: 14px;
  outline: 0;
  padding: 15px;
  text-transform: uppercase;
  transition: all 0.3 ease;
  width: 100%;
 }

 .form button:hover, .form button:active, .form button:focus { background: #43A047; }

 .form .message {
  color: #b3b3b3;
  font-size: 12px;
  margin: 15px 0 0;
 }

 .form .message a {
  color: #4CAF50;
  text-decoration: none;
 }

 .form .register-form { display: none; }

 .container {
  margin: 0 auto;
  max-width: 300px;
  position: relative;
  z-index: 1;
 }

 .container:before, .container:after {
  clear: both;
  content: "";
  display: block;
 }

 .container .info {
  margin: 50px auto;
  text-align: center;
 }

 .container .info h1 {
  color: #1a1a1a;
  font-size: 36px;
  font-weight: 300;
  margin: 0 0 15px;
  padding: 0;
 }

 .container .info span {
  color: #4d4d4d;
  font-size: 12px;
 }

 .container .info span a {
  color: #000000;
  text-decoration: none;
 }

 .container .info span .fa { color: #EF3B3A; }

 body {
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  background: #76b852; /* fallback for old browsers */
  background: -webkit-linear-gradient(right, #76b852, #8DC26F);
  background: -moz-linear-gradient(right, #76b852, #8DC26F);
  background: -o-linear-gradient(right, #76b852, #8DC26F);
  background: linear-gradient(to left, #76b852, #8DC26F);
  font-family: "Roboto", sans-serif;
 }
 </style>
 <!--[if IE]>
 <script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script>
 <![endif]-->
 <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>
 <div>
  @using (Html.BeginForm("Login", "Account", FormMethod.Post, new { @class = "login-form" }))
  {
  <span style="float: left; color: red;">@Html.ValidationSummary()</span>
  @Html.AntiForgeryToken()
  @Html.textboxfor (x = > x.username, new {placeholder = "username"})
  @HTML. Editorfor (x = > x.password, new {placeholder = "password",})

  < input type = "submit" value = "login" / >
  }

 </div>
 </div>

</body>
</html>

[note] the validateantiforgerytoken feature is used to prevent cross Site Request Forgery (CSRF) attacks.

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.