ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

ASP.NET Core MVC企业网站实战:从架构到部署全解析

ASP.NET Core MVC企业网站实战:从架构到部署全解析 简介本资源是一个基于ASP.NET Core MVC框架构建的企业级内容管理系统KWCMS完整示例面向Web开发初学者与中级.NET开发者解决企业官网、产品展示及内容发布类网站的快速搭建问题。压缩包共2000个文件总大小17.13MB涵盖884个JavaScript前端交互脚本、320张PNG图标与界面素材、269个CSS样式文件、181个HTML静态页、54个C#核心业务逻辑类、50个Razor视图cshtml、以及MySQL/SQL Server双数据库支持所需的配置与迁移脚本。已有921人学习下载。项目采用EF Core Code-First模式实现数据建模与自动迁移内置用户管理、权限控制、内容发布等CMS典型模块并包含ASP经典组件如Uploader.Class.asp、config_loader.asp等兼容层便于传统ASP站点向Core平滑过渡目录结构清晰模型-控制器-视图分层规范附带完整启动配置与多环境部署说明可直接运行并二次开发。 最近帮一个客户搭企业官网后端我直接选了asp.net core mvc。说实话做了这么多年.NET项目这个组合到今天依然是做传统企业网站最稳的选择之一。项目从需求梳理到上线用了不到三周中间踩了不少坑也沉淀了一些比较实用的经验。这篇文章就围绕这个企业网站示例项目把架构思路、核心模块实现、安全防护和问题排查全过程拆开讲清楚。如果你正准备用asp.net core mvc做企业站、营销展示站或者想把老旧的Web Forms项目重构到asp.net core mvc这篇文章可以直接当参考手册用。文章不会堆概念我会尽量用项目里的真实代码和配置说话遇到需要解释原理的地方也会展开讲清楚保证看完之后你能自己动手搭一套。1. 项目整体设计与架构思路1.1 需求分析与方案选型先说需求。客户那边是一家做工业设备的中型厂商网站要承担三件事品牌形象展示、产品信息发布、潜在客户线索收集。没有电商交易没有会员体系后台管理也只要求能维护产品、新闻和接收留言。这种需求在企业站里非常典型核心特点就是内容为主、交互较轻。选型的时候我其实对比了好几个方案。Blazor确实交互体验好但企业站这种项目大部分页面是服务端渲染的内容页Blazor的实时交互优势发挥不出来反而会增加前端资源加载压力。传统Web Forms已经处于维护模式新项目用起来总觉得背着历史包袱。最后定了asp.net core mvc理由很直接成熟的MVC模式路由、模型绑定、验证、依赖注入这些基础设施开箱即用服务端渲染对SEO天然友好企业官网最看重的就是搜索引擎收录生态完善跟EF Core、Serilog、FluentValidation这些第三方库配合非常顺畅部署灵活可以跑在Windows IIS上也能用Docker容器化部署后来项目做完复盘这个选型是站得住的。整个网站几十个页面大部分是数据展示型页面asp.net core mvc这种约定大于配置的风格让开发效率很高团队成员不需要额外学习复杂的框架知识就能上手。1.2 解决方案结构设计项目结构我采用了经典的分层架构没有过度设计。很多初学者喜欢把项目拆成五六个类库什么Application、Domain、Infrastructure、WebAPI但对于企业站这种体量的项目过度分层只会让维护成本变高。我最终的结构是这样CompanyWebsite.sln ├── src/ │ ├── CompanyWebsite.Web/ // MVC主项目 │ ├── CompanyWebsite.Core/ // 实体、接口定义 │ └── CompanyWebsite.Data/ // EF Core数据访问 └── tests/ └── CompanyWebsite.Tests/ // 单元测试项目CompanyWebsite.Core放实体类和仓储接口不引用任何基础设施层面的东西。CompanyWebsite.Data负责EF Core的DbContext配置、数据迁移和仓储实现。CompanyWebsite.Web是表现层引用了Core和Data负责控制器、视图、模型绑定和管线配置。这里有一个值得注意的点很多教程会推荐在Web项目里直接放Models文件夹但实际项目里我倾向于把视图模型放到Web项目内部的ViewModels子目录把领域实体放到Core项目里。这样区分的好处是视图模型可以自由添加验证特性、展示层属性而不污染领域实体。比如企业站的产品列表页需要显示价格区间字符串这种展示逻辑不应该出现在实体类里。1.3 MVC模式的理解与定位关于MVC架构模式趁机多聊几句。很多人在面试里被问过MVC、MVP、MVVM的区别实际写代码的时候反而容易忽略模式本身的约束。ASP.NET Core MVC中的M严格来说是模型包含了视图模型和领域模型两套东西V是视图负责展示C是控制器负责接收请求、调度业务逻辑、返回响应。MVC的核心价值在于关注点分离。控制器不写SQL视图不写业务逻辑模型不感知HTTP上下文。这样约束下来代码的可测试性会有质的提升。我见过很多伪MVC项目把业务逻辑全塞在控制器里一个Action几百行数据库查询、字符串拼接、文件操作全在里面这种代码基本无法单元测试也没法在多个Action间复用逻辑。对比一下MVP和MVVM。MVP中的Presenter完全接管了视图的行为逻辑适合WinForms、WPF这种事件驱动的桌面应用MVVM利用数据绑定让View和ViewModel自动同步适合WPF、前端框架这类有完善绑定机制的场合。而asp.net core mvc的请求-响应循环更贴近Web的本质每次请求都是无状态的控制器Action就是一个纯粹的处理入口这让它在服务端渲染场景下优势非常明显。2. 环境搭建与项目初始化2.1 开发环境与工具准备项目用的是当前最新的.NET 9版本开发工具是Visual Studio 2022数据库选的SQL Server 2019。如果你只是个人学习或者做轻量级企业站用Visual Studio Code加上.NET CLI也完全够用。需要安装的组件有这么几项.NET 9 SDK这是核心运行时和编译工具链Visual Studio 2022或者VS Code选一个就行SQL Server Express或者LocalDB数据库用Git版本控制我习惯用dotnet CLI创建项目执行速度比Visual Studio的向导快不少而且CLI命令本身就是文档方便后续写脚本自动化构建。dotnet new mvc -n CompanyWebsite.Web -o src/CompanyWebsite.Web dotnet new classlib -n CompanyWebsite.Core -o src/CompanyWebsite.Core dotnet new classlib -n CompanyWebsite.Data -o src/CompanyWebsite.Data创建完项目之后还需要建立项目引用关系。Web引用Core和DataData引用Core。引用关系一定要单向流动如果出现循环引用说明分层设计有问题需要重新梳理职责边界。2.2 创建ASP.NET Core MVC项目用dotnet new mvc生成的模板项目自带了一整套MVC的骨架代码。Program.cs是应用的入口ConfigureServices方法负责注册服务中间件管线定义了请求从进入到响应的处理顺序。看一下新版模板的关键代码var builder WebApplication.CreateBuilder(args); // 注册MVC服务 builder.Services.AddControllersWithViews(); var app builder.Build(); // 配置请求管线 if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler(/Home/Error); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: default, pattern: {controllerHome}/{actionIndex}/{id?}); app.Run();这里面每行代码都有讲究。AddControllersWithViews注册了MVC框架需要的所有服务包括模型绑定器、Action执行器、视图引擎、各种过滤器它的内部实现比老ASP.NET的AddMvc要精简很多。UseStaticFiles很重要它让wwwroot目录下的静态文件可以直接通过URL访问。企业站的CSS、JavaScript、图片资源都放在这里如果不调用这个方法浏览器请求css文件会返回404。MapControllerRoute定义了默认路由规则。{controllerHome}/{actionIndex}/{id?}的意思是如果是根路径/默认访问Home控制器的Index方法如果是/Product/Detail/5就访问Product控制器的Detail方法参数id等于5。这里问号表示id是可选参数注意模型绑定会把字符串类型的路由值转换成Action方法参数的类型比如id是int类型的话会自动转换转换失败会返回404或者默认值。2.3 基础配置解析appsettings.json是整个应用的配置中心数据库连接字符串、日志级别、业务参数都可以放这里。我用了一个比较标准的配置结构{ ConnectionStrings: { DefaultConnection: Server.;DatabaseCompanyWebsite;Trusted_ConnectionTrue;TrustServerCertificateTrue; }, Logging: { LogLevel: { Default: Information, Microsoft.AspNetCore: Warning } }, AllowedHosts: *, SiteSettings: { Name: 某某工业设备有限公司, Phone: 400-888-8888, Email: contactexample.com } }SiteSettings这个自定义配置节我专门用来存网站的基础信息比如公司名称、联系电话、邮箱这些。这样设计的好处是修改这些信息不需要改代码运营人员直接改配置文件或者数据库配置表就行。读取配置的方式是通过IOptions 模式public class SiteSettings { public string Name { get; set; } string.Empty; public string Phone { get; set; } string.Empty; public string Email { get; set; } string.Empty; } // 在Program.cs中注册 builder.Services.ConfigureSiteSettings(builder.Configuration.GetSection(SiteSettings)); // 在控制器中注入 public class HomeController : Controller { private readonly IOptionsSiteSettings _siteSettings; public HomeController(IOptionsSiteSettings siteSettings) { _siteSettings siteSettings; } }IOptions 是单例模式整个应用生命周期内只读取一次配置。对于运行期可能需要热更新的场景可以用IOptionsMonitor 但企业站这种静态信息用IOptions就足够了。3. 核心功能实现与关键环节3.1 布局系统与导航设计企业网站内容虽然多但大部分页面共享相同的头部导航和底部信息。ASP.NET Core MVC的布局页Layout机制就是为这种场景设计的。项目默认的布局页在Views/Shared/_Layout.cshtml所有视图如果不指定其他布局都会自动套用这个页面。布局页的结构是这样的!DOCTYPE html html langzh-CN head meta charsetutf-8 / meta nameviewport contentwidthdevice-width, initial-scale1.0 / titleViewData[Title] - 某某工业设备有限公司/title link relstylesheet href~/css/site.css asp-append-versiontrue / await RenderSectionAsync(Styles, required: false) /head body header nav classnavbar div classcontainer a classnavbar-brand asp-controllerHome asp-actionIndex公司Logo/a ul classnavbar-nav lia asp-controllerHome asp-actionIndex首页/a/li lia asp-controllerProduct asp-actionIndex产品中心/a/li lia asp-controllerNews asp-actionIndex新闻资讯/a/li lia asp-controllerHome asp-actionAbout关于我们/a/li lia asp-controllerContact asp-actionIndex联系我们/a/li /ul /div /nav /header main classcontainer RenderBody() /main footer div classcontainer pcopy; DateTime.Now.Year - 某某工业设备有限公司/p /div /footer script src~/js/site.js asp-append-versiontrue/script await RenderSectionAsync(Scripts, required: false) /body /html导航链接我用了Tag Helper语法asp-controller和asp-action而不是直接写硬编码的URL。这样做的最大好处是如果路由规则调整或者控制器改名Tag Helper会自动生成正确的链接避免了手工维护URL字符串。asp-append-versiontrue这个特性也有用它会给静态资源链接自动附加一个哈希版本号资源内容一改版本号跟着变浏览器就不会继续使用缓存里的旧文件解决了企业站更新静态资源后用户看到旧版本的问题。企业站通常需要给不同页面设置不同的SEO标题、关键词和描述。布局页通过ViewData[Title]获取每个页面传入的标题数据。在控制器Action里设置ViewData[Title] 产品中心;就可以覆盖默认标题。如果页面需要额外的CSS或者JS可以用section Styles和section Scripts在子视图中注入。这个机制在企业站开发中几乎每个页面都会用到。3.2 产品展示模块与分页实现企业网站的核心模块是产品展示。我设计了ProductController来处理产品相关的请求产品列表页面需要支持按分类筛选、关键字搜索和分页。看一下核心的控制器代码public class ProductController : Controller { private readonly IRepositoryProduct _productRepository; private readonly IRepositoryProductCategory _categoryRepository; public ProductController(IRepositoryProduct productRepository, IRepositoryProductCategory categoryRepository) { _productRepository productRepository; _categoryRepository categoryRepository; } public async TaskIActionResult Index(string category, string keyword, int page 1, int pageSize 9) { var query _productRepository.Query() .Where(p p.IsPublished); if (!string.IsNullOrEmpty(category)) { query query.Where(p p.Category.Slug category); } if (!string.IsNullOrEmpty(keyword)) { query query.Where(p p.Name.Contains(keyword) || p.Description.Contains(keyword)); } var totalCount await query.CountAsync(); var products await query .OrderByDescending(p p.CreatedAt) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); var viewModel new ProductListViewModel { Products products, CurrentCategory category, Keyword keyword, CurrentPage page, PageSize pageSize, TotalCount totalCount }; return View(viewModel); } }这段代码里有几个关键点值得展开。第一IRepository 是我在Core项目里定义的仓储接口封装了IQueryable 的查询入口。这里不返回IQueryable而是返回TaskList 因为EF Core的延迟执行会导致查询在用到的时候才真正执行如果控制器里没法保证DbContext的声明周期很容易出现DisposedObjectException。所以我在仓储层就直接把数据取回内存。第二分页查询用Skip和Take实现EF Core会自动翻译成SQL Server的OFFSET FETCH语法性能有保证。注意这里一定不能用ToList之后再分页否则会把整张表的数据全捞到内存里企业站产品量一多就会出现明显的性能问题。第三keyword搜索用的Contains对应SQL的LIKE %keyword%。对于企业站这种数据量这种方式完全够用。如果将来数据量上来了再考虑引入全文检索或者第三方搜索引擎也不迟。视图这边产品列表页面用Razor语法循环渲染每个产品卡片。每个产品关联一张封面图、名称和简短描述点击进入详情页。详情页的URL我设计成/Product/Detail/5这种数字主键风格简单直接。如果产品有别名Slug也可以用/Product/Detail/my-product-name这种语义化URL对SEO更友好但需要额外维护别名唯一性。3.3 表单处理与远程验证技术企业网站的联系我们表单是转化线索的关键入口也是整个项目里我花心思比较多的部分。这个表单有姓名、电话、邮箱、留言内容四个字段需要做客户端和服务端双重验证。ASP.NET Core MVC自带了一套基于数据注解的验证框架。我在ViewModel上声明验证规则public class ContactFormViewModel { [Required(ErrorMessage 姓名不能为空)] [StringLength(20, ErrorMessage 姓名最长20个字符)] [Display(Name 姓名)] public string Name { get; set; } string.Empty; [Required(ErrorMessage 手机号不能为空)] [RegularExpression(^1[3-9]\d{9}$, ErrorMessage 请输入合法的手机号)] [Display(Name 手机号)] public string Phone { get; set; } string.Empty; [Required(ErrorMessage 邮箱不能为空)] [EmailAddress(ErrorMessage 请输入合法的邮箱地址)] [Display(Name 邮箱)] public string Email { get; set; } string.Empty; [Required(ErrorMessage 留言内容不能为空)] [StringLength(500, MinimumLength 10, ErrorMessage 留言内容需在10到500个字符之间)] [Display(Name 留言内容)] public string Message { get; set; } string.Empty; }这些数据注解在服务端验证和客户端验证中都会生效。服务端验证发生在模型绑定之后如果ModelState.IsValid为false控制器会返回错误信息给视图。客户端验证是jQuery Validation Unobtrusive插件根据这些特性自动生成的用户在输入框失焦时就能看到错误提示不需要提交到服务端。这里要特别说明远程验证Remote Validation技术。远程验证是一种AJAX验证方式适用于需要访问数据库才能判断是否合法的场景。企业站常见的应用场景有两个用户名注册时检查是否唯一以及留言表单中防止同一个人短时间重复提交。实现方式是在ViewModel属性上挂[Remote]特性public class ContactFormViewModel { public string Phone { get; set; } // 远程验证提交时异步调用CheckPhone Action验证号码 [Remote(action: CheckPhone, controller: Contact)] public string ConfirmPhone { get; set; } }然后在对用的控制器里写验证Action[AcceptVerbs(GET, POST)] public IActionResult CheckPhone(string confirmPhone) { var existing _messageRepository.Query() .Any(m m.Phone confirmPhone m.CreatedAt DateTime.Now.AddMinutes(5)); if (existing) { return Json($手机号 {confirmPhone} 在最近5分钟内已提交过留言请勿重复提交); } return Json(true); }远程验证的精妙之处在于它把需要数据库判断的验证逻辑从表单提交流程中分离出来用户填到那个字段就能立刻得到反馈体验很好。但这里有个坑Remote验证默认只接受GET请求而企业站的留言数据属于敏感信息GET请求会把参数暴露在URL和服务器日志里。所以我写代码时用了[AcceptVerbs(GET, POST)]同时支持两种请求然后在Remote特性里指定HttpMethodPOST[Remote(action: CheckPhone, controller: Contact, HttpMethod POST)] public string ConfirmPhone { get; set; }这样客户端验证会改用POST发送AJAX请求数据安全很多。凡是涉及查询用户输入的远程验证我都建议用POST方式这只是把习惯调整一下却能减少敏感信息暴露面。3.4 数据访问与SQL注入防护企业网站最容易被忽视的安全问题就是SQL注入。很多刚转.NET的开发者喜欢用拼接SQL字符串的方式查询数据这在现代ASP.NET Core应用里是完全不必要的危险操作。项目里我全程使用EF Core所有数据库操作都走参数化查询从机制上杜绝了SQL注入的可能。EF Core本质上就是一套对象关系映射框架它把C#表达式树转换成参数化的SQL语句。比如上面的查询代码query.Where(p p.Category.Slug category)EF Core生成的SQL是SELECT * FROM Products p WHERE EXISTS ( SELECT 1 FROM Categories c WHERE c.Id p.CategoryId AND c.Slug __category_0 )这里的__category_0就是参数化查询参数。用户输入的category值不管包含什么恶意内容到SQL语句里都只是一个值不会被解析成SQL代码。这就是参数化查询的威力。但光靠EF Core还是不够的有些场景下必须写原生SQL比如复杂的报表查询、大数据量批量更新。这时候就要用FromSqlRaw或ExecuteSqlRaw方法这两个方法在EF Core 7之后就要求用户显式传递参数对象不能直接拼接字符串。有人还是会图省事直接写var sql $SELECT * FROM Products WHERE Name LIKE %{keyword}%; var products await _context.Products.FromSqlRaw(sql).ToListAsync();这个写法等于把EF Core的安全保障全部扔掉了keyword如果包含敏感内容整个数据库都有被拖库的风险。正确的写法是用两个参数var sql SELECT * FROM Products WHERE Name LIKE keyword; var keywordParam new SqlParameter(keyword, $%{keyword}%); var products await _context.Products.FromSqlRaw(sql, keywordParam).ToListAsync();两种写法对比后者把keyword作为参数传给数据库引擎数据库会先把SQL语句编译好然后把参数当作纯数据处理没有任何解释成代码的空间。在实际企业站项目中这种场景其实很少但如果真的遇到了记住一条原则任何用户输入内容一律参数化禁止拼接。另外还要提一下数据校验的纵深防御思路。EF Core在SaveChanges的时候如果检测到某些字段违反实体配置比如字符串超长会抛异常。我习惯在实体类上再配合Fluent API做字段长度约束双保险。4. 常见问题与排查技巧实录4.1 依赖注入生命周期问题排查企业站项目用了依赖注入DI最常见的问题就是DbContext的生存期设置错误导致程序崩溃或者性能异常。EF Core的DbContext注册为Scoped生命周期这意味着每个HTTP请求会创建一个新的实例请求结束自动释放。如果在写代码时把DbContext注入到单例服务里就会抛出异常Cannot consume scoped service AppDbContext from singleton ISomeService.这个异常信息其实已经很明确但很多人第一次遇到还是懵。根本原因是要理解三种生命周期Transient每次获取都创建新实例最轻量Scoped每个请求作用域内单例请求结束释放Singleton进程内全局单例应用生命周期内只有一个企业站的DbContext必须是Scoped不能在Singleton服务里注入Scoped服务。如果确实有这种需求唯一的办法是使用IServiceScopeFactory在单例服务里手动创建作用域来获取Scoped服务但这种情况属于设计问题的补救措施建议不要滥用。另一个相关问题是DbContext的多个实例同时访问数据库时报并发冲突。这个问题往往出现在一个Action里多次new DbContext的情况。正确做法是全程通过构造函数注入同一个DbContext实例EF Core内部有ChangeTracker自动跟踪实体状态多个查询共享一个上下文实例能保持数据一致性。4.2 路由配置不当导致页面404asp.net core mvc的路由有约定路由Conventional Routing和属性路由Attribute Routing两种方式。项目默认用的是约定路由定义在Program.cs里的MapControllerRoute。这种方式的优点是规则统一维护成本低。但有几种情况特别容易导致404。第一种是控制器没有以Controller结尾。MVC的约定是控制器类名必须以Controller后缀结束比如HomeController如果写成HomePage那么无论你访问什么URL都匹配不到这个控制器。第二种是Action方法没有声明为publicMVC只会把public方法当作可访问的Action。第三种是视图文件不存在注意Razor视图的文件路径必须和控制器、Action名称对应。属性路由适合API场景企业站前端页面建议统一用约定路由。如果你真的要在控制器上混用属性路由注意属性路由的优先级高于约定路由而且一旦控制器上标记了[Route]特性约定路由就不会再对该控制器生效。4.3 静态资源无法加载企业站上线后最容易遇到的一个问题页面样式正常但图片不显示或者CSS、JS文件404。这个问题的排查路径非常简单第一步确认文件在wwwroot目录下。ASP.NET Core默认只把wwwroot目录下的文件作为静态资源对外提供服务放在其他目录的文件即使URL路径正确也无法访问。第二步确认调用了UseStaticFiles。如果不调用这个中间件浏览器请求css文件会返回404。这个坑在项目模板里默认没有需要手动加上。第三步检查路径大小写。企业站如果部署在Linux容器里文件系统是区分大小写的而Windows不区分。这就导致本地开发好好的部署到Linux服务器后图片全部裂开。解决方案是统一使用小写文件名或者严格保持URL路径和实际文件名大小写一致。第四步确认没有和路由冲突。如果路由模式定义为{controllerHome}/{actionIndex}/{id?}静态文件中间件会先于路由执行所以理论上不会冲突。但如果把UseStaticFiles写到了MapControllerRoute之后那就不一定能正确匹配到文件了。4.4 性能优化建议企业站虽然交互不多但性能依然不能忽视。有一段时间客户反馈后台管理页面上传产品图片后首页加载变得很慢。排查发现是产品图片没有做压缩处理一张照片好几MB直接就往数据库和静态目录里放。后来我加了一个图片处理服务上传时自动压缩到合适的尺寸首屏加载速度从4秒降到了1.2秒。另外几个实用的优化手段响应压缩在Program.cs里注册UseResponseCompression对文本类响应启用Gzip或Brotli压缩通常能减少70%左右的传输体积浏览器缓存给静态资源设置Cache-Control头配合前面布局页里提到的asp-append-version让浏览器在文件没变化的情况下直接用缓存数据库索引给产品表的IsPublished、CategoryId、CreatedAt这些常见查询条件字段添加索引效果立竿见影按需加载对于图片多的页面用LazyLoad方式让图片在滚动到视口时才加载我特别想强调响应压缩这一项。很多开发者觉得网站性能优化就要上Redis缓存、CDN其实先把压缩中间件打开把静态资源压缩掉小项目的性能就已经提升一大截了。这些动作不需要额外部署任何组件纯代码配置性价比极高。5. 部署与上线经验5.1 发布配置与部署方式企业站部署我推荐两种方式具体看客户的运维能力。如果客户有Windows服务器直接发布到IIS是最稳妥的选择如果是走容器化路线用Docker镜像部署更灵活。IIS部署需要安装ASP.NET Core Hosting Bundle这是微软提供的IIS托管模块。发布的时候在项目上右键选择发布目标选文件夹然后把生成的发布文件拷贝到服务器的网站目录。需要注意配置IIS应用程序池的.NET CLR版本要选无托管代码因为ASP.NET Core是独立进程运行IIS只是做了反向代理。Docker部署需要先写好Dockerfile。这个是最常用的镜像定义FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base WORKDIR /app EXPOSE 8080 FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build WORKDIR /src COPY . . RUN dotnet restore RUN dotnet publish -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --frombuild /app/publish . ENTRYPOINT [dotnet, CompanyWebsite.Web.dll]这个Dockerfile用了多阶段构建先把代码编译发布再拷贝到运行时镜像里最终镜像体积能控制在100MB以内。运行时用的aspnet镜像只包含ASP.NET Core运行时不含SDK安全问题少很多。有些企业站还会遇到连接字符串放到环境变量里的需求。Docker部署时可以通过-e参数传环境变量在Program.cs里用builder.Configuration.GetConnectionString(DefaultConnection)读取配置系统会自动把环境变量映射过来。一定要避免把数据库账号密码直接写死在镜像里。5.2 数据安全与备份策略企业站上了线数据安全就是头等大事。我会在项目里做三个层面的安全措施第一个层面是配置保护。生产环境的连接字符串和密钥用环境变量或者密钥管理服务存储不写进appsettings.json更不进Git仓库。可以用appsettings.Production.json覆盖开发配置也可以直接在环境变量里设置ConnectionStrings__DefaultConnection。第二个层面是HTTPS强制跳转。Program.cs里默认有UseHttpsRedirection这行代码会在用户通过HTTP访问时自动301跳转到HTTPS。线上环境务必配置好SSL证书企业站涉及用户留言提交不加密的传输协议等于把用户信息裸奔在网络上。第三个层面是数据备份。数据库的定时备份任务要提前配置好备份文件异地存放。我见过不止一个企业站因为数据库文件意外损坏又没有备份结果整个网站的产品信息和留言数据全部丢失这个教训太惨痛了。对于企业站来说数据量不大但都是宝贵资产每天做一次全量备份完全够用。5.3 项目后续扩展规划企业站上线只是第一步后续的维护和扩展更加重要。根据我协助客户做过的一些需求最常见的扩展方向有这么几个增加后台管理模块用AdminLTE或者类似的模板做一套管理界面让运营人员自己能维护产品、新闻和网站配置接入数据统计在前端埋点或者在服务端集成日志分析了解用户访问行为增加在线询盘功能把留言表单升级成更完整的需求提交流程对接收单员通知多语言支持如果是做外贸的企业站中英双语是最基本的需求站点搜索优化如果产品很多可以引入Elasticsearch或者数据库全文索引做扩展规划的时候我建议遵循一个原则别一上来就做一个大而全的系统先根据客户的实际使用反馈把最痛的点解决掉。比如客户如果反复提到后台维护产品很麻烦那就先把后台管理模块做完善如果客户关心询盘转化那就优先优化表单交互和线索通知流程。技术选型上当前的分层架构已经为这些扩展预留了空间Controller可以加Service可以加数据库表也可以加不需要推倒重来。写在最后回顾这个asp.net core mvc企业网站示例项目我觉得最有价值的一点是验证了一套适合中小型团队的标准化打法。选型上用asp.net core mvc搭配EF Core架构上分层清晰不过度设计安全上把SQL注入和远程验证这两个点吃透性能上用好压缩和缓存这些基本功。这套组合延续了.NET平台一贯的稳健风格对开发者的要求没那么苛刻但交付质量非常可靠。最后分享一个实操中的小技巧项目上线前建议花半天时间把网站所有页面用工具自动跑一遍链接检查把死链、404、资源错误全部揪出来。这类问题在开发环境往往注意不到但用户访问时体验非常差。我用的是一款叫Xenu Link Sleuth的免费工具跑一遍能生成完整的断链报告省去了大量手工点击测试的时间。如果你正在考量企业网站的技术选型或者已经在用asp.net core mvc做这类项目希望这篇文章能帮你少走几步弯路。有机会咱们再聊后台管理模块的设计和其他扩展方案。本文还有配套的精品资源点击获取
返回列表