你的位置:首页 > ASP.net教程

[ASP.net教程]31、三层架构、AJAX+FormsAuthentication实现登陆


三层架构

前段时间公司要求修改一个网站,打开后我疯了,一层没有都是调用的DB接口,遍地的SQL语句,非常杂乱。

什么是三层架构?

三层架构是将整个项目划分为三个层次:表现层、业务逻辑层、数据访问层。目的为了高内聚低耦合思想。

三层结构

表现层(UI):接受用户请求,数据的返回呈现。

业务逻辑层(BLL ):用来处理业务逻辑,处理用户提交的数据。

数据访问层(DAL):用来与数据库进行交互,处理增、删、改、差。

实体类对象(Model):用来存储实体类。

三层关系

UI表现层:接收用户输入的数据,并传递给业务逻辑层。

BLL业务逻辑层:把接收到的数据传递给数据访问层,并返回结果给UI层。

DAL数据访问层:负责与数据库进行交互,并将结果返回给BLL层。

什么时候需要分层?

当项目过大的时候可以分层开发,这样可以每个人负责的不同,共同进行开发。如果一个项目非常小的话,独立开发可以不分层。

操作步骤:

1、创建结构

新增三个类库分别是:BLL、DAL、Model,并建立一个UI层(winform 或 webform 或 MVC项目)设置启动项为UI层。

这里随个人喜欢可以再加一个Common层,负责处理其他常用方法。

2、引用关系

DAL要用到Model,BLL要用到DAL、CL和Model,UI要用到BLL、Model和CL。相互引用即可。

实现登陆

登陆是所有系统的入口,相信大家一般都用session,本例使用FormsAuthentication微软提供的身份认证,存储在cookie中。

假设有如下管理员表:

mif_id:唯一标识,lever等级,usernmae,psw账号密码,trueName姓名,createTime创建日期,isLock是否锁定,Power权限。

编写实体类

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5  6 namespace Model 7 { 8   /// <summary> 9   /// 管理员实体类10   /// </summary>11   public class ManagerInfo12   {13     /// <summary>14     /// 标识ID15     /// </summary>16     public int mif_id { get; set; }17     /// <summary>18     /// 暂时没用19     /// </summary>20     public int mif_pid { get; set; }21     /// <summary>22     /// 管理员等级23     /// </summary>24     public int mif_lever { get; set; }25     /// <summary>26     /// 账号27     /// </summary>28     public string mif_userName { get; set; }29     /// <summary>30     /// 密码31     /// </summary>32     public string mif_psw { get; set; }33     /// <summary>34     /// 姓名35     /// </summary>36     public string mif_trueName { get; set; }37     /// <summary>38     /// 创建时间39     /// </summary>40     public DateTime mif_createTime { get; set; }41     /// <summary>42     /// 是否锁定43     /// </summary>44     public bool mif_isLock { get; set; }45     /// <summary>46     /// 权限47     /// </summary>48     public string mif_power { get; set; }49   }50 }

ManagerInfo.cs

习惯性编写完Model重新生成下Model层,检查是否有错误。

编写数据访问层

引入sqlHelper文件(上一文章中找),修改UI中的web.config添加数据库连接字符串,并在sqlHelper中修改相关名称,注意引入System.configuration.dll。

编写操作ManagerInfo的数据访问类

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using Model; 6 using System.Data.SqlClient; 7  8 namespace DAL 9 {10   /// <summary>11   /// 管理员数据访问类12   /// </summary>13   public class ManagerInfoDal14   {15     /// <summary>16     /// 通过账号查找17     /// </summary>18     /// <param name="userName"></param>19     /// <returns></returns>20     public static ManagerInfo row(string userName)21     {22       string sql = "select mif_id,mif_lever,mif_userName,mif_psw,mif_trueName,mif_createTime,mif_isLock from managerInfo where mif_userName = @uid";23       var v = new ManagerInfo();24       using (var dr = SQLHelper.ExecuteReader(sql, new SqlParameter("@uid", userName)))25       {26         if (dr.Read())27         {28           v.mif_id = Convert.ToInt32(dr["mif_id"]);29           v.mif_lever = Convert.ToInt32(dr["mif_lever"]);30           v.mif_userName = dr["mif_userName"].ToString();31           v.mif_psw = dr["mif_psw"].ToString();32           v.mif_trueName = dr["mif_trueName"].ToString();33           v.mif_createTime = Convert.ToDateTime(dr["mif_createTime"]);34           v.mif_isLock = Convert.ToBoolean(dr["mif_isLock"]);35         }36       }37       return v;38     }39   }40 }

ManagerInfoDal.cs

编写业务逻辑层

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using DAL; 6 using System.Web.Security; 7 using System.Web; 8 using CL; 9 using Model;10 11 namespace BLL12 {13   /// <summary>14   /// 管理员15   /// </summary>16   public class ManagerInfoBll17   {18     /// <summary>19     /// 登陆20     /// </summary>21     /// <param name="userName"></param>22     /// <param name="passWord"></param>23     /// <param name="remember">是否记住</param>24     /// <returns></returns>25     public static string login(string userName, string passWord, string remember)26     {27       var v = ManagerInfoDal.row(userName);28       if (v.mif_id > 0)29       {30         if (v.mif_isLock == false)31         {32           if (v.mif_psw.Equals(createMD5.getMD5(passWord)))33           {34             var expires = DateTime.Now.AddMinutes(20);35             if (remember.Equals("on"))36             {37               expires = DateTime.Now.AddDays(8);38             }39             //将登陆的用户存储在Ticket中40             var ticket = new FormsAuthenticationTicket(0, userName, DateTime.Now, expires, true, userName);41             //使用Encrypt方法加密Ticket,并存储在cookie中42             var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));43             //防止浏览器攻击窃取、伪造cookie信息44             cookie.HttpOnly = true;45             cookie.Expires = expires;46             HttpContext.Current.Response.Cookies.Add(cookie);47             return "0";48           }49           return "1";50         }51         return "2";52       }53       return "3";54     }55     public static ManagerInfo row(string userName)56     {57       return ManagerInfoDal.row(userName);58     }59     /// <summary>60     /// 查询权限(通过数字查询名字)61     /// </summary>62     /// <param name="userRole">权限数字</param>63     /// <returns></returns>64     public static string getRole(int userRole)65     {66       switch (userRole)67       {68         case 0:69           return "编辑";70         case 1:71           return "管理员";72         case 2:73           return "系统";74         default:75           return "暂无";76       }77     }78   }79 }

ManagerInfoBll.cs

管理员等级这里建议写成枚举,本人较懒。

FormsAuthenticationTicket这个东西比传统的session和cookie的好处就是 可以任意目录设置他的访问权限,如果没有登陆直接跳出到登陆页面。而不用每个页面一开始都认证一番~

还有 cookie.HttpOnly 在使用cookie时这一项最好设置一下,否则可能会客户端模拟cookie进行攻击。

编写通用层

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Security.Cryptography; 5 using System.Text; 6  7 namespace CL 8 { 9   public class createMD510   {11     public static string getMD5(string str)12     {13       var md5 = MD5.Create();14       var buffer = Encoding.Default.GetBytes(str);15       var mdbuffer = md5.ComputeHash(buffer);16       StringBuilder result = new StringBuilder();17       for (int i = 0; i < mdbuffer.Length; i++)18       {19         result.Append(mdbuffer[i].ToString("x2"));20       }21       return result.ToString();22     }23   }24 }

createMD5

生成MD5的代码,可不用。

 1 using System; 2 using System.Collections.Generic; 3 using System.Drawing; 4 using System.Drawing.Drawing2D; 5 using System.Drawing.Imaging; 6 using System.IO; 7 using System.Linq; 8 using System.Text; 9 using System.Web;10 11 namespace CL12 {13   public static class createVlidate14   {15     /// <summary>16     /// 字符17     /// </summary>18     /// <param name="len">几位</param>19     /// <returns></returns>20     public static string validation(int cd)21     {22       var ran = new Random();23       int num, tem;24       string rtuStr = "";25       for (int i = 0; i < cd; i++)26       {27         num = ran.Next();28         if (i % 2 == 1)29           tem = num % 10 + '0'; //数字30         else31           tem = num % 26 + 'A'; //字母32         rtuStr += Convert.ToChar(tem).ToString();33       }34       //写入cookie35       HttpCookie cookie = new HttpCookie("check");36       cookie.Value = rtuStr.ToLower();37       HttpContext.Current.Response.Cookies.Add(cookie);38       return rtuStr;39     }40 41     /// <summary>42     /// 生成图像43     /// </summary>44     /// <param name="check">字符</param>45     public static byte[] drawImg(string check)46     {47       Bitmap img = new Bitmap(90, 34);48       var ht = Graphics.FromImage(img);49       ht.Clear(Color.White);50       ht.DrawLine(new Pen(Color.SpringGreen), 1, 1, 90, 34);51       Font font = new Font("微软雅黑", 20, FontStyle.Bold);52       var jianbian = new LinearGradientBrush(new Rectangle(0, 0, img.Width, img.Height), Color.Teal, Color.Snow, 2f, true);53       ht.DrawString(check, font, jianbian, 0, 0);54       ht.DrawRectangle(new Pen(Color.Aqua), 0, 0, img.Width - 1, img.Height - 1);55       MemoryStream ms = new MemoryStream();56       img.Save(ms, ImageFormat.Jpeg);57       ht.Dispose();58       img.Dispose();59       return ms.ToArray();60     }61   }62 }

createValidate.cs

生成验证码的,可自己写。

编写展现层

首先需要在web.config中设置authentication节点为Forms验证模式,然后就可以在目录中任意设置访问级别了。

<authentication mode="Forms"> <forms loginUrl="~/manage/index.html" timeout="10080" defaultUrl="~/manage/Net/" /></authentication>

登陆页面代码

 1 <!DOCTYPE html> 2 <html ="http://www.w3.org/1999/xhtml"> 3 <head> 4   <meta charset="utf-8" /> 5   <meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1" /> 6   <meta http-equiv="X-UA-Compatible" content="IE=9" /> 7   <meta name="viewport" content="width=device-width, initial-scale=1"> 8   <title>汉之殇管理系统</title> 9   <link href="css/bootstrap.css" rel="stylesheet" />10   <link href="css/admin.css" rel="stylesheet" />11   <!--[if lt IE 9]>12    <script src="http://apps.bdimg.com/libs/html5shiv/3.7/html5shiv.min.js"></script>13    <script src="http://apps.bdimg.com/libs/respond.js/1.4.2/respond.min.js"></script>14   <![endif]-->15 </head>16 <body class="login">17   <nav class="navbar navbar-default navbar-static-top">18     <div class="container">19       <div class="navbar-header">20         <a href="login.html" class="navbar-brand">汉之殇管理系统</a>21       </div>22     </div>23   </nav>24   <div class="container">25     <div class="panel panel-default">26       <div class="panel-body">27         <div id="ts"></div>28         <h4 class="page-header">登陆</h4>29         <div class="form-group has-feedback">30           <label class="sr-only" for="userName">账号</label>31           <input type="text" id="userName" class="form-control" placeholder="账号" maxlength="50" autofocus />32           <span class="glyphicon glyphicon-user form-control-feedback" aria-hidden="true"></span>33         </div>34         <div class="form-group has-feedback">35           <label class="sr-only" for="passWord">密码</label>36           <input type="password" id="passWord" class="form-control" maxlength="50" placeholder="密码" />37           <span class="glyphicon glyphicon-lock form-control-feedback" aria-hidden="true"></span>38         </div>39         <div class="form-group has-feedback">40           <label class="sr-only" for="validateCode">验证码</label>41           <input type="text" id="validateCode" class="form-control validateCode" placeholder="验证码" maxlength="4" />42           <img src="checkLogin/ValidateCode.ashx" id="img" onclick="changeCode()" class="validateImg">43           <a href="javascript:changeCode()">看不清,换一张</a>44         </div>45         <div class="form-group">46           <input type="checkbox" id="remember" checked="checked" /> <span class="form-control-static">记住我 </span>47           <button id="submit" type="button" class="btn btn-primary col-xs-offset-4" style="width:40%">登录</button>48         </div>49       </div>50     </div>51   </div>52   <nav class="navbar navbar-default navbar-fixed-bottom">53     <div class="container">54       <div class="navbar-header">55         <p class="navbar-text">&copy; 2015 汉之殇 版权所有</p>56       </div>57     </div>58   </nav>59   <script src="js/jquery-1.7.2.min.js"></script>60   <script src="js/status.js"></script>61   <script src="js/login.js"></script>62 </body>63 </html>

index.html

验证码

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using CL; 6  7 namespace UI.manage.checkLogin 8 { 9   /// <summary>10   /// ValidateCode 的摘要说明11   /// </summary>12   public class ValidateCode : IHttpHandler13   {14     public void ProcessRequest(HttpContext context)15     {16       context.Response.ContentType = "image/jpeg";17 18       var check = createVlidate.validation(4);19       byte[] buffer = createVlidate.drawImg(check);20 21       context.Response.BinaryWrite(buffer);22     }23 24     public bool IsReusable25     {26       get27       {28         return false;29       }30     }31   }32 }

ValidateCode.ashx

登陆用的JS

 1 $(function () { 2   $.get("checkLogin/Validate.ashx?rand=" + Math.random(0, 1), function (data) { 3     if (data == "0") { 4       location.href="http://www.cnblogs.com//manage/Net/" 5     } 6   }); 7  8   if (top.location != self.location) { 9     top.location = self.location;10   }11 12   var lj = window.location.toString();13   if (lj.lastIndexOf("?") != -1) {14     status("info", "请先登录!");15     $("#userName").focus();16   }17 18   $(".login").height(document.documentElement.clientHeight);19   document.onkeydown = function (e) {20     var ev = document.all ? window.event : e;21     if (ev.keyCode == 13) {22       $("#submit").click();23     }24   }25 });26 $("#submit").click(function () {27   //$("#submit").attr("disabled", "true");28   var userName = $("#userName").val();29   var passWord = $("#passWord").val();30   var validateCode = $("#validateCode").val();31   var remember = $("#remember:checked").val();32   if (userName != "") {33     if (passWord != "") {34       if (validateCode != "") {35         if (validateCode.toLowerCase() == getCode()) {36           $.post("checkLogin/Validate.ashx", { userName: userName, passWord: passWord, remember: remember }, function (data) {37             changeCode();38             if (data == "0") {39               location.href = "Net/";40             } else if (data == "1") {41               status("no", "登陆失败,密码错误!");42               $("#passWord").focus();43               return false;44             } else if (data == "2") {45               status("no", "登陆失败,账号已被禁止登陆!");46               $("#userName").focus();47               return false;48             } else {49               status("no", "登陆失败,账号不存在!");50               $("#userName").focus();51               return false;52             }53           });54           return false;55         }56         status("no", "验证码不正确!");57         $("#validateCode").focus();58         return false;59       }60       status("info", "请输入验证码!");61       $("#validateCode").focus();62       return false;63     }64     status("info", "请输入您的密码!");65     $("#passWord").focus();66     return false;67   }68   status("info", "请输入您的账号!");69   $("#userName").focus();70   return false;71 });72 function changeCode() {73   $("#img").attr("src", $("#img").attr("src") + "?");74 }75 function getCode() {76   var cookies = document.cookie.split(";");77   for (var i = 0; i < cookies.length; i++) {78     var validate = cookies[i].split("=");79     if (validate[0].replace(/(^\s*)|(\s*$)/g, "") == "check") {80       return validate[1].replace(/(^\s*)|(\s*$)/g, "");81     }82   }83 }

login.js

验证的一般处理程序

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5  6 namespace UI.manage.checkLogin 7 { 8   /// <summary> 9   /// Validate 的摘要说明10   /// </summary>11   public class Validate : IHttpHandler12   {13 14     public void ProcessRequest(HttpContext context)15     {16       var userName = context.Request["userName"];17       var passWord = context.Request["passWord"];18       var remember = context.Request["remember"] == null ? "" : context.Request["remember"];19       if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(passWord))20       {21         var result = BLL.ManagerInfoBll.login(userName, passWord, remember);22         context.Response.Write(result);23       }24       else25       {26         if (context.Request.IsAuthenticated)27         {28           context.Response.Write("0");29         }30       }31     }32 33     public bool IsReusable34     {35       get36       {37         return false;38       }39     }40   }41 }

Validate.ashx

最后附上效果图如下:

登陆后使用 Page.User.Identity.Name 获取用户标识。

如下:

获取信息及退出登陆如下:

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.Security; 6 using System.Web.UI; 7 using System.Web.UI.WebControls; 8 using BLL; 9 using Model;10 11 namespace UI.manage.Net12 {13   public partial class _default : System.Web.UI.Page14   {15     protected void Page_Load(object sender, EventArgs e)16     {17       if (!IsPostBack)18       {19         var userName = Page.User.Identity.Name;20         int roleId = ManagerInfoBll.row(userName).mif_lever;21 22         this.userId.InnerText = userName;23         this.userRole.InnerText = ManagerInfoBll.getRole(roleId);24       }25 26       var logout = Request["logout"];27       if (!string.IsNullOrEmpty(logout))28       {29         FormsAuthentication.SignOut();30         Response.Redirect("../index.html");31       }32     }33   }34 }

default.aspx.cs

最后 在禁止匿名访问的目录下 新增一个web.config 内容如下

<?"1.0"?><configuration> <system.web>  <authorization>   <deny users="?"/>  </authorization> </system.web></configuration>

这样,当记住凭证后直接访问登陆或者该目录都可以直接跳转,如果点击退出或过期后,则自动跳出到登陆页面中。至此大功告成~