侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

一步步打造简单的MVC电商网站BooksStore(4)

php  /  管理员 发布于 8年前   230

一步步打造一个简单的 MVC 电商网站 - BooksStore(四)

本系列的 GitHub地址:https://github.com/liqingwen2015/Wen.BooksStore

《一步步打造一个简单的 MVC 电商网站 - BooksStore(一)》

《一步步打造一个简单的 MVC 电商网站 - BooksStore(二)》

《一步步打造一个简单的 MVC 电商网站 - BooksStore(三)》

《一步步打造一个简单的 MVC 电商网站 - BooksStore(四)》

简介

  上一节我们完成了两个主要功能:完成了整个购物车的流程,以及订单处理(发邮件进行通知),今天我们来学习一下最基本的增删改查,以及登录认证过滤器,加入防 CSRF 攻击,本系列已完结。

  该系列主要功能与知识点如下:

  分类、产品浏览、购物车、结算、CRUD(增删改查) 管理、发邮件、分页、模型绑定、认证过滤器和单元测试等。

【备注】项目使用 VS2015 + C#6 进行开发,有问题请发表在留言区哦,还有,页面长得比较丑,请见谅。

目录

基本的增删改查 CRUD

登录授权认证过滤

基本的增删改查 CRUD

我们创建一个新的控制器进行增删改查功能,AdminController,并添加一个显示所有数据的方法:

/// <summary> /// 后台管理控制器 /// </summary> public class AdminController : Controller { private readonly IBookRepository _bookRepository; public AdminController(IBookRepository bookRepository) {  _bookRepository = bookRepository; } /// <summary> /// 首页 /// </summary> /// <returns></returns> public ActionResult Index() {  return View(_bookRepository.Books); } }

不在沿用之前的布局页了,创建一个新的布局页 _AdmindLayout.cshtml:

<!DOCTYPE html><html><head> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> <link href="https:/article/~/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; }

对应的Index.cshtml:

@model IEnumerable<Wen.BooksStore.Domain.Entities.Book>@{ Layout = "~/Views/Shared/_AdminLayout.cshtml";}<p> @Html.ActionLink("新增", "Edit")</p><table class="table"> <tr> <th>  名称 </th> <th>  描述 </th> <th>  价格 </th> <th>  分类 </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", new { id = item.Id })  @using (Html.BeginForm("Delete", "Admin", FormMethod.Post, new { style = "display:inline;" }))  {   @Html.Hidden("id", item.Id)   <input type="submit" value="删除" />  }  </td> </tr> }</table>

编辑,我把新增和编辑的位置放在一块,使用 id 进行区分,如果 id = 0 就表示新增的信息。

在 AdminCtroller 中添加关于编辑的方法

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

更新存储库中的方法:

IBookRepository.cs

/// <summary> /// 书存储库接口 /// </summary> public interface IBookRepository { /// <summary> /// 书模型集合 /// </summary> IQueryable<Book> Books { get; } /// <summary> /// 保存书 /// </summary> /// <param name="book"></param> /// <returns></returns> int SaveBook(Book book); /// <summary> /// 删除书 /// </summary> /// <param name="id"></param> /// <returns></returns> Book DeleteBook(int id); }

EfBookRepository.cs

/// <summary> /// 书存储库 /// </summary> public class EfBookRepository : IBookRepository { private readonly EfDbContext _context = new EfDbContext(); /// <summary> /// 书模型集合 /// </summary> public IQueryable<Book> Books => _context.Books; /// <summary> /// 保存书 /// </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> /// 删除书 /// </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; } }

需要对 Book 模型加上验证用的特性:

[Table("Book")] public class Book { /// <summary> /// 标识 /// </summary> public int Id { get; set; } /// <summary> /// 名称 /// </summary> [Required(ErrorMessage = "名称不能为空")] public string Name { get; set; } /// <summary> /// 描述 /// </summary> [Required(ErrorMessage = "描述不能为空")] public string Description { get; set; } /// <summary> /// 价格 /// </summary> [Required(ErrorMessage = "价格不能为空")] [Range(0.01, double.MaxValue, ErrorMessage = "请填写合适的价格")] public decimal Price { get; set; } /// <summary> /// 分类 /// </summary> [Required(ErrorMessage = "分类不能为空")] public string Category { get; set; } }

_AdminLayout.cshtml 需要引入验证用的 js(客户端验证):

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

Edit.cshtml

@model Wen.BooksStore.Domain.Entities.Book@{ Layout = "~/Views/Shared/_AdminLayout.cshtml";}<h2>编辑</h2><div> @Html.ValidationSummary() <div> @using (Html.BeginForm()) {  @Html.HiddenFor(x => x.Id)  <table>  <tr>   <td>名称</td>   <td>@Html.TextBoxFor(x => x.Name)</td>  </tr>  <tr>   <td>价格</td>   <td>@Html.TextBoxFor(x => x.Price)</td>  </tr>  <tr>   <td>分类</td>   <td>@Html.TextBoxFor(x => x.Category)</td>  </tr>  <tr>   <td>描述</td>   <td>@Html.TextAreaFor(x => x.Description)</td>  </tr>  </table>  <input type="submit" value="提交" /> } </div></div>

图:错误提示

删除

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

加入提示,我们在新增、编辑和删除时应该加入必要的提示信息,使用TempData。

/Admin/Index.cshtml 下的也要添加:

执行效果:

【备注】TempData 临时数据保存了一条信息,是一个“键/值”字典,类似会话 Session 和 ViewBag,它和 Session 的差别是,在 HTTP 请求结束后会被删除。因为这里使用了 RedirectToAction ,一条重定向指令,会告诉浏览器重定向请求到一个新地址,这时就不能使用 ViewBag,ViewBag 用于在控制器与视图之间传递数据,但它保持数据的时间不能比当前的HTTP 请求长,重定向意味着用户是跨请求的,ViewBag 不能用于跨请求时传递数据。

登录授权认证过滤

上面是一个 Admin 的后台管理操作,不是每一个用户都能够进入管理的,所以现在加入登录授权认证功能,只有成功后,才能进入管理界面。

先在配置文件 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=\"Web\" /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>

  在这里使用的授权认证模式为表单认证,为了简化与数据库的交互操作,采取的是硬编码的形式。如果尚未得到认证,会跳转到 Account/Login 的地址让管理员先进行登录,timeout 表示登录(即认证)成功的保持时长为 2880 分钟(即 48 小时),而 name 表示的就是用户名, password 表示的就是登录密码。  

  这里采用的是授权认证过滤器,我们需要对要认证后才能进入的控制器添加一个特性[Authorize],即对 AdminController 添加该特性。

新建表单认证提供器,一个接口和一个实现:

IAuthProvider.cs:

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

FormsAuthProvider.cs:

/// <summary> /// 表单认证提供者 /// </summary> public class FormsAuthProvider:IAuthProvider { /// <summary> /// 认证 /// </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)  {  //设置认证 Cookie  FormsAuthentication.SetAuthCookie(userName, false);  }  return result; } }

AddBindings() 方法中注册:

/// <summary> /// 添加绑定 /// </summary> private void AddBindings() {  _kernel.Bind<IBookRepository>().To<EfBookRepository>();  _kernel.Bind<IOrderProcessor>().To<EmailOrderProcessor>();  _kernel.Bind<IAuthProvider>().To<FormsAuthProvider>(); }

/// <summary> /// 登录视图模型 /// </summary> public class LoginViewModel { [Required(ErrorMessage = "用户名不能为空")] public string UserName { get; set; } [Required(ErrorMessage = "密码不能为空")] [DataType(DataType.Password)] public string Password { get; set; } }

新建 AccountController

public class AccountController : Controller { private readonly IAuthProvider _authProvider; public AccountController(IAuthProvider authProvider) {  _authProvider = authProvider; } /// <summary> /// 登录 /// </summary> /// <returns></returns> public ActionResult Login() {  return View(); } /// <summary> /// 登录 /// </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("", "账号或用户名有误");  return View(new LoginViewModel()); } }

Login.cshtml 登录页面:

@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>登录</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="https:/article/~/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="http://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script> <![endif]--> <script src="https:/article/~/Scripts/jquery-1.10.2.js"></script> <script src="https:/article/~/Scripts/jquery.validate.js"></script> <script src="https:/article/~/Scripts/jquery.validate.unobtrusive.js"></script></head><body> <div id="wrapper" class="login-page"> <div id="login_form" class="form">  @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 = "用户名" })  @Html.EditorFor(x => x.Password, new { placeholder = "密码", })  <input type="submit" value="登 录" />  } </div> </div></body></html>

【备注】ValidateAntiForgeryToken 特性用于防止跨站请求伪造(CSRF)攻击。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


  • 上一条:
    VS2017添加EF的MVC控制器报错的解决方法
    下一条:
    一步步打造简单的MVC电商网站BooksStore(3)
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • 用Time Warden监控PHP中的代码处理时间(0个评论)
    • 在PHP中使用array_pop + yield实现读取超大型目录功能示例(0个评论)
    • Property Hooks RFC在PHP 8.4中越来越接近现实(0个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-06
    • 2017-07
    • 2017-08
    • 2017-09
    • 2017-11
    • 2017-12
    • 2018-01
    • 2018-02
    • 2018-03
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-09
    • 2021-02
    • 2021-03
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    • 2024-05
    • 2024-06
    • 2024-07
    • 2024-09
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客