
简介这是一套面向.NET初学者与企业信息化开发者的完整企业门户网站源码可作为搭建新闻发布、用户管理、权限控制等模块的参考案例也适合需要阅读ASP.NET Web Forms项目结构的团队使用。压缩包共211个文件约2.08MB以81个gif、38个cs、21个aspx及ascx、master等页面与控件文件为主并含dll、pdb程序集、css、js和图片等前端资源以及config、xml配置、mdf、ldf数据库文件和docx说明基本覆盖源码、页面、静态资源与部署配置。内容预览显示其包含全局应用入口、登录与新闻发布用户控件、页头页脚、用户校验与删除处理、新闻管理和用户管理页面等可据此理解门户系统页面组织、后台代码与数据库协作方式并借助配置文档了解部署环境、数据库连接与IIS设置。已有375人学习下载适合希望研究完整Web Forms门户实现的中级学习者参考。1. 企业门户不是官网一次把“.NET 企业门户网站”讲透很多团队接到“企业门户网站”需求时第一反应是把它当成官网来做——几个静态页面、一个新闻列表、一个联系方式表单就交付。真到上线才发现需求方要的是能承载多部门内容发布、权限分级、审批流、统一登录和后续扩展的内容平台静态站点根本兜不住。这也是搜索“.NET 企业门户网”的人最常见的落差以为是做页面实际是做一套内容与权限体系。这份完整版方案要解决的正是这件事用 .NET 技术栈搭一套可交付、可维护、可扩展的企业门户让做毕设/课设的同学有完整结构可抄让要交付方案的从业者有骨架可改。它适合三类人需要交一份完整门户设计的在校学生、要接企业内部站点建设的技术负责人、以及想把单体门户拆成模块化架构的开发者。下面从需求、架构、实现一路讲到验收。2. 需求与选型先定清门户的四个角色和三条技术底线2.1 把“门户”拆成四类角色需求才不会飘企业门户最容易翻车的地方不是技术是需求边界不清。我一般会先强制把使用者拆成四类角色每一类对应一组功能需求讨论就围绕这四类展开谁也别想再加“顺便做个小功能”。角色核心诉求对应模块访客看得到、找得到、能联系首页、栏目页、搜索、留言内容编辑快速发文、图文混排、预览内容管理、富文本编辑、附件上传部门管理员管本部门栏目、审本部门稿件栏目授权、稿件审批系统管理员管用户、管角色、管日志用户权限、系统配置、操作审计这张表的价值在于它是后面所有设计的锚点。栏目权限怎么配、审批流几级、日志记到什么颗粒度全部回到这张表来问。实际项目里需求方临时加需求是常态有了这张角色表你可以理直气壮地问一句“这是哪个角色的诉求归到哪个模块”能挡掉大半无效需求。做课设的同学也建议把这张表放进文档第一章答辩时评委最爱问的就是“你的系统有几类用户权限怎么区分”提前答掉。角色定完再定内容模型。企业门户的内容类型通常收敛到三类单页关于我们、联系方式这类静态内容、列表新闻、公告、产品、外链跳转。三类内容模型定死后台的新建内容表单就能做成配置化不用每加一个栏目就改一次代码。这一步做扎实后面扩展栏目几乎是零成本。2.2 技术选型ASP.NET Core EF Core 关系库别追新选型这块我的态度很明确企业门户是长期维护型项目稳定压过时髦别为了简历好看上微服务。方案里的技术底线是三条。第一条Web 框架用 ASP.NET Core MVC 或 Razor Pages。门户是典型的服务端渲染场景SEO 要友好、首屏要快、后台表单多MVC 的分层结构刚好匹配“控制器处理请求、视图渲染页面、模型承载数据”的门户业务流。前后端分离不是不能做但门户的内容页天然适合服务端出 HTML硬拆成 SPA 只会让首屏和 SEO 都变差。第二条数据访问用 EF Core配合 Code First 迁移。门户的实体关系清晰用户-角色-权限、栏目-内容-附件用 EF Core 的导航属性能省掉大量手写 JOIN迁移机制让数据库结构跟着代码走多人协作时不会出现“你本地库和我本地库字段对不上”的血泪经验。第三条数据库选 SQL Server 或 PostgreSQL 这类关系库。门户的数据是强关系型的权限、栏目树、审批记录都靠外键和事务保证一致性文档库在这里是给自己挖坑。缓存用内存缓存起步访问量真上来了再上 Redis不要一上来就堆中间件。// Program.cs门户的最小启动配置按需增删 var builder WebApplication.CreateBuilder(args); // 注册 MVC 与 Razor 视图 builder.Services.AddControllersWithViews(); // 注册 EF Core连接串从配置读取禁止硬编码 builder.Services.AddDbContextPortalDbContext(opt opt.UseSqlServer(builder.Configuration.GetConnectionString(Portal))); // 门户必备会话登录态、内存缓存栏目树、授权 builder.Services.AddSession(); builder.Services.AddMemoryCache(); builder.Services.AddAuthorization(); var app builder.Build(); app.UseStaticFiles(); // 静态资源css/js/上传的图片 app.UseRouting(); app.UseSession(); // 注意顺序Session 必须在 Routing 之后、Authorization 之前 app.UseAuthorization(); app.MapControllerRoute( name: default, pattern: {controllerHome}/{actionIndex}/{id?}); app.Run();这段启动代码里最值得说的是中间件顺序。UseSession必须放在UseRouting之后、UseAuthorization之前放错了登录态会读不到表现为“用户明明登录了刷新一下又变游客”这类问题排查起来非常费时属于典型的环境玄学。连接串走配置中心而不是写死在代码里是为了让开发、测试、生产三套环境用同一份代码。AddMemoryCache是为栏目树准备的——门户的导航栏几乎每个页面都要渲染每次都查库是浪费栏目变更时主动清缓存即可。3. 总体架构设计分层结构与模块职责划分3.1 四层结构把门户切成可独立替换的块架构上采用经典四层表现层、应用层、领域层、基础设施层。听起来像教科书落到门户上有很具体的含义。表现层就是 MVC 的 Controller 和 View负责接收请求、校验入参、渲染页面。它不许直接碰 DbContext所有数据操作通过应用层暴露的服务接口走。这一条纪律能保证以后换 UI 框架比如从 Razor 换到别的前端方案时业务逻辑一行不用改。应用层是业务用例的集合比如“发布一篇稿件”“审批一篇稿件”“给角色分配栏目权限”。每个用例一个服务类方法内部编排领域对象和仓储。门户的业务逻辑复杂度适中应用层是防止 Controller 变胖的关键我见过太多门户项目的 Controller 动辄八百行改一个需求要读半小时根子就在这里。领域层放实体和领域规则比如内容实体上的“已发布状态不可直接编辑必须走修订”这条规则写在实体方法里而不是散落在 Service 里。基础设施层收口 EF Core 的 DbContext、仓储实现、文件存储、日志组件。四层的依赖方向是单向的表现层依赖应用层应用层依赖领域层基础设施层被上层依赖但不反向依赖。// 应用层示例发布稿件的用例Controller 只调这一个方法 public class ContentPublishService { private readonly PortalDbContext _db; private readonly IMemoryCache _cache; public ContentPublishService(PortalDbContext db, IMemoryCache cache) { _db db; _cache cache; } // 返回结果对象而不是抛异常便于前端展示友好提示 public async TaskPublishResult PublishAsync(int contentId, int operatorId) { var content await _db.Contents.FindAsync(contentId); if (content is null) return PublishResult.Fail(内容不存在); // 领域规则只有草稿或已驳回状态可以发布 if (!content.CanPublish()) return PublishResult.Fail(当前状态不允许发布); content.Status ContentStatus.Published; content.PublishedAt DateTime.UtcNow; content.PublishedBy operatorId; await _db.SaveChangesAsync(); // 发布影响栏目下的内容列表清掉相关缓存 _cache.Remove($column_contents_{content.ColumnId}); return PublishResult.Ok(); } }这段代码有两个设计点值得展开。第一状态判断下沉到实体方法CanPublish()而不是在 Service 里写一长串 if-else好处是规则只有一处以后加状态不用满项目找判断。第二发布成功后主动清缓存而不是等缓存过期——门户的内容更新要求即时可见等过期在业务上不可接受。参数operatorId独立传入而不是从content上取是为了审计发布人和创建人可能不是同一个人日志里必须能区分。3.2 模块划分六个模块覆盖门户全部功能面功能模块按职责切成六块每块对应一组 Controller 和 Service边界清晰。内容管理模块是核心负责内容的增删改查、富文本编辑、附件上传、草稿与发布状态流转、版本修订。栏目管理模块负责栏目树的维护支持无限层级用 ParentId 自关联实现栏目上挂权限配置。用户与权限模块负责用户、角色、权限点三张核心表权限粒度做到“栏目 操作”如“新闻栏目-发布”。审批模块负责稿件从提交到发布之间的流转支持一级或多级审批审批记录留痕。系统配置模块负责站点名称、Logo、备案信息、友情链接这类可配置项避免改文案就要发版。日志审计模块记录登录日志和关键操作日志出问题时这是唯一的黑匣子。模块之间通过应用层接口通信不跨层直接调用。比如审批模块通过IContentQueryService读内容而不是直接查Contents表这样内容表结构变化时审批模块不受影响。这套划分的好处是分工明确三个人可以并行开发三个模块坏处是前期要把接口定好接口定错的返工成本不低所以建议第一周就把六个模块的接口签名全部落成代码骨架哪怕方法体是空的。3.3 数据库核心表设计八张表撑起门户数据模型不追求多够用且规范就行。核心八张表如下。表名关键字段说明UsersId, UserName, PasswordHash, DeptId, Status用户密码加盐哈希存储RolesId, RoleName, Description角色PermissionsId, Code, Name, Type权限点Code 形如 column:news:publishUserRolesUserId, RoleId用户角色关联RolePermissionsRoleId, PermissionId角色权限关联ColumnsId, ParentId, Name, Sort, IsNav栏目树ParentId 自关联ContentsId, ColumnId, Title, Body, Status, CreatedBy, PublishedAt内容主表AuditLogsId, UserId, Action, TargetType, TargetId, CreatedAt操作审计设计上有三个注意点。密码字段只存哈希不存明文这是底线没有商量余地。栏目表用 ParentId 自关联而不是嵌套集模型是因为门户栏目层级通常不超过四级自关联查询简单、维护直观嵌套集那套左右值方案在门户场景属于过度设计。审计日志表要预留索引UserId CreatedAt否则数据量上去后按人查日志会慢到怀疑人生。另外内容表把正文和附件分表存附件表挂 ContentId 外键避免正文表被大字段拖慢列表查询。4. 关键模块实现内容发布、权限控制与审批流4.1 内容发布链路从富文本到静态化内容发布是门户里操作最频繁的链路也是最容易出问题的地方。完整链路是编辑在后台填写标题、摘要、正文富文本、封面图选择栏目和发布时间保存为草稿提交审批后进入待审状态审批通过后状态变为已发布前台按栏目聚合展示。富文本这块有个绕不开的坑编辑器输出的 HTML 必须做服务端清洗。前端编辑器的过滤可以被绕过直接构造请求就能提交带脚本的 HTML门户一旦被注入影响的就不只是自己。通常的做法是引入 HTML 清洗库在保存前对正文做白名单过滤只允许常规标签和属性。// 内容保存时的服务端清洗白名单策略 public string SanitizeHtml(string rawHtml) { // 白名单只放行排版类标签脚本、iframe、事件属性一律清除 var sanitizer new HtmlSanitizer(); sanitizer.AllowedTags.Clear(); sanitizer.AllowedTags.Add(p); sanitizer.AllowedTags.Add(img); sanitizer.AllowedTags.Add(a); sanitizer.AllowedTags.Add(ul); sanitizer.AllowedTags.Add(li); sanitizer.AllowedTags.Add(h2); sanitizer.AllowedAttributes.Add(href); sanitizer.AllowedAttributes.Add(src); sanitizer.AllowedAttributes.Add(alt); return sanitizer.Sanitize(rawHtml); }清洗策略的关键是“默认拒绝、显式放行”而不是反过来列黑名单——黑名单永远列不全。href和src放行的同时还要在渲染端做二次校验防止javascript:伪协议。图片统一上传到站内存储并重命名用 GUID 或时间戳不直接引用外链图床避免图床挂掉后门户一片红叉。发布之后还有一步常被忽略内容列表的查询优化。前台列表页只查摘要字段标题、摘要、封面、发布时间、栏目不查正文大字段SQL 上就是显式 Select 需要的列而不是SELECT *。这一条在数据量小的时候看不出差别攒到几万条内容时列表页会肉眼可见地变慢。4.2 权限控制基于策略的授权而不是到处写 if门户的权限必须做到“栏目 操作”粒度。常见错误做法是在每个 Action 里写if (currentUser.Role ! Admin) return Forbid();几十个 Action 复制粘贴改一次权限规则要改几十处。正确做法是用 ASP.NET Core 的策略授权把权限判断收口成声明式。// 注册基于权限点的授权策略 builder.Services.AddAuthorization(options { // 动态生成策略每个权限 Code 对应一个策略 foreach (var code in PermissionCodes.All) { options.AddPolicy(code, policy policy.RequireClaim(permission, code)); } }); // Controller 上声明式使用不写任何 if [Authorize(Policy column:news:publish)] public IActionResult Publish(int id) { ... }这套方案的核心是把权限点做成 Claim 写进登录凭证用户登录时查出其所有角色对应的权限 Code 集合一次性塞进身份信息。Action 上只声明需要的权限点框架负责校验。参数说明PermissionCodes.All是权限点常量集合建议用静态类维护前后端共用同一份定义Claim 的类型名permission要全局统一改名会导致所有策略失效。栏目级的数据权限还要再叠一层即使有“新闻栏目-发布”权限也只能操作自己部门的栏目。这层在应用层做数据过滤查询栏目时按用户所属部门过滤而不是在授权层做。两层分开授权层管“能不能做这个操作”数据层管“能操作哪些数据”职责清晰排查问题时也好定位。4.3 审批流状态机而不是散落的布尔字段审批流的实现方式直接决定后期维护成本。见过最糟的写法是在内容表上加IsSubmitted、IsApproved、IsRejected三个布尔字段组合起来有八种状态其中一半是非法的。正确做法是把内容状态定义成枚举流转规则集中管理。当前状态允许操作目标状态操作角色草稿提交待审批内容编辑待审批通过已发布部门管理员待审批驳回已驳回部门管理员已驳回修改后重新提交待审批内容编辑已发布下线已下线部门管理员状态流转集中在一个状态机类里每次流转前校验“当前状态 操作 操作人权限”三者是否匹配校验通过才改状态并写审批记录。审批记录单独一张表记下每次流转的操作人、时间、意见形成完整留痕。这套设计的好处是加审批层级时只要扩状态枚举和流转表业务代码不动坏处是前期要把状态图画全画的时候把“已发布能不能编辑”这类边界问题一次性讨论清楚——我的建议是已发布内容不允许直接编辑要改必须先下线或走修订否则线上内容和审批记录对不上审计时说不清。5. 避坑与排查门户项目里最容易翻车的五个点5.1 登录态丢失Session 配置顺序与分布式部署现象是用户登录成功跳转后立刻变回未登录或者偶尔正常偶尔失效。原因通常有两个一是中间件顺序错了UseSession放在了UseRouting之前Session 中间件拿不到路由上下文二是部署到多台服务器后用了内存 Session请求被负载均衡打到另一台机器就读不到会话。解决办法先检查中间件顺序本地单机没问题再上多机的话把 Session 存储换成数据库或 Redis并在配置里显式指定 Session Cookie 的名称和过期时间避免和其他站点冲突。5.2 富文本内容被二次转义现象是后台编辑好的排版前台显示出一堆p标签原文。原因是 Razor 默认对输出做 HTML 编码正文这种可信 HTML 要用Html.Raw()输出。但这里有个连环坑一旦用了Html.Raw前面 4.1 节的服务端清洗就必须做到位否则等于把注入通道打开。解决顺序是先确保清洗生效再在渲染端放开编码两件事必须成对出现只做一半比不做更危险。5.3 栏目树查询的 N1 问题现象是导航栏渲染慢日志里一次页面请求打了几十条 SQL。原因是按 ParentId 逐层递归查询每层一次数据库往返。解决办法是栏目数量少通常几十到几百条一次性把全部栏目查出来在内存里组装成树或者把组装好的树放进内存缓存栏目增删改时清缓存。栏目树是典型的“读多写极少”数据缓存收益极高这也是 2.2 节里AddMemoryCache的用意。5.4 附件上传的路径与权限漏洞现象是上传的图片能访问但用直接构造的路径能下载到别人上传的合同附件。原因是附件目录没有按业务隔离且静态文件中间件把整个上传目录都暴露了。解决办法上传目录按业务类型分文件夹敏感附件不放静态目录改由 Controller 校验权限后用FileStreamResult输出上传时校验扩展名白名单和文件头不信任客户端传来的 Content-Type文件名统一重命名防止路径穿越。5.5 发布后前台不更新现象是后台显示已发布前台栏目页还是旧内容。三种可能一是缓存没清对应 3.1 节的主动清缓存逻辑漏了某个入口二是前台用了输出缓存但没设失效策略三是数据库读写分离时读库延迟。排查顺序是先看缓存再看输出缓存最后怀疑读写延迟。我的习惯是在发布服务里把“清缓存”写成独立方法并打日志出问题时看日志就知道清没清、清了哪几个 key比盲猜快得多。6. 验收与答辩用一份验收清单把方案讲成闭环验收这件事学生和从业者的诉求其实一样证明这套门户真的能跑、能管、能扩。我的做法是准备一份验收清单逐项过过不了的当场记录别靠嘴说“没问题”。清单分四组覆盖功能、权限、性能和运维。验收组验收项通过标准功能三类内容单页/列表/外链全部可建可发前台正确渲染无乱码无转义功能栏目树增删改、排序、隐藏导航即时生效层级正确权限四类角色分别登录越权操作被拒编辑无法审批访客无法进后台权限部门管理员只能管本部门栏目跨部门操作返回无权限性能首页与列表页在万级内容下的响应常规配置下 1 秒内返回运维日志可查登录与关键操作按人按时间可检索运维配置项站点名/Logo/友情链接后台可改改完刷新即生效无需发版答辩环节最容易被问倒的不是功能是设计取舍。评委和评审最爱问的三类问题为什么不用前后端分离、权限为什么做到栏目级而不是菜单级、审批流为什么不做成可配置的工作流引擎。这三个问题提前准备好答案答的时候落到具体场景上——前后端分离会让内容页 SEO 变差、栏目级权限是因为编辑和部门管理员的职责边界就在栏目、工作流引擎对一个内部门户属于过度设计、状态机够用且可维护。答“因为复杂”不如答“因为在这个场景下收益不抵成本”后者才是工程师的语言。演示顺序也有讲究。别从登录页开始一路点那样十分钟讲不完。我的习惯是倒着演先演前台一个内容完整的栏目页说明数据从哪来再切后台演示发布一篇稿件的完整链路草稿→提交→审批→发布→前台可见最后演一次越权被拒证明权限是真的生效而不是摆设。这条动线三分钟能把方案的核心价值讲完剩下的时间留给提问。从那以后我每次交方案验收清单都是提前一周写好、自己先跑一遍的跑不通的项要么改代码要么改需求绝不留到评审现场翻车。这份 .NET 企业门户网站的完整方案从角色拆解、四层架构、六模块划分到八张核心表、三条关键实现链路和七项验收标准结构是完整的细节是按能直接落地写的拿去做毕设或课设的骨架、做企业门户的初版方案都够用。希望帮到你。本文还有配套的精品资源点击获取