本文对应版本:EasyAdminBlazor 2.3.0,在线升级扩展对应 EasyAdminBlazor.Upgrade 2.4.0-preview.4。
EasyAdminBlazor 面向 .NET 开发者,Windows + IIS 是很多接单交付的首选(客户有服务器、运维熟悉 IIS)。这篇以 IIS 为主线写完整流程,并补充 Windows 服务形态(在线升级必需)与 Linux 宝塔部署,Nginx 放在最后作为补充。
说明:Hosting Bundle、应用池、证书这些属于 ASP.NET Core + IIS 的通用步骤,不是框架特有的;文中与框架相关的配置(Data Protection、WorkId、UseAutoSyncStructure、在线升级等)会标注来源。
一、发布
dotnet publish -c Release -o publish
把 publish 目录整体拷到服务器。服务器不需要装 SDK,但需要:
- .NET 10 Runtime
- ASP.NET Core Hosting Bundle(IIS 部署必须,见下一节)
发布完成后目录结构大致如下:
publish\
├── 程序文件 / wwwroot /
└── updater\ ← 引用升级扩展后自动生成,别手工删
└── updates\ ← 引用升级扩展后自动生成
├── index.json
└── v1.0.0\ update.zip + release.json + files.json
如果只做普通 IIS 部署、不需要在线升级,updater\ 与 updates\ 不会出现,忽略即可。
二、前置条件:IIS 环境
1. 安装 Hosting Bundle
从微软官网下载 ASP.NET Core Hosting Bundle(版本与项目目标框架一致,即 .NET 10)。它会安装 .NET Runtime 与 ASP.NET Core Module(ANCM)——IIS 与 Kestrel 之间的桥梁。
安装后重启 IIS:
iisreset
2. 启用 WebSocket 协议(最容易漏的一步)
Blazor Server 依赖 WebSocket 长连接。IIS 默认不安装 WebSocket 功能,不启用会出现“后台页面反复重连/刷新”。
Windows Server(服务器管理器):
服务器管理器 → 添加角色和功能 → Web 服务器(IIS) → 应用程序开发
→ 勾选"WebSocket 协议" → 安装
PowerShell(管理员):
Install-WindowsFeature Web-WebSockets
iisreset
Windows 客户端 / 独立 IIS:
控制面板 → 启用或关闭 Windows 功能 → Internet Information Services
→ 万维网服务 → 应用程序开发功能 → 勾选"WebSocket 协议"
安装后重启应用程序池。
三、创建站点与应用池
| 项 | 设置 | 原因 |
|---|---|---|
| 站点物理路径 | publish 目录 | 直接指向发布物 |
| 应用程序池 .NET CLR 版本 | 无托管代码 | ASP.NET Core 由 ANCM 托管,IIS 不负责加载 CLR |
| 托管管道模式 | 集成 | 默认即可 |
| 启用 32 位应用程序 | False | 除非依赖 32 位组件 |
| 启动模式 | AlwaysRunning | 避免首次访问冷启动 |
| 空闲超时 | 0(不回收)或较大值 | 默认 20 分钟回收会打断在线用户 |
| 回收 → 固定时间间隔 | 0(禁用)或设到业务低峰 | 同上 |
关于“不回收”:IIS 默认定期回收应用池,Blazor Server 下会强制所有在线用户断线重连。后台系统建议关闭定期回收,或设到凌晨并配合 DisconnectedCircuitRetentionPeriod(第 26 篇)。
四、生产配置与环境变量
1. 设置环境
ASPNETCORE_ENVIRONMENT = Production
这样应用会读取 appsettings.Production.json 覆盖开发配置。在 IIS 里通过 web.config 设置:
<aspNetCore processPath="dotnet"
arguments=".\YourApp.dll"
stdoutLogEnabled="false"
stdoutLogFile=".\logs\stdout"
hostingModel="inprocess">
<environmentVariables>
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" />
</environmentVariables>
</aspNetCore>
hostingModel="inprocess" 是 IIS 上的推荐模式(进程内托管,性能更好)。如果遇到连接相关的问题,可以对比 outofprocess 验证差异。
覆盖连接串等敏感配置时优先用环境变量,不要写进仓库里的
appsettings.json。
2. 数据库与自动建表
FreeSqlBuilder = a => a
.UseConnectionString(DataType.Sqlite, configuration["ConnectionStrings:default"])
.UseMonitorCommand(cmd => System.Console.WriteLine($"[{DateTime.Now.ToString("HH:mm:ss")}] {cmd.CommandText}\r\n"))//监听SQL语句
.UseAutoSyncStructure(env.IsDevelopment())
演示项目的写法就是推荐写法:开发环境自动同步表结构,生产环境不自动改结构。
doc/部署上线.md 里还有两条实践经验:
- MySQL 连接串建议带
Charset=utf8mb4,避免中文乱码; - 首次上线确需自动建表时,临时开启、跑通后关闭。
3. Data Protection 密钥持久化(IIS 部署必配)
这是 IIS 部署里最容易被忽略、后果又最明显的一项。框架提供了配置项:
/// <summary>
/// Data Protection 密钥持久化目录。IIS 部署建议配置到应用目录外的固定路径,
/// 避免程序池重启/重新部署后密钥丢失导致已登录用户被强制重新登录。
/// 为空时使用 ASP.NET Core 默认存储(IIS 下位于程序池标识的用户配置文件目录,不稳定)。
/// </summary>
public string DataProtectionKeyPath { get; set; } = "";
/// <summary>
/// Data Protection 应用名称。为空时使用默认应用名(随部署路径变化),
/// 建议配置固定名称以保证密钥环稳定。仅配置了 DataProtectionKeyPath 时生效。
/// </summary>
public string DataProtectionApplicationName { get; set; } = "";
用法:
builder.AddEasyAdminBlazor(new EasyAdminBlazorOptions
{
// ...
DataProtectionKeyPath = @"D:\EasyAdminKeys",
DataProtectionApplicationName = "MyAdminApp"
});
框架内部对应的注册逻辑:
var dataProtectionBuilder = builder.Services.AddDataProtection();
if (!string.IsNullOrWhiteSpace(options.DataProtectionKeyPath))
{
dataProtectionBuilder.PersistKeysToFileSystem(new DirectoryInfo(options.DataProtectionKeyPath));
dataProtectionBuilder.SetApplicationName(string.IsNullOrWhiteSpace(options.DataProtectionApplicationName)
? "EasyAdminBlazor"
: options.DataProtectionApplicationName!);
}
不配置会出现什么:
应用池重启 / 重新部署
→ 默认密钥环位于程序池标识的用户配置文件目录,可能变化
→ 所有登录票据无法解密
→ 全体用户被强制重新登录
对内部后台可能只是“烦”,对客户生产环境就是事故。给密钥目录配好应用池账号的读写权限,并纳入备份。
五、目录权限
| 目录 | 权限 |
|---|---|
publish(站点根) | 应用池账号:读取 + 执行 |
publish\wwwroot\uploads | 应用池账号:修改/写入 |
D:\EasyAdminKeys | 应用池账号:修改/写入 |
publish\logs(若开启 stdout 日志) | 应用池账号:修改/写入 |
PowerShell 示例(把占位符换成实际池名与路径):
icacls "D:\sites\MyAdmin\publish\wwwroot\uploads" /grant "IIS AppPool\MyAdminPool:(OI)(CI)M" /T
icacls "D:\EasyAdminKeys" /grant "IIS AppPool\MyAdminPool:(OI)(CI)M" /T
上传目录不要给“执行”权限,也不要让 IIS 把它当作可执行目录。
六、HTTPS 与反向代理头
1. 绑定 HTTPS
IIS 管理器 → 站点 → 绑定 → 添加 → 类型 https → 选择证书 → 端口 443
证书可以用企业证书或 Let's Encrypt。建议再加一条 80 → 443 的重定向规则(URL Rewrite)。
2. Forwarded Headers
如果前面还有一层反向代理(Nginx、F5、云负载均衡),必须透传协议与主机头,否则会出现“HTTPS 站点跳转回 HTTP”。程序里已经调用:
app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.All });
代理侧要带:
X-Forwarded-Proto: https
X-Forwarded-For: 客户端 IP
Host: 原始域名
多租户场景特别注意:租户按 Host 识别(第 13 篇),代理必须透传原始 Host,否则所有请求会被当成同一个租户。
七、验证 WebSocket 是否真的通了
部署完别只看“页面能打开”,要确认长连接:
浏览器 F12 → Network → WS(WebSocket 过滤)
→ 应看到一条到 /_blazor 的连接,状态 101 Switching Protocols
如果看到反复建立/断开,或者只有轮询没有 WS,基本就是 IIS 没启用 WebSocket 功能。
八、日志
1. 框架内置的数据库日志
框架已注册 DatabaseLoggerProvider(第 27 篇),错误会自动进 SysLog 表,后台“错误日志”页面可查。生产排查优先看这里,里面有 TraceId / 请求路径 / 用户 / 租户。
2. stdout 日志(排查启动失败)
启动失败时页面只有 500,看不到原因。此时打开 ASP.NET Core Module 的 stdout 日志:
<aspNetCore processPath="dotnet"
arguments=".\YourApp.dll"
stdoutLogEnabled="true"
stdoutLogFile=".\logs\stdout"
hostingModel="inprocess">
New-Item -ItemType Directory -Force -Path "D:\sites\MyAdmin\publish\logs"
icacls "D:\sites\MyAdmin\publish\logs" /grant "IIS AppPool\MyAdminPool:(OI)(CI)M" /T
复现问题后记得关掉(stdoutLogEnabled="false")——它会持续写盘且不滚动。
3. Serilog(可选)
需要结构化文件日志时按 doc/Serilog日志配置.md 接入。建议分工:文件日志给运维排查,数据库日志给业务侧查看。
九、多实例部署
要多台 IIS(或 IIS + 其他宿主)同时提供服务时,有几项必须处理:
| 项 | 处理 |
|---|---|
| 雪花 ID | 每个实例配置不同的 WorkId(EasyAdminBlazorOptions.WorkId,默认 1),否则可能生成重复主键 |
| Data Protection 密钥 | 所有实例指向同一个 DataProtectionKeyPath 且 DataProtectionApplicationName 一致 |
| 缓存一致性 | 接入 Redis / FusionCache(第 19 篇),否则权限缓存各实例独立 |
| SignalR | 需要粘性会话(ARR Affinity)或 Redis backplane,否则推送可能落到没有连接的实例 |
| 文件上传 | uploads 目录要共享(NAS/共享盘),否则 A 实例上传、B 实例看不到 |
| 定时任务 | 任务定义在数据库里共享;任务体建议加幂等保护(第 21 篇) |
| 数据库 | 连接池与最大连接数按实例数放大评估 |
单实例部署可以忽略这一节。但只要上第二台,WorkId 与 Data Protection 这两项必须动。
十、Windows 部署为服务(IIS 反代 + Kestrel)
1. 为什么 Windows 下要部署为服务
如果你需要在线升级能力,Windows 下必须使用“Windows 服务”形态。原因在于:
在线升级要靠外部进程停掉应用、替换文件、再把它拉起来。IIS 进程内托管时应用跑在 w3wp.exe(应用程序池)里:
- 应用池回收/停止会连带杀掉它启动的子进程(升级程序),升级程序也没有权限把整个应用池停下来再启动;
- 而且一个应用池可能承载多个站点,停/起粒度对不上。
因此框架检测到 IIS 进程内托管时会直接禁用升级入口(页脚不显示「检查更新」、不注册 /health)。
所以 Windows 上要让应用以独立进程运行:注册成 Windows 服务(Kestrel 监听本地端口),IIS 只做反向代理;这样升级程序才能 sc stop/start 服务名 停掉并重新拉起应用。
控制台运行(
RestartMode: Direct)技术上也能升级,但不适合生产(窗口关掉就没人管了)。
2. 安装升级扩展
方式 A:NuGet(推荐)
<ItemGroup>
<PackageReference Include="EasyAdminBlazor.Upgrade" Version="2.4.0-preview.4" />
</ItemGroup>
一行搞定:扩展本体、升级引擎、发布钩子都在包里,不用写 Import,也不用把任何项目加进解决方案。
方式 B:源码引用(跟随框架源码开发)
<ItemGroup>
<ProjectReference Include="..\..\yyq\EasyAdminBlazor\Extensions\EasyAdminBlazor.Upgrade\EasyAdminBlazor.Upgrade.Extension.csproj" />
</ItemGroup>
<!-- 放在 </Project> 之前 -->
<Import Project="..\..\yyq\EasyAdminBlazor\Extensions\EasyAdminBlazor.Upgrade\build\EasyAdminBlazor.Upgrade.targets" />
源码引用还需要把下面三个项目加进解决方案,否则 VS 会报“找不到项目信息”(命令行 dotnet build/publish 不受影响,会自动连带还原):
Extensions/EasyAdminBlazor.Upgrade/EasyAdminBlazor.Upgrade.Extension.csproj 扩展
Extensions/EasyAdminBlazor.Upgrade/Engine/EasyAdminBlazor.Upgrade.csproj 升级引擎
Extensions/EasyAdminBlazor.Upgrade/Updater/EasyAdminBlazor.Updater.csproj 升级程序 + 打包器
两种方式效果一样:发布时自动把升级程序放进 updater\,Release 配置下自动生成升级包。
3. 启用升级
Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Windows 服务形态需要;控制台 / 宝塔 可以不加
builder.Host.UseWindowsService();
builder.AddEasyAdminBlazor(new EasyAdminBlazorOptions
{
// …你现有的配置不动…
})
// …你现有的扩展链…
.AddEasyAdminBlazorUpgrade(); // 启用升级(底部「检查更新」+ /health)
/health 由扩展自己挂载,宿主不需要写 app.MapGet("/health")。宿主原有的 app.UseEasyAdminBlazor(); 保留(它映射消息通知的 SignalR Hub)。
appsettings.json(Windows 服务):
{
"Urls": "http://0.0.0.0:5099",
"Upgrade": {
"Enabled": true,
"Source": "Local",
"ReleasesPath": "updates",
"RestartMode": "WindowsService",
"ServiceName": "EasyAdminBlazor",
"HealthCheckUrl": "http://127.0.0.1:5099"
}
}
HealthCheckUrl 必须是本机能访问到的地址,端口与站点实际监听端口一致。
想让客户从发布站点点两下升级:Source 改成 Url,ReleaseIndexUrl 填 https://你的发布地址/releases/index.json(默认要求 HTTPS,内网 http 需加 "AllowInsecureHttp": true)。
4. 注册为 Windows 服务
管理员命令行,二选一:
方式 A:系统自带 sc
sc.exe create EasyAdminBlazor binPath= "D:\app\EasyAdminBlazor\你的应用.exe" start= auto
sc.exe start EasyAdminBlazor
方式 B:NSSM
nssm install EasyAdminBlazor "D:\app\EasyAdminBlazor\你的应用.exe"
nssm set EasyAdminBlazor AppDirectory "D:\app\EasyAdminBlazor"
nssm set EasyAdminBlazor Start SERVICE_AUTO_START
nssm start EasyAdminBlazor
验证:
curl http://127.0.0.1:5099/health
应返回:
{
"status": "Healthy",
"applicationVersion": "1.0.0",
"frameworkVersion": "2.4.0-preview.4"
}
升级程序会执行:sc stop 服务名 → 等进程退出 → 备份 → 替换 → sc start 服务名 → 检查 /health 版本号。前提是服务账号有控制该服务的权限(sc create 默认的 LocalSystem 即可;受限账号需授予“启动/停止该服务”权限,并保证应用目录可写)。
5. IIS 反向代理配置
安装 ARR:IIS 管理器 → 服务器节点 → Application Request Routing Cache → Server Proxy Settings → 勾选 Enable proxy。
创建网站:IIS 管理器 → 网站 → 添加网站,物理路径指向一个空目录(用于放 web.config),应用程序池选 “无托管代码”。
在网站根目录放 web.config(注意把 5099 改成你实际监听的端口):
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="ReverseProxyToKestrel" stopProcessing="true">
<match url="(.*)" />
<action type="Rewrite" url="http://127.0.0.1:5099/{R:1}" />
</rule>
</rules>
</rewrite>
<proxy preserveHostHeader="true" />
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="104857600" />
</requestFiltering>
</security>
</system.webServer>
<system.web>
<httpRuntime maxRequestLength="102400" executionTimeout="600" />
</system.web>
</configuration>
说明:
preserveHostHeader="true"保留原始 Host 头,否则 Blazor 的 URL 推导会出错;maxAllowedContentLength放宽到 100MB,覆盖文件上传场景;- 应用程序池必须是无托管代码模式,因为应用是 Kestrel 自托管的。
6. 应用池身份权限
IIS 应用池以 ApplicationPoolIdentity 运行,它需要能访问后端 Kestrel 监听的端口(127.0.0.1:5099)。因为是本机回环,通常无需额外配置。但如果站点报 502,检查 IIS 是否能访问该端口:
curl http://127.0.0.1:5099/health
如果这条命令在服务器上能通,IIS 的反代就没有问题。
采用“Windows 服务 + IIS 反代”架构后,本文第三节中关于“关闭应用池定期回收、空闲超时设 0”的建议可以放宽——因为长连接直接由 Kestrel 服务承载,IIS 只是转发,应用池回收不会再打断 Blazor 连接。但 Windows 服务本身仍要保持稳定,不要用任何会周期性重启服务的工具去“守护”它。
十一、Linux 宝塔部署(已实测可用,无需注册服务)
不需要注册 systemd 服务,也不要用宝塔的「进程守护」——直接用宝塔自带的 .NET 网站功能发布即可,升级程序会自己停/起进程。
1. 发布与上传
# 本机发布
dotnet publish -c Release -o publish
把 publish/ 整个传到 /www/wwwroot/your-app。
2. 宝塔建站
宝塔 → 网站 → 添加站点 → 选择 .NET 项目 / .NET 网站:
- 项目目录:
/www/wwwroot/your-app - 启动文件:
你的应用.dll - 端口:按宝塔给的(例如 5018)
- 站点启动参数是
--urls http://localhost:5018
3. appsettings.json(Linux 宝塔)
{
"Urls": "http://127.0.0.1:5018",
"Upgrade": {
"Enabled": true,
"Source": "Local",
"ReleasesPath": "updates",
"RestartMode": "Direct",
"HealthCheckUrl": "http://127.0.0.1:5018"
}
}
端口与宝塔站点实际监听端口一致。
4. 权限要点
应用目录要可写(升级要写 .upgrade/ 和程序文件),属主建议给 www:
chown -R www:www /www/wwwroot/your-app
5. 升级操作
把新版本的 updates/v1.1.0/ 和 updates/index.json 传到 <应用目录>/updates/,后台点「检查更新 → 立即升级」。
十二、在线升级的工作方式
1. 出包
只改一处:宿主 csproj 的 <Version>。
dotnet publish -c Release -o publish
VS 右键发布(Release 配置)同样有效。发布完成后:
publish\
├── 程序文件 / wwwroot / updater\ ← updater\ 自动生成,别手工删
└── updates\
├── index.json
├── v1.0.0\ update.zip + release.json + files.json ← 上一版(基线)
└── v1.1.0\ update.zip + release.json + files.json ← 本次增量包(约 1~2 MB)
用同一个发布目录连续发布,上一版基线才在,出的是增量包。想写更新说明,在项目根目录放 release-notes.txt,每行一条(# 开头是注释)。
2. 升级流程
管理员点「检查更新 → 立即升级」,系统自动:
停服务 → 备份 → 替换文件 → 启动 → 健康检查 → 失败自动回滚
升级期间服务停几十秒,页面自动重连,用户无需重新登录。
升级包是逐版本增量(1.0 → 1.1 → 1.2 逐版升),落后多个版本会自动依次走完每一步。
3. 把新版本发到服务器
服务器不需要重新部署程序,只把一个目录拷过去:
服务器 <应用目录>/updates/ ← 放入新版本目录(如 v1.1.0/)
然后管理员登录后台 →「检查更新」→「立即升级」,等页面自动刷新即可。
首次部署新服务器:把整个 publish/ 拷过去(已带 updates/v1.0.0/ 基线),之后每版只传增量目录(实测如果嫌基线版本占空间不放也行)。
4. 不引用扩展的后果
升级是扩展:不引用 EasyAdminBlazor.Upgrade,后台就没有「检查更新」入口、不注册 /health、发布时也不生成升级包。老项目行为完全不变。
十三、部署方式选择对照表
| 场景 | 部署方式 | 在线升级 | 关键配置 |
|---|---|---|---|
| Windows + IIS 进程内 | 传统 IIS 托管 | ❌ 不可用 | 本文第二~八节配置,RestartMode 不生效 |
| Windows + IIS 反代 + Kestrel 服务 | Windows 服务 | ✅ 可用 | UseWindowsService() + RestartMode: "WindowsService" |
| Linux + 宝塔 .NET 网站 | 宝塔托管 | ✅ 可用 | RestartMode: "Direct" |
| Linux + Nginx 反代 | systemd 或裸进程 | 视 RestartMode 而定 | 参考第十四节 |
本文第二~八节的 IIS 部署部分仍然适用于“不使用在线升级”的场景;如果需要在线升级能力,Windows 端必须切换为“IIS 反代 + Windows 服务”的架构。
十四、补充:Nginx 反代的差异点
如果确实用 Linux + Nginx,与 IIS 的差异主要在:
location / {
proxy_pass http://127.0.0.1:5207;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; # Blazor Server 必须
proxy_set_header Connection "upgrade"; # Blazor Server 必须
proxy_set_header Host $host; # 多租户按 Host 识别,必须保留
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s; # 长连接,避免空闲断开
}
关键点与 IIS 一一对应:WebSocket 升级头、原始 Host、长超时。doc/部署上线.md 里有这份配置,可以直接取用。
十五、常见错误与排查
| 现象 | 常见原因 | 处理 |
|---|---|---|
| HTTP 500.30 / 500.31 / 500.32 | 应用启动失败 | 打开 stdout 日志看真实异常;确认运行时与 Hosting Bundle 版本 |
| HTTP 502.5 | 进程启动失败 | 同上;确认 web.config 的 processPath / arguments |
| 页面反复重连 | 未启用 WebSocket 协议 | 安装 Web-WebSockets 并 iisreset,重启应用池 |
| 长连接一会儿就断 | 应用池定期回收 / 空闲超时 | 关闭定期回收,空闲超时设为 0;或改用 Windows 服务形态 |
| 全体用户被强制重新登录 | Data Protection 密钥丢失 | 配置 DataProtectionKeyPath(固定路径 + 可写 + 备份) |
| 登录后跳转变成 http | 代理没透传 X-Forwarded-Proto | 补代理头;确认 UseForwardedHeaders 生效 |
| 后台 404 | AdminRouteSecret 改了 | 访问 /admin/{新安全码} |
| 上传失败 | uploads 无写权限 | 给应用池账号写权限 |
| 多租户站点互相串 | 代理没透传 Host | 反向代理保留原始 Host(IIS 需 preserveHostHeader="true") |
| 多实例主键冲突 | WorkId 相同 | 每实例配置不同 WorkId |
| 页脚没有「检查更新」 | IIS 进程内托管自动禁用升级 | 改用 Windows 服务 + IIS 反代 |
/health 返回 404 | 未引用升级扩展 | 添加 EasyAdminBlazor.Upgrade 包并调用 .AddEasyAdminBlazorUpgrade() |
升级后 /health 版本号没变 | 升级程序替换失败或回滚 | 查看 .upgrade/ 目录日志;确认应用目录可写、服务名正确 |
| 宝塔站点报 502 | 端口不一致 / 目录不可写 | 核对 HealthCheckUrl 与宝塔端口;chown -R www:www |
十六、上线检查清单
通用(IIS / Windows 服务 / 宝塔)
- 已安装 .NET 10 Runtime 与 ASP.NET Core Hosting Bundle(IIS 需要)
-
ASPNETCORE_ENVIRONMENT=Production - 已配置
DataProtectionKeyPath(固定路径、可写、纳入备份)与DataProtectionApplicationName - 已修改
AdminRouteSecret与AesKey - 默认管理员密码已改(首次登录强制改密流程可用)
-
UseAutoSyncStructure生产已关闭(或在测试库验证过) - 连接串走环境变量,未写入仓库
-
wwwroot/uploads与密钥目录已给运行账号写权限 - HTTPS 绑定完成,80 → 443 已跳转
- 反向代理(如有)透传
X-Forwarded-Proto与Host - F12 Network 能看到
/_blazor的 WebSocket 101 - Redis 已设密码、端口未暴露公网(如使用)
- 数据库已备份,备份任务已设置
- 多实例部署时
WorkId各不相同、密钥共享、缓存接入 Redis - 准备
app_offline.htm以便快速灰度/回滚
IIS 进程内专用
- 已启用 IIS WebSocket 协议功能
- 应用池:无托管代码、AlwaysRunning、关闭定期回收
Windows 服务 + 在线升级专用
- 已添加
EasyAdminBlazor.Upgrade包并调用.AddEasyAdminBlazorUpgrade() -
Program.cs已加builder.Host.UseWindowsService() - 服务已注册(
sc.exe或 NSSM),并能正常start/stop -
RestartMode: "WindowsService"、ServiceName与实际服务名一致 -
HealthCheckUrl指向本机监听端口,curl /health能通 - IIS 已装 ARR 并启用 proxy,
web.config中preserveHostHeader="true" -
updater\与updates\目录未被手工删除
宝塔 + 在线升级专用
- 站点选择 .NET 项目,启动文件与端口配置正确
-
RestartMode: "Direct"、HealthCheckUrl与宝塔端口一致 - 应用目录属主为
www且可写(升级需要写.upgrade/与程序文件) - 未使用宝塔「进程守护」等会与升级停/起冲突的功能
灰度与回滚
ASP.NET Core 支持“离线文件”机制:在站点根目录放一个 app_offline.htm,ANCM 会停止应用并把该页面返回给所有请求;删掉文件后应用重新启动。
发布流程:放 app_offline.htm → 覆盖 publish → 删除 app_offline.htm → 访问验证
这比“直接覆盖正在运行的文件”安全得多。
采用 Windows 服务形态时,更推荐直接用在线升级的“备份 + 回滚”机制;
app_offline.htm主要适用于纯 IIS 托管形态或首次部署。
十七、小结
EasyAdminBlazor 的部署成败,取决于“环境 + 进程形态 + 密钥 + 权限”四件事:
- 环境:Hosting Bundle + WebSocket 功能(漏一个就是 500 或反复重连);
- 进程形态:
- 不需要在线升级 → IIS 进程内托管即可,重点管好应用池不随意回收;
- 需要在线升级 → Windows 必须用 Windows 服务 + IIS 反代,Linux 用 宝塔 .NET 网站,让升级程序能停/起进程;
- 密钥:
DataProtectionKeyPath固定且可写(决定用户会不会集体掉线,升级后用户“无需重新登录”也靠它); - 权限与协议:
uploads可写、HTTPS 正常、代理头透传原始 Host。
这四项做完,剩下的就是常规的数据库、日志、备份与监控。EasyAdminBlazor 已经把与框架相关的部分(密钥持久化、雪花 WorkId、上传目录、后台路由、在线升级)做成了配置项或扩展包。