欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-11-30 08:36:22
一步步打造一个简单的 mvc 电商网站 - booksstore(四) 本系列的 github地址:https://github.com/liqingwen2015/we...

一步步打造一个简单的 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,并添加一个显示所有数据的方法:

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

/// <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:

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

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

对应的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>

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

编辑,我把新增和编辑的位置放在一块,使用 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");
 }

更新存储库中的方法:

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

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="~/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>编辑</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>

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

图:错误提示

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

删除

/// <summary>
 /// 删除
 /// </summary>
 /// <param name="id"></param>
 /// <returns></returns>
 [httppost]
 public actionresult delete(int id)
 {
  _bookrepository.deletebook(id);
  return redirecttoaction("index");
 }

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

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

/admin/index.cshtml 下的也要添加:

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

执行效果:

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

【备注】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="943239005@qq.com"/>
 </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 添加该特性。

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

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

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

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>();
 }

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

/// <summary>
 /// 登录视图模型
 /// </summary>
 public class loginviewmodel
 {
 [required(errormessage = "用户名不能为空")]
 public string username { get; set; }

 [required(errormessage = "密码不能为空")]
 [datatype(datatype.password)]
 public string password { get; set; }
 }

新建 accountcontroller

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

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="~/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="~/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 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>

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

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

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

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