ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ASP.NET WebForms三层聊天室实战:IIS部署、Session优化与伪实时轮询

ASP.NET WebForms三层聊天室实战:IIS部署、Session优化与伪实时轮询 简介这是一份基于ASP.NET Web Forms开发的三层架构在线聊天室源码面向Web开发初学者与.NET技术实践者适用于学习B/S架构通信逻辑、数据库交互及前后端协同开发。资源采用标准三层结构表现层aspx/cs、业务逻辑层App_Code、数据访问层SQL Server包含用户登录、消息发送与实时显示等核心功能可快速部署为网站留言或简易即时通讯系统。压缩包共19个文件涵盖4个关键页面Login.aspx、Main.aspx、Speak.aspx、ShowMessage.aspx、7个C#后台逻辑文件、1个SQL Server数据库文件.mdf/.ldf、1个Web.config配置及CSS样式与GIF/JPG界面资源整体仅115KB轻量易上手。已有94人下载学习读者可直接运行调试掌握三层解耦设计思想、ViewState状态管理、SQL Server本地数据库集成及基础AJAX局部刷新实现方式。1. 这不是“老古董演示项目”一个能跑在 IIS 10 WinServer 2016 上的 Asp.net WebForms 三层聊天室真能接真实用户留言、抗住百人并发、不崩在 Session 和 ViewState 上你搜“Asp.net 聊天室源码”十有八九点开是 2012 年的截图、没注释的 .aspx 堆砌、Web.config 里还写着compilation debugtrue targetFramework4.0/——这种代码扔进生产环境第一轮压力测试就给你弹出HttpException: Validation of viewstate MAC failed。但这份mychatroom源码不一样它用标准三层架构UI / BLL / DAL切得干净所有数据库操作走SqlHelper封装Speak.aspx.cs里连Response.Redirect都加了endResponse:false防止线程中止异常ShowMessage.aspx的分页逻辑直接手写 SQLROW_NUMBER() OVER (ORDER BY id DESC)而非依赖 GridView 自动分页——这意味着它不是教学玩具而是当年某企业内网客服系统裁剪下来的可运行实体。它适合三类人需要快速搭一个带登录、留言、实时刷新伪实时靠定时器轮询的内部沟通页面的运维/行政正在学 Asp.net WebForms 三层拆分但卡在“怎么让 BLL 层不直接 new DAL 类”的初学者还有那些被客户硬性要求用 .NET Framework 4.7.2 SQL Server 2016 部署旧系统的外包工程师。别被“三层”二字吓退——它没用 Entity Framework没碰 WCF所有技术栈都钉死在 WebForms 生态里反而成了最稳的落地选择。2. 从解压到上线五步跑通 mychatroom关键在 Web.config 的三处硬编码和 App_Code 的命名空间对齐2.1 解压后目录结构与核心文件职责定位拿到mychatroom.rar后解压得到根目录下 5 个关键文件夹/文件App_Code/存放SqlHelper.cs封装 SqlConnection/SqlCommand、UserBLL.cs用户登录校验业务逻辑、MessageDAL.cs增删查留言数据访问层Styles/仅main.css控制登录框宽度、消息气泡圆角、滚动条隐藏::-webkit-scrollbar {display:none}Images/logo.png和send_btn.png无 SVG 或响应式适配纯固定尺寸.aspx 页面组Login.aspx表单 POST 到自身校验后写 Session[uid]、Main.aspx主框架iframe 套ShowMessage.aspxSpeak.aspx、Speak.aspx含 TextBox Button点击触发Speak.aspx.cs中的InsertMessage()根目录Web.config这才是真正的“心脏”——它控制着连接字符串、编译目标框架、Session 超时、甚至customErrors是否开启提示不要急着双击Login.aspx在浏览器打开WebForms 必须走 IIS 或 Visual Studio 内置 IIS Express 才能解析.aspx直接用文件协议会报 404 或空白页。2.2 修改 Web.config 的三处硬编码数据库连接、Session 超时、自定义错误页打开Web.config找到connectionStrings节点原始内容是add nameconnStr connectionStringData Source.;Initial Catalogmychat;User IDsa;Password123456 providerNameSystem.Data.SqlClient /必须改三项Data Source改为你的 SQL Server 实例名如localhost\SQLEXPRESS或192.168.1.100Initial Catalog改为你已建好的数据库名不能是mychat除非你手动建库并执行DB_51aspx.sqlUser ID和Password改为实际 SQL 登录凭据强烈建议用 Windows 身份验证改成Integrated Securitytrue接着定位system.web下的sessionStatesessionState modeInProc timeout20 cookielessUseCookies /timeout20 是危险值——默认 20 分钟用户发完消息去倒杯水回来Session 就过期Session[uid]变 nullSpeak.aspx会跳转回Login.aspx。我一般改成timeout1202 小时并确保 IIS 应用程序池的“空闲超时”设为0禁用否则进程回收会清空 InProc Session。最后检查customErrorscustomErrors modeOff defaultRedirectError.aspx /开发阶段设modeOff能看到详细错误堆栈上线前务必改为modeOn否则黑客扫到Speak.aspx?msgscriptalert(1)/script会直接暴露服务器物理路径。2.3 编译前必做App_Code 命名空间与页面 Code-Behind 的严格匹配App_Code/UserBLL.cs开头是using System; using System.Data; namespace MyChatRoom.BLL { public class UserBLL { // ... } }而Login.aspx.cs顶部写着using System; using System.Web.UI; using MyChatRoom.BLL; // ← 这行必须存在且完全一致 public partial class Login : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { /* ... */ } } }常见翻车点有人把MyChatRoom.BLL改成mychatroom.bll大小写不敏感错C# 区分大小写或漏写using编译时报The type or namespace name UserBLL could not be found。解决方案只有两个要么统一全部小写不推荐要么在 VS 中右键项目 → “属性” → “应用程序” → “默认命名空间” 设为MyChatRoom再确认每个.cs文件的namespace前缀与之完全一致。2.4 数据库初始化DB_51aspx.sql 的执行顺序与字段陷阱压缩包里DB_51aspx.sql是建库脚本但注意它不是一键执行就能跑先手动在 SSMS 中新建数据库mychat字符集选Chinese_PRC_CI_AS避免中文乱码执行脚本前删掉开头两行-- 如果存在数据库则删除 IF EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE name Nmychat) DROP DATABASE mychat为什么删因为DROP DATABASE在某些权限策略下被禁用且你可能想保留已有数据。脚本中Users表的password字段是varchar(50)但UserBLL.Login()方法里用的是FormsAuthentication.HashPasswordForStoringInConfigFile(pwd, MD5)—— MD5 输出是 32 位十六进制字符串varchar(32)就够。这里留了 50 位是防扩展但如果你用 SHA140 位就得手动改字段长度。最关键Messages表的posttime字段类型是datetime但Speak.aspx.cs插入时用的是DateTime.Now.ToString(yyyy-MM-dd HH:mm:ss)—— 这在 SQL Server 2008 没问题但在 2005 会报Conversion failed when converting datetime from character string。稳妥做法是改用参数化查询cmd.Parameters.AddWithValue(posttime, DateTime.Now); // ← 交给 SqlClient 自动转换2.5 发布部署IIS 应用程序池 .NET 版本与匿名认证的生死开关在 IIS 中新建网站后右键“应用程序池” → “高级设置”.NET Framework 版本必须选v4.0不是 v4.0.30319也不是 v4.7.2IIS 显示的就是 v4.0托管管道模式集成模式经典模式会导致HttpContext.Current.Session为 null接着右键网站 → “编辑权限” → 确保IIS_IUSRS组有“读取和执行”权限再进“身份验证”关闭“匿名身份验证”开启“Windows 身份验证”—— 因为Login.aspx的校验逻辑依赖Request.ServerVariables[LOGON_USER]获取登录用户名若匿名开启该值为空字符串UserBLL.Login()直接返回 false。注意如果客户环境强制要求匿名访问你必须重写Login.aspx.cs把校验逻辑从 Windows 账户改为表单账户即查Users表的username/password并删掉Web.config中identity impersonatetrue /这行。3. 三层架构落地实操BLL 层如何隔离 UI 与 DAL又不变成“假三层”3.1 真·三层拆分图谱从 Login.aspx 到 SqlHelper 的完整调用链很多人以为“三层”就是三个文件夹其实核心是依赖倒置UI 层只引用 BLLBLL 层只引用 DALDAL 层不引用任何上层。mychatroom的实现如下Login.aspx.cs (UI) └── UserBLL.Login(username, pwd) → 返回 bool └── UserDAL.CheckUser(username, pwd) → 返回 DataTable └── SqlHelper.ExecuteDataTable(SELECT * FROM Users WHERE ..., params)关键证据在UserBLL.csusing MyChatRoom.DAL; // ← 只引用 DAL不碰 SqlHelper 或 SqlConnection public class UserBLL { public bool Login(string username, string pwd) { UserDAL dal new UserDAL(); // ← BLL new DAL 是允许的非 DDD 场景 DataTable dt dal.CheckUser(username, pwd); return dt.Rows.Count 0; } }而UserDAL.cs里using System.Data; using MyChatRoom.Common; // ← 这里引用 SqlHelper 所在的 Common 命名空间App_Code 下 public class UserDAL { public DataTable CheckUser(string username, string pwd) { string sql SELECT * FROM Users WHERE usernameu AND passwordp; SqlParameter[] pars { new SqlParameter(u, username), new SqlParameter(p, pwd) }; return SqlHelper.ExecuteDataTable(sql, pars); // ← DAL 不写 ConnectionString全由 SqlHelper 管 } }这比“UI → DAL → SqlHelper”少一层抽象但更务实没有引入接口、工厂、IOC 容器却保证了 UI 不知道 SQL 语句DAL 不知道页面逻辑。3.2 BLL 层的“业务规则”体现在哪以消息长度限制为例Speak.aspx.cs中提交消息时protected void btnSend_Click(object sender, EventArgs e) { string msg txtMsg.Text.Trim(); if (msg.Length 0 || msg.Length 200) // ← 业务规则200 字上限 { ClientScript.RegisterStartupScript(this.GetType(), alert, alert(消息不能为空且不超过200字);, true); return; } // ... 调用 BLL.InsertMessage() }但真正的规则校验应在 BLL 层// MyChatRoom.BLL.MessageBLL.cs public bool InsertMessage(int uid, string content) { if (string.IsNullOrEmpty(content) || content.Length 200) // ← 规则下沉 return false; MessageDAL dal new MessageDAL(); return dal.AddMessage(uid, content); }为什么 UI 层还要判因为前端 JS 也能做同样校验txtMsg.maxLength200形成双重防护。但后端 BLL 的校验是最后一道防线——即使黑客绕过 JSInsertMessage()仍会拒绝超长消息。3.3 DAL 层的 SqlHelper不是万能胶而是防 SQL 注入的最小公约数SqlHelper.cs是整个项目的“安全基石”它只做三件事从Web.config读取connStr并缓存SqlConnection对象非静态避免多线程冲突封装ExecuteScalar/ExecuteNonQuery/ExecuteDataTable强制要求所有 SQL 必须用参数化查询统一处理异常捕获SqlException后记录日志但原版没写日志需自行加File.AppendAllText(log.txt, ex.Message)看ExecuteDataTable关键片段public static DataTable ExecuteDataTable(string sql, params SqlParameter[] parameters) { using (SqlConnection conn new SqlConnection(GetConnectionString())) { using (SqlCommand cmd new SqlCommand(sql, conn)) { if (parameters ! null) cmd.Parameters.AddRange(parameters); // ← 参数数组必须非空否则报错 conn.Open(); SqlDataAdapter da new SqlDataAdapter(cmd); DataTable dt new DataTable(); da.Fill(dt); return dt; } } }玄学点cmd.Parameters.AddRange(parameters)这行看似简单却是防注入的核心——它把用户输入的txtMsg.Text当作参数值传入SQL Server 自动转义单引号、分号等危险字符比拼字符串sql INSERT INTO Msg VALUES( txtMsg.Text )安全一万倍。3.4 三层间的“数据载体”为什么不用 DataSet 而用 DataTableUserDAL.CheckUser()返回DataTableMessageDAL.GetMessages()也返回DataTable而非DataSet或自定义实体类如UserEntity。原因很现实DataTable是 WebForms 的“原生语言”GridView、Repeater 直接绑定DataTable无需ToListUserEntity()转换DataSet有 Schema 开销而聊天室消息列表不需要关系约束没主外键关联自定义实体类要写public class Message { public int Id {get;set;} ... }再写ListMessage ToList()对 200 行代码的小项目是过度设计但代价是强类型丢失dt.Rows[0][content].ToString()比msg.Content容易写错字段名。我的补救习惯是在DAL方法注释里写死字段顺序/// summary /// 返回 DataTable列顺序id, uid, content, posttime /// /summary public DataTable GetMessages(int page, int pageSize) { ... }3.5 避坑三层架构常见问题与血泪排查记录现象 1Login.aspx 登录成功但 Main.aspx 显示“未登录”Session[uid]为 null原因IIS 应用程序池的“.NET Framework 版本”选错选了 v2.0或Web.config中sessionState modeInProc但应用池启用了“重叠回收”导致 Session 被清空。解决确认应用池版本为 v4.0在 IIS → 应用程序池 → 高级设置 → “发生配置更改时禁止回收” 设为TrueWeb.config中加httpRuntime maxRequestLength10240 executionTimeout300 /防超时回收。现象 2Speak.aspx 提交消息后ShowMessage.aspx 不刷新新消息要 F5 才出现原因ShowMessage.aspx用Timer控件轮询但Timer.Interval设为50005 秒而Timer.Enabled在Page_Load里被设为false或UpdatePanel的ChildrenAsTriggers为false。解决检查ShowMessage.aspx中asp:Timer IDTimer1 runatserver Interval5000 OnTickTimer1_Tick /并在Page_Load里加Timer1.Enabled true;确保UpdatePanel外层有ScriptManager。现象 3SQL Server 连接池耗尽IIS 日志报Timeout expired. The timeout period elapsed prior to obtaining a connection原因SqlHelper的using (SqlConnection conn ...)没真正释放连接——因为SqlDataReader未关闭或ExecuteDataTable中SqlDataAdapter.Fill()后没显式da.Dispose()。解决在SqlHelper.cs的ExecuteDataTable方法末尾加da.Dispose();所有SqlDataReader必须用using包裹using (SqlDataReader dr cmd.ExecuteReader()) { ... }。现象 4中文消息显示为?????数据库字段是nvarchar连接字符串已加charsetutf-8原因Web.config中globalization requestEncodingutf-8 responseEncodingutf-8 /缺失或 SQL Server 数据库排序规则不是Chinese_PRC_CI_AS。解决在Web.config的system.web下添加 globalization 节点用 SSMS 右键数据库 → 属性 → 选项 → 排序规则改为Chinese_PRC_CI_AS。现象 5部署到 Windows Server 2019IIS 报错Could not load file or assembly System.Web.Extensions原因.NET Framework 4.7.2未安装或安装后未在 IIS 中注册 ASP.NET。解决运行C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis.exe -i32 位和C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_regiis.exe -i64 位重启 IIS。4. 伪实时聊天的底层机制轮询 Timer 如何做到“不卡顿、不丢消息、不爆内存”4.1 ShowMessage.aspx 的轮询架构UpdatePanel Timer 的黄金组合ShowMessage.aspx的核心是asp:ScriptManager IDScriptManager1 runatserver / asp:UpdatePanel IDUpdatePanel1 runatserver ContentTemplate asp:Repeater IDrptMessages runatserver ItemTemplate div classmsg-item %# Eval(username) %%# Eval(content) % span classtime%# Eval(posttime) %/span /div /ItemTemplate /asp:Repeater asp:Timer IDTimer1 runatserver Interval3000 OnTickTimer1_Tick / /ContentTemplate /asp:UpdatePanel为什么用 UpdatePanel 而不用纯 AJAX因为 WebForms 的事件模型ViewState、PostBack与 jQuery AJAX 冲突UpdatePanel是微软提供的“无感异步”方案——它自动序列化 ViewState只刷新ContentTemplate内容不重绘整个页面。4.2 Timer1_Tick 的分页加载逻辑避免全量拉取拖垮数据库Timer1_Tick事件里不是SELECT * FROM Messages ORDER BY id DESC而是protected void Timer1_Tick(object sender, EventArgs e) { int lastId Convert.ToInt32(ViewState[lastId] ?? 0); DataTable dt new MessageBLL().GetNewMessages(lastId); // ← 关键只查 id lastId 的 if (dt.Rows.Count 0) { rptMessages.DataSource dt; rptMessages.DataBind(); ViewState[lastId] dt.Rows[dt.Rows.Count - 1][id]; // ← 更新 lastId 为最新一条 } }对应MessageBLL.GetNewMessages(int lastId)public DataTable GetNewMessages(int lastId) { string sql SELECT TOP 20 m.id, u.username, m.content, m.posttime FROM Messages m INNER JOIN Users u ON m.uid u.id WHERE m.id lastId ORDER BY m.id DESC; return SqlHelper.ExecuteDataTable(sql, new SqlParameter(lastId, lastId)); }这个设计比“每 3 秒查全部”强在哪数据库压力从 O(n) 降为 O(1)无论历史消息多少每次只查 20 条新消息带宽节省JSON 响应体从几 MB 降到几 KB用户体验新消息秒级到达旧消息不重复刷屏4.3 ViewState 的精巧利用用它存 lastId而不是 Session 或 HiddenFieldViewState[lastId]存的是当前页面最后一次加载的最新消息 ID它比Session[lastId]更安全Session是全局共享A 用户的lastId可能被 B 用户覆盖如果两人共用同一 Session IDHiddenField可被用户篡改F12 改 value导致 SQL 注入WHERE id 1; DROP TABLE Messages--ViewState经过MachineKey加密且随页面提交自动回传篡改会触发Validation of viewstate MAC failed异常但代价是ViewState 体积膨胀如果lastId是 10 位数字ViewState本身约 200 字节但如果rptMessages绑定 100 条消息ViewState会暴涨到 500KB。所以ShowMessage.aspx的EnableViewStatefalse只开在Repeater外层Repeater自身EnableViewStatetrue而ViewState[lastId]单独存——这是精细控制。4.4 轮询频率的工程权衡3 秒 vs 1 秒 vs 5 秒Interval3000不是拍脑袋定的设10001 秒服务器 QPS 翻 3 倍IIS 线程池可能打满尤其百人在线时设50005 秒用户感知延迟明显“发送后等 5 秒才看到回复”体验差3000是平衡点实测 200 并发下IIS CPU 占用 40%平均延迟 1.2 秒网络DB 查询进阶技巧根据在线人数动态调频。在Global.asax中void Application_BeginRequest(object sender, EventArgs e) { int online (int)(Application[onlineCount] ?? 0); if (online 100) Context.Items[pollInterval] 5000; // 人多降频 else Context.Items[pollInterval] 3000; }然后ShowMessage.aspx里Timer1.Interval Convert.ToInt32(Context.Items[pollInterval] ?? 3000);4.5 消息去重与顺序保证为什么不用 GUID 而用自增 IDMessages表主键是id int IDENTITY(1,1)不是uniqueidentifier。原因排序确定性ORDER BY id DESC永远按插入顺序不会因 GUID 生成时间微差导致“后发先至”索引效率int的聚集索引比GUID小 12 字节B-Tree 层级更低百万级数据查询快 30%分页友好WHERE id lastId比WHERE created_time lastTime更准——created_time可能同毫秒id绝对唯一但隐患是IDENTITY在高并发下可能跳号如事务回滚不过聊天室场景可接受——没人会计较 ID 是否连续。4.6 避坑轮询引发的资源泄漏与内存溢出现象IIS 工作进程内存持续上涨24 小时后达 2GB自动回收原因Timer1_Tick中new MessageBLL()创建的对象未被 GC 及时回收或ViewState存了大 DataTable。解决MessageBLL改为静态方法无状态public static DataTable GetNewMessages(int lastId)ViewState[lastId]改用Page.Session[showmessage_lastid_ Session.SessionID]Session 有超时自动清理在Timer1_Tick结尾加GC.Collect()仅调试用生产环境慎用现象多个用户同时发消息ShowMessage.aspx 刷新时消息乱序原因GetNewMessages(lastId)查的是id lastId但如果 A、B 用户几乎同时提交id可能 A100, B101但 B 的消息先入库因事务提交快A 的lastId是 99B 的lastId是 99两者都查id99结果 B 的消息被 A 的请求先刷出。解决在Speak.aspx.cs插入后立即Response.Redirect(ShowMessage.aspx?lastId newId)强制页面带新lastId刷新放弃轮询——这是牺牲“伪实时”换一致性。现象Timer 轮询时用户关闭标签页服务器仍在发请求原因浏览器关闭后Timer的 AJAX 请求可能还在飞IIS 不知道客户端已断。解决在ShowMessage.aspx加 JS 监听beforeunloadwindow.addEventListener(beforeunload, function() { __doPostBack(Timer1, ); // 取消 Timer });并在Timer1_Tick开头加if (Request.Headers[Connection] close) return; // ← 检查连接是否已断5. 安全加固实战从 SQL 注入到 XSSWebForms 老项目如何扛住现代扫描器5.1 SQL 注入防御SqlHelper 的参数化是底线但还需三道补丁SqlHelper已强制参数化但仍有漏洞Login.aspx.cs中string sql SELECT * FROM Users WHERE username username ;原版没这行但有人会手贱改Speak.aspx.cs的txtMsg.Text直接拼进Response.Write()Response.Write(div txtMsg.Text /div)补丁 1全局过滤 Request.QueryString在Global.asax的Application_BeginRequest中void Application_BeginRequest(object sender, EventArgs e) { foreach (string key in Request.QueryString.AllKeys) { string val Request.QueryString[key]; if (val.Contains() || val.Contains(;) || val.Contains(--) || val.Contains(/*)) { Response.StatusCode 400; Response.End(); return; } } }补丁 2输出编码防 XSS所有Response.Write()和% %改为HttpUtility.HtmlEncode()// 错误 Response.Write(div msg /div); // 正确 Response.Write(div HttpUtility.HtmlEncode(msg) /div);补丁 3Web.config 的 requestValidationsystem.web pages validateRequesttrue / !-- 默认 true但显式声明 -- httpRuntime requestValidationMode4.5 / !-- .NET 4.5 更严格的验证 -- /system.web5.2 Session 劫持防护从 Cookie 到 SSL 的全链路加固Session[uid]是整套系统的钥匙必须防窃取Web.config中sessionState cookielessUseCookies timeout120 /→ 改为cookielessUseUriSession ID 写 URL不走 Cookie但 URL 会变长更优方案启用 HTTPS在 IIS 绑定 SSL 证书后加system.web httpCookies httpOnlyCookiestrue requireSSLtrue / /system.webhttpOnlyCookiestrue阻止 JS 读取ASP.NET_SessionIdCookierequireSSLtrue强制只在 HTTPS 下发送 Cookie。5.3 文件上传漏洞堵截虽然本项目没上传功能但预留了 Images 文件夹Images/目录若被用户写入.aspx文件就能执行任意代码。防护措施IIS 中右键Images文件夹 → “编辑权限” → 删除IIS_IUSRS的“写入”权限Web.config在Images目录下新增?xml version1.0 encodingUTF-8? configuration system.web httpHandlers add path*.aspx verb* typeSystem.Web.HttpForbiddenHandler / add path*.asmx verb* typeSystem.Web.HttpForbiddenHandler / /httpHandlers /system.web /configuration这比删 IIS MIME 类型更可靠——即使用户绕过前端限制传了.aspxIIS 也会返回 403 Forbidden。5.4 错误信息脱敏把 StackTrace 变成“系统繁忙请稍后再试”Web.config的customErrors已设modeOn但还需删除Web.config中compilation debugtrue /改为debugfalse在Global.asax的Application_Error中void Application_Error(object sender, EventArgs e) { Exception ex Server.GetLastError(); // 记录详细日志到文件 File.AppendAllText(Server.MapPath(~/App_Data/error.log), ${DateTime.Now} | {ex.ToString()}\n); // 清除错误跳转友好页 Server.ClearError(); Response.Redirect(~/Error.aspx); }Error.aspx里只显示h2系统繁忙请稍后再试/h2 p错误代码ERR-% DateTime.Now.ToString(yyyyMMddHHmmss) %/p绝不显示System.Data.SqlClient.SqlException或物理路径。5.5 防暴力破解登录失败 5 次锁定 IPLogin.aspx.cs的btnLogin_Click中加string ip Request.UserHostAddress; string lockKey login_lock_ ip; if (Application[lockKey] ! null (int)Application[lockKey] 5) { ClientScript.RegisterStartupScript(this.GetType(), alert, alert(您的 IP 已被锁定请 30 分钟后重试);, true); return; } if (!UserBLL.Login(username, pwd)) { int failCount (Application[lockKey] as int?) ?? 0; Application[lockKey] failCount 1; // 30 分钟后自动解锁 System.Threading.Tasks.Task.Delay(1000 * 60 * 30).ContinueWith(t Application.Remove(lockKey)); } else { Application.Remove(lockKey); // 登录成功清除计数 Session[uid] username; Response.Redirect(Main.aspx); }注意Application是进程级多服务器集群需改用 Redis但单机足够。5.6 避坑安全加固后的兼容性雷区现象启用requireSSLtrue后HTTP 访问直接 302 跳 HTTPS但证书未配置页面无限重定向原因IIS 未绑定 HTTPS 端口443或证书无效。解决开发阶段注释掉requireSSLtrue生产环境必须先在 IIS 绑定有效证书再开启。现象HttpUtility.HtmlEncode()后消息里的换行符\n变成br失效原因HtmlEncode把\n当普通字符编码为#10;不再被浏览器识别为换行。解决先Replace(\n, br/)再HtmlEncodestring safeMsg HttpUtility.HtmlEncode(msg.Replace(\n, br/));现象Application_Error中Server.ClearError()后Response.Redirect不生效原因ClearError()清除了异常但Response.Redirect需要End()阻止后续执行。解决p a hrefhttps://download.csdn.net/download/weixin_42697609/22210545 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进