一步步打造简单的MVC电商网站BooksStore(4)
一步步打造一个简单的 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="~/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="~/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>
图:错误提示
删除
/// <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="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 添加该特性。
新建表单认证提供器,一个接口和一个实现:
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="~/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>
【备注】validateantiforgerytoken 特性用于防止跨站请求伪造(csrf)攻击。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 使用C#调用系统API实现内存注入的代码
下一篇: python实现在一个画布上画多个子图