尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ASP.NET MVC三层架构博客系统:从分层设计到安全与性能实践

ASP.NET MVC三层架构博客系统:从分层设计到安全与性能实践 简介这是一套基于ASP.NET MVC实现的三层架构博客网站系统源码面向Web开发初学者及具备C#基础的中级开发者旨在帮助读者深入理解MVC模式、分层解耦设计表现层/业务逻辑层/数据访问层以及企业级博客系统的完整构建流程。资源包含418个文件涵盖35个cshtml视图页、23个cs业务与实体类、23个config配置文件、32个js前端脚本、11个css样式表及121个dll依赖库完整支撑SEO优化、用户管理、文章发布与评论等核心功能压缩包大小为29.41MB结构清晰含.sln解决方案、.edmx数据模型及.sql数据库脚本便于本地快速部署与调试。已有1708人学习下载配套Global.asax全局配置、MyBlogNet.csproj项目定义及多级缓存与编译中间文件可直接用于教学演示、课程设计或二次开发实践是掌握.NET Web开发工程化落地的优质学习样本。 做了不少年.NET开发一直觉得博客这种项目最适合拿来练手分层。今天要聊的这套基于ASP.NET MVC的三层架构博客网站系统算是把传统三层架构和MVC框架结合得比较完整的一套源码。它解决的痛点很直接很多初学者拿到博客源码要么是一堆文件堆在Page_Code里要么是Controller里塞满SQL语句根本谈不上工程化。这套系统的价值在于它把数据访问、业务处理、页面表现彻底拆开每个环节都能单独替换、单独测试对想搞懂三层架构到底怎么落地的人来说是一份很不错的参考资料。不管是刚接触.NET的在校生还是工作中需要维护老项目、准备把WebForms迁移到MVC的开发者都可以拿这套源码当切入点。你会看到文章发布、分类管理、评论互动这些博客标配功能是如何按照“界面层只负责展示、业务层只负责规则、数据层只负责存取”的规矩各司其职的。更重要的是源码里有很多实际项目才会暴露的细节处理——比如分页怎么做、SQL注入怎么防、会话超时怎么办这些才是真正值钱的地方。1. 项目概述与需求拆解1.1 博客系统的核心需求先看看这套系统到底做了什么。博客网站本质上是内容管理系统的一个垂直分支核心需求就三大块内容的发布与展示、用户互动、后台管理。从源码的实际模块来看它把这几块都覆盖了前台有文章列表、文章详情、分类检索、评论展示与发布后台有文章管理、分类管理、评论审核、基础设置。说句实在话很多人一上来就想做社交、做推荐算法但博客系统最考验基本功的地方恰恰是它的“简单中的复杂”。就拿文章列表来说表面上就是SELECT出来绑到页面上但仔细想列表要分页、要按时间倒序、要统计阅读量、要关联分类名称、要判断是否置顶、还要在列表页和详情页展示不同的摘要长度。把这些逻辑全部理清楚不放错层就是一个很好的架构练习。这套源码的定位也很明确——不是花里胡哨的演示项目而是强调结构规整、注释清楚、可以二次开发的工程模板。所以它的功能边界是可预期的不追求复杂的权限体系不搞分布式缓存就是老老实实把三层架构下的CRUD、关联查询、认证登录这些基础能力做扎实。1.2 为什么是ASP.NET MVC加三层架构聊到技术选型先说结论ASP.NET MVC已经不算新技术了甚至在.NET 6之后微软官方推荐的是Razor Pages和ASP.NET Core MVC。但这个选题放在今天依然有意义原因有三点。第一存量市场。大量企业内部的OA系统、门户网站、电商后台还是基于ASP.NET Framework 4.x部署的用的就是MVC 5这套技术栈。学了这套源码等于掌握了能直接上手维护老项目的能力这是很多招聘JD里明确写着的。第二MVC和三层的结合方式非常经典。MVC解决的是“界面层内部的职责分工”而三层架构解决的是“整个系统的纵向分层”。两者是不同维度的问题结合起来才是一个完整的架构方案。Asp.NET MVC把Controller当作表现层的入口Controller调用业务层业务层再调数据层Model作为数据传输的载体贯穿三层这个配合比WebForms时代清晰得多。第三从学习曲线角度MVC 5相对轻量。它没有引入太多的中间件概念、依赖注入容器这些额外复杂度一个初学者只要理解路由、Controller、Action、View再补上EF的DbContext用法就能把整个链路跑通。拿它入门.NET Web开发阻力最小。1.3 源码适合谁阅读我按自己的经验把适合读这套源码的人分成三类。第一类是刚学完C#基础、想找个完整项目练手的初学者。这类人最需要看的是“一个请求从浏览器出发到数据库再返回页面”的完整链路。建议阅读顺序是先按代码里的Readme跑起来然后从Controller层往下追一个功能一个功能地看。第二类是准备面试.NET开发岗位的求职者。三层架构是面试题里的常客但很多人只会背概念说不出“为什么Controller不该直接写SQL”“为什么业务逻辑不能堆在视图里”。这套源码能帮你把概念落到具体代码上面试时讲起来有例子有细节。第三类是要给公司做内部系统、需要快速搭建一个带后台管理的前台展示网站的同学。直接基于这套源码改一改前台换成公司产品列表后台换成数据管理效率比自己从零开始写高得多。2. 架构设计与数据库建模2.1 三层架构的职责边界先把这个架构的基础打牢。三层架构通常指表现层UI、业务逻辑层BLL、数据访问层DAL这套源码在标准三层之上又拆出了一个实体层Model和一个公共类库Common整体分了五层但核心思想还是那三层。每层的职责在源码里分得很清楚表现层Blog.Web只做三件事接收用户输入、调用业务层方法、把结果渲染到视图。你会在Controller里看到的代码都是类似这样的流程接收参数、构造ViewModel、调用service方法、返回View。Controller里不会出现字符串拼接SQL的代码。业务逻辑层Blog.BLL承载所有业务规则。什么叫业务规则比如“文章发布时自动把状态设为已发布”“删除分类时如果分类下还有文章就不允许删除”“评论内容需要先过滤敏感词”。这些规则放在业务层最大的好处是将来换界面层比如改成Web API时业务代码可以直接复用。数据访问层Blog.DAL负责和数据库打交道。这里用的是Entity Framework Code First模式所有数据库操作封装在Repository类里。数据访问层返回的是实体对象或者IQueryable上层永远不知道数据到底存在SQL Server里还是MySQL里。这种分层方式最直接的好处是依赖方向是单向的Web引用BLLBLL引用DAL谁都不反向引用。源码里如果出现循环引用编译阶段就会报错从机制上保证了架构不会被破坏。2.2 MVC与三层架构的协同关系很多初学者会把MVC和三层架构搞混这里必须说清楚。三层架构是系统的纵向骨架MVC是表现层内部的横向分工。把这套源码的解决方案打开在Blog.Web项目里你会看到典型的MVC结构Controllers文件夹放控制器Views文件夹放视图Models文件夹放视图模型。但是控制器本质上只是在执行表现层的任务它的“上司”是业务层。也就是说一个请求到达Controller后Controller并不是自己查数据库而是调用Blog.BLL里的某个Service。Service把数据处理完返回结果Controller再把它交给特定的View去渲染。这里有个非常关键的细节也是在源码里值得反复看的ViewModel和实体Model是分开的。实体Model是数据库表的映射比如Article类对应Articles表。但页面展示时需要的可能是ArticleDetailViewModel里面包含文章实体、作者名字、分类名字、评论列表等。这套源码里Controller负责把实体Model转换成ViewModel用AutoMapper或者手动赋值都行。这个习惯非常重要因为你永远不应该把数据库表结构直接暴露给视图不然哪天表加个字段前端界面的坑会一个接一个。2.3 数据库表设计与关系这套博客系统的数据库并不复杂一共四张核心表加上一张可选的关系表。我直接说设计思路。用户表Users包含用户基本信息和登录凭据。密码字段这里值得注意源码存的是哈希值而不是明文。PasswordHash字段的长度设置建议到128位以上因为无论用MD5、SHA1还是BCrypt哈希结果都比较长。这里我用的是Rfc2898DeriveBytes也就是PBKDF2算法比单纯MD5靠谱得多。文章表Articles是整站的核心。字段包括标题、正文、分类ID、作者ID、阅读数、评论数、发布日期、是否置顶、是否发布。看到“评论数”这个字段了吗这个字段是个典型的反范式设计——评论表里COUNT一下不就行了吗但实际开发中文章列表页要显示每条文章的评论数如果列表页每行都去评论表COUNT一次那就是N1条SQL性能很难看。所以用一个冗余字段存评论数在新增评论时顺便1是空间换时间的经典做法。分类表Categories很简单ID、名称、描述、排序号。但有一个细节值得借鉴排序号SortOrder单独用了一个int字段而不是直接用ID排序。因为ID和业务无关它只表示插入顺序如果将来要前台自定义分类展示顺序用SortOrder就灵活得多。评论表Comments设计得比较保守内容限制500字以内支持审核标记。审核这个字段在个人博客里可能用不上但放在这套源码里是个不错的扩展点——将来想接广告、开放游客评论时审核字段就是第一道防线。这几张表的关系也直接明了文章属于一个分类、属于一个作者评论属于一篇文章、属于一个用户。外键约束在数据库里建好了EF映射里也配置了导航属性。这样查询文章时可以直接article.Category.Name拿到分类名不用手动Join。3. 核心功能实现与关键代码3.1 数据访问层的Repository模式实现说这套源码“工程化”很大程度体现在数据访问层的实现方式上。DAL里定义了一套泛型仓储接口再用具体类实现这种写法在老项目中并不多见但维护价值极高。泛型仓储的核心定义大概长这样public interface IRepositoryT where T : class { T GetById(int id); IQueryableT GetAll(); void Insert(T entity); void Update(T entity); void Delete(T entity); void Save(); }为每个实体实现一个独立的仓储接口继承这个基类接口再加自己的专属方法。比如IArticleRepository会多一个GetPagedArticles方法public interface IArticleRepository : IRepositoryArticle { PagedResultArticle GetPagedArticles(int pageIndex, int pageSize); IQueryableArticle GetArticlesByCategory(int categoryId); }为什么要套一层Repository直接用DbContext不行吗这个问题我自己刚开始也觉得是多此一举。但后来维护项目多了才理解Repository把Entity Framework的细节隔离在DAL内部上层业务代码不需要知道查询是用EF还是Dapper实现的。哪天想换ORM只需要改DAL一个项目BLL和Web层一行都不用动。DbContext的生命周期管理也是这套源码值得一看的地方。它没有用什么高级的IoC容器而是在DAL层用了一个简单的DbContext工厂每个请求创建一个DbContext实例请求结束自动释放。这种方式对中小型系统完全够用也比手动new到处传要规范得多。3.2 业务逻辑层的核心服务实现业务逻辑层是这套源码里代码量最大、也最能体现“逻辑”的地方。我把文章服务单独拿出来说。“查看文章详情”这个动作表面上看起来就是一个GetById但在业务层里实际要做几步从仓储取出文章实体如果为null则抛出业务异常。阅读数加1然后更新回数据库。查询该文章的分类名称、作者昵称组合成页面需要的完整数据。查询该文章的评论列表按时间正序排列。返回一个封装好的ArticleDetailModel给Controller。这个流程放到Controller里写的话也就十几行但放在BLL里的意义是如果将来增加“阅读数需要去重同一个IP只增加一次”的规则只需要改BLLController和DAL都不用动。这就是“单一职责”的价值。业务层还负责权限校验。比如删除文章的后台操作业务方法第一行就检查当前用户是否管理员public void DeleteArticle(int articleId, string operatorName) { var article _articleRepository.GetById(articleId); if (article null) { throw new Exception(文章不存在或已被删除); } var user _userRepository.GetByUserName(operatorName); if (user null || user.Role ! Admin) { throw new UnauthorizedAccessException(没有删除权限); } _articleRepository.Delete(article); _articleRepository.Save(); }这段代码虽然简单但体现了一个重要原则安全校验不能只靠界面隐藏按钮必须在业务层做二次校验。因为将来如果加了API接口绕过界面直接调接口的事随时可能发生。3.3 表现层的控制器与视图实现Controller的写法直接决定了这套源码好不好读。它很克制地遵循了MVC的规范Controller不做业务只做“调度员”的工作。以文章列表为例Controller的代码逻辑是public ActionResult Index(int page 1) { int pageSize 10; var result _articleService.GetPagedArticles(page, pageSize); var model new ArticleListViewModel { Articles result.Items, CurrentPage page, TotalPages result.TotalPages }; return View(model); }重点在于View层不直接操作数据只负责展示ArticleListViewModel。我注意到这套源码的视图大量使用了HTML Helper和Razor语法比如Html.ActionLink生成链接、Html.Raw输出富文本内容仅限信任内容、Html.AntiForgeryToken防伪令牌。这些都是MVC时代的标准做法比字符串拼接HTML安全得多。视图里还有一个值得一说的地方是布局页的应用。_Layout.cshtml里定义了网站的整体结构——头部导航、侧边栏、主内容区、底部版权。每个页面通过{ ViewBag.Title 首页; }来设置不同的面包屑和标题这一套机制在维护多页面网站时非常高效。如果你想给这个博客换皮肤只需要改_Layout.cshtml外加对应的CSS所有页面就一起更新了。3.4 分页组件与评论功能的实现细节分页是列表页的基础功能但实现得草率还是讲究差别很大。这套源码里的分页是独立封装的一个PagedResult类包含当前页数据列表、总记录数、总页数、当前页码。前端用RenderPage直接输出分页链接。翻页参数是page通过MVC路由默认值的机制传给Action。分页SQL用的是EF的Skip和Take底层会被翻译成OFFSET ... ROWS FETCH NEXT这是SQL Server 2012的标准分页语法性能尚可。这里要提醒一句不要在大数据量表上使用Skip和Take做深分页Offset超过10万行以后性能会明显下降到时候得用键集分页keyset pagination。这个属于优化话题后面单独说。评论功能比较完整包含发表评论和评论列表展示。发表评论走的是POST请求表单里带上了AntiForgeryToken防止跨站请求伪造。评论内容在写入数据库之前做了StripTags处理去掉所有HTML标签再存从源头堵住存储型XSS的入口。评论列表则是在文章详情页内通过主键关联查询拿到不在列表页做循环嵌套查询性能可控。4. 安全防范与性能优化实践4.1 防SQL注入的三个层次SQL注入在所有Web安全威胁里排行非常靠前而这套基于EF的源码天然对它有较好的免疫能力。但“天然免疫”不代表可以掉以轻心我在这里说三个层次。第一层是防住字符串拼接SQL。整个源码里你搜索SqlCommand或者“SELECT * FROM”这种字符串应该是找不到的。所有数据库操作都走EF的LINQ查询或参数化查询。凡是看到有人用字符串拼接用户输入来生成SQL语句不管输入有没有做校验都应该直接判为不合格代码。第二层是实体校验。EF的DbContext里对字段长度做了配置比如文章标题最大200字符、评论内容最大500字符。超长内容在写入时会直接抛异常——这从另一个维度减少了恶意输入的危害。MVC模型验证也配合做了ModelState.IsValid不通过就不进入业务层。第三层是权限控制。登录功能用Session保存用户状态每个需要登录的后台操作都在业务层做权限判断而不是只在前台做视图层控制。这种纵深防御的思路比单纯依赖某一道防线稳妥得多。顺带提一个很常见但容易被忽略的坑直接从Request接收字符串然后拼进LINQ的Where过滤条件。虽然不会构成SQL注入但可能造成意外的数据泄露。比如评论查询里过滤ArticleId的时候不给非法值做类型校验别人就能通过遍历ID把不公开的文章读出来。这套源码在Controller里针对ID参数做了int.TryParse解析失败直接返回404这个细节看起来不起眼但值得保留。4.2 XSS与CSRF防护XSS跨站脚本攻击在博客系统里是重灾区因为博客天然允许用户输入内容。这套源码主要防了两条路径。存储型XSS路径发表评论时输入内容在进入数据库之前经过了HtmlEncode处理或者StripTags过滤尖括号、引号都被转义了。就算恶意脚本被存进数据库它在页面上也只会被当作普通文本显示不会执行。文章正文因为是博客作者本人编辑启用了Html.Raw直接输出原始HTML但前提是博客作者本身可信。如果以后要开放投稿功能这里就必须改成富文本白名单过滤方案否则就是后门。反射型XSS路径搜索功能会把用户输入的搜索词回显到页面上。源码里使用Html.Encode或Razor的默认编码输出搜索词里面如果夹带
返回列表