EasyAdminBlazor 生产部署完整指南:IIS、Windows 服务与宝塔

3 阅读7分钟

本文对应版本: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 生效
后台 404AdminRouteSecret 改了访问 /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 的部署成败,取决于“环境 + 进程形态 + 密钥 + 权限”四件事:

  1. 环境:Hosting Bundle + WebSocket 功能(漏一个就是 500 或反复重连);
  2. 进程形态:
    • 不需要在线升级 → IIS 进程内托管即可,重点管好应用池不随意回收;
    • 需要在线升级 → Windows 必须用 Windows 服务 + IIS 反代,Linux 用 宝塔 .NET 网站,让升级程序能停/起进程;
  3. 密钥:DataProtectionKeyPath 固定且可写(决定用户会不会集体掉线,升级后用户“无需重新登录”也靠它);
  4. 权限与协议:uploads 可写、HTTPS 正常、代理头透传原始 Host。

这四项做完,剩下的就是常规的数据库、日志、备份与监控。EasyAdminBlazor 已经把与框架相关的部分(密钥持久化、雪花 WorkId、上传目录、后台路由、在线升级)做成了配置项或扩展包。