Aspire 项目实战:BoardApp 传统 .NET LOB 应用的架构解析与手动部署指南
【免费下载链接】aspireAspire is the tool for code-first, extensible, observable dev and deploy.项目地址: https://gitcode.com/GitHub_Trending/as/aspire
导读
本文基于 Aspire 开源仓库中的示例应用BoardApp(dotnet-traditional),完整讲解一个典型的"传统 .NET 业务线(LOB)应用"应如何搭建与手动运行:它由 Vue 3 前端、ASP.NET 最小 API、Blazor Server 管理后台、EF Core 迁移 Worker 与共享数据层组成,并依赖 PostgreSQL 与 Redis。读完本文,你将掌握该应用的整体架构、.env配置体系、5 个关键端口的启动流程与验证方法,并理解它作为aspireify技能评测"前状态"(pre-aspirification)样本的设计意图,为后续用 Aspire 编排同类应用打下对照基础。
应用定位:Aspirify 评测的"前状态"样本
BoardApp 位于仓库的 playground/aspireify-eval/dotnet-traditional/ 目录,是 Aspire 项目中专门用于评测aspireify技能的pre-aspirification(Aspirify 前)示例应用。它刻意不接入 Aspire,代表真实世界 LOB 应用迁移前的形态:
- 它拥有一个含多个 .NET 项目与 Vue 前端的完整解决方案;
- 所有配置散落在根目录
.env中,各服务通过环境变量自行读取; - 启动依赖人工维护多个终端窗口、手工导出环境变量、按顺序启动基础设施与各服务。
根据父目录 playground/aspireify-eval/README.md 的说明,评测流程是:进入该应用目录 → 执行aspire init→ 让 Agent 使用aspireify技能将其完整 Aspirify(接入 Aspire 编排),并依据 EVAL-RUBRIC.md 打分。因此,本文描述的手动运行方式,正是评测中所指的"before"状态——理解它,才能对比出 Aspire 编排带来的差异。
架构总览
BoardApp 由 5 个组成部分构成,各部分职责清晰:
frontend/ → Vue 3 + Vite (port 5173),将 /api/* 代理到 BoardApi src/BoardApi/ → ASP.NET 最小 API (port 5220),EF Core + Postgres,Redis 缓存 src/AdminDashboard/→ Blazor Server (port 5230),与 BoardApi 共享同一数据库 src/MigrationRunner/→ Worker 服务,运行 EF Core 迁移后自动退出 src/BoardData/ → 类库,共享 EF Core DbContext 与模型 BoardApp.slnx → 将上述项目串联起来的解决方案文件 .env → 全部配置:DB 连接、Redis、API 密钥、机密从实际源码可以进一步验证这一结构:
- 解决方案文件 BoardApp.slnx 使用新的 slnx 格式,显式引用了
BoardApi、AdminDashboard、MigrationRunner、BoardData四个 .csproj; - 共享数据层 src/BoardData/Models.cs 定义了
BoardDbContext(含BoardItems与UserProfiles两个DbSet)以及BoardItem、UserProfile两个实体模型; - 前端 frontend/vite.config.ts 将
/api前缀的请求代理到process.env.API_URL(默认http://localhost:5220)。
数据流与协作关系
从源码调用关系可以推断出应用内部的数据协作方式:
- MigrationRunner最先启动,通过
BoardDbContext执行EnsureCreatedAsync()建表,并在空库时写入种子数据(3 条BoardItem和 1 条UserProfile); - BoardApi暴露 REST 端点,读写同一
BoardDbContext,并通过StackExchange.Redis的IConnectionMultiplexer访问 Redis; - AdminDashboard作为管理端,同样直连数据库统计
BoardItems与UserProfiles数量; - 前端在开发期通过 Vite 代理把
/api/*请求转发给 BoardApi,生产期则依赖同源部署。
依赖环境
运行 BoardApp 需要以下基础组件(均为本地开发环境要求):
| 依赖 | 版本/端口要求 | 用途 |
|---|---|---|
| .NET SDK | .NET 10(各 csproj 的 TargetFramework 为net10.0) | 编译运行 4 个 .NET 项目 |
| Node.js | 20+ | 运行 Vite/Vue 前端 |
| PostgreSQL | localhost:5432 | 主数据库 |
| Redis | localhost:6379 | 缓存(/api/cached-count端点) |
各 .NET 项目均以net10.0为目标框架(参见 BoardApi.csproj),依赖Microsoft.EntityFrameworkCore、Npgsql.EntityFrameworkCore.PostgreSQL、StackExchange.Redis等包;前端依赖vue@^3.5.0、vite@^6.3.0与@vitejs/plugin-vue(参见 frontend/package.json)。
配置体系:.env 与关键变量
BoardApp 的全部配置集中在仓库根目录的.env文件中,各 .NET 服务通过Environment.GetEnvironmentVariable()读取:
| 变量 | 用途 | 是否机密 |
|---|---|---|
DATABASE_URL | Postgres 连接字符串 | 是(含密码) |
REDIS_URL | Redis host:port | 否 |
EXTERNAL_API_KEY | 第三方通知服务密钥 | 是 |
ADMIN_SECRET | 管理后台认证令牌 | 是 |
FEATURE_ENABLE_NOTIFICATIONS | 功能开关 | 否 |
从源码可以确认这些变量的实际消费方式:
- BoardApi(src/BoardApi/Program.cs):读取
DATABASE_URL配置 EF Core 的 Npgsql 提供程序;读取REDIS_URL建立ConnectionMultiplexer;读取EXTERNAL_API_KEY,缺失时直接throw new InvalidOperationException("EXTERNAL_API_KEY must be set"); - AdminDashboard(src/AdminDashboard/Program.cs):读取
DATABASE_URL与ADMIN_SECRET,后者缺失同样抛异常; - MigrationRunner(src/MigrationRunner/Program.cs):读取
DATABASE_URL; - 前端:在
vite.config.ts中读取API_URL作为开发代理目标。
值得注意的一个细节是:各服务在环境变量缺失时都提供了指向本地默认值的回退(例如 BoardApi 与 MigrationRunner 默认连接Host=localhost;Port=5432;Database=boardapp;Username=postgres;Password=localdev123),但机密型变量(EXTERNAL_API_KEY、ADMIN_SECRET)则是硬性要求,这体现了"本地可快速跑通、生产强制注入机密"的常见工程模式。
手动运行指南(未接入 Aspire)
由于没有 Aspire 编排,运行整个应用需要4 个终端窗口与2 个基础设施服务。下面按官方步骤逐步执行。
1. 启动基础设施
在终端 1 中依次启动 PostgreSQL 与 Redis 容器:
# 启动 Postgres(或复用已有实例) docker run -d --name boardapp-pg \ -e POSTGRES_PASSWORD=localdev123 \ -e POSTGRES_DB=boardapp \ -p 5432:5432 \ postgres:16 # 启动 Redis docker run -d --name boardapp-redis \ -p 6379:6379 \ redis:7注意:Postgres 容器的密码localdev123与库名boardapp必须与代码中的默认连接字符串、.env中DATABASE_URL保持一致,否则服务启动后无法连通数据库。
2. 加载环境变量
在每个运行 .NET 服务的终端中先导出.env中的变量:
export $(cat .env | xargs)该命令把.env每行的KEY=value逐条导出为进程环境变量;由于后续步骤会启动多个服务,因此每个终端都要单独执行一次。请确保在执行该命令前,终端工作目录位于存放.env的仓库根目录。
3. 运行数据库迁移
在终端 2 中运行迁移服务,它会在建表与种子数据写入完成后自动退出:
cd src/MigrationRunner dotnet run # 等待输出 "Migrations complete." 后该进程退出从 MigrationRunner/Program.cs 可以看到它执行的是db.Database.EnsureCreatedAsync()(而非传统MigrateAsync()),随后在BoardItems为空时写入 3 条种子任务与 1 个管理员UserProfile,最后打印Migrations complete.。这一步必须最先完成,否则 BoardApi 与 AdminDashboard 查询时表还不存在。
4. 启动 API
迁移完成后,复用终端 2(或新开终端并重新export环境变量):
cd src/BoardApi dotnet run --urls http://localhost:5220BoardApi 暴露的端点(来自 src/BoardApi/Program.cs)包括:
| 端点 | 方法 | 行为 |
|---|---|---|
/api/health | GET | 返回{"status":"healthy"} |
/api/items | GET | 按CreatedAt倒序返回全部 BoardItem |
/api/items | POST | 新增 BoardItem,返回 201 Created |
/api/items/{id} | GET | 按 Id 查询,未找到返回 404 |
/api/cached-count | GET | 从 Redis 读取item-count字符串,验证 Redis 连通性 |
/api/notify | POST | 桩实现,模拟调用第三方通知服务(使用EXTERNAL_API_KEY前缀) |
5. 启动管理后台
在终端 3 中:
cd src/AdminDashboard dotnet run --urls http://localhost:5230AdminDashboard 是 Blazor Server 应用(src/AdminDashboard/Program.cs),额外暴露/admin/health(返回带role="admin"的健康信息)与/admin/stats(统计 items/users 数量)两个最小 API 端点,并注册了MapBlazorHub()与_Host回退页面。
6. 启动前端
在终端 4 中:
cd frontend npm install npm run dev # 打开 http://localhost:5173Vite 开发服务器监听 5173 端口,并把所有/api/*请求代理到 BoardApi(可通过API_URL环境变量覆盖代理目标,参见 vite.config.ts)。
验证应用是否正常运行
全部服务启动后,可通过以下 URL 逐项验证(对应 README 中的验证清单):
- 前端:http://localhost:5173 —— 应显示 "BoardApp" 及一列种子数据条目;
- API 健康检查:http://localhost:5220/api/health —— 应返回
{"status":"healthy"}; - API 条目列表:http://localhost:5220/api/items —— 应返回种子 BoardItem 列表;
- 管理统计:http://localhost:5230/admin/stats —— 应返回 item/user 数量;
- Redis 缓存验证:http://localhost:5220/api/cached-count —— 测试 Redis 连通性(首次访问返回
{"count":"not cached"},若已写入则返回缓存值)。
任何一项不符合预期,可按依赖顺序排查:先确认 Postgres/Redis 容器状态,再确认对应终端已执行export $(cat .env | xargs),最后核对端口占用(5220/5230/5173)与DATABASE_URL中的密码是否与容器一致。
从"Before"到"After":Aspirify 的评测意义
本文所述的整套手动流程(4 个终端 + 2 个基础设施 + 顺序启动 + 手工导环境变量)正是aspireify技能评测要消除的痛点。作为对照基准,这个 dotnet-traditional 应用刻意保持"未 Aspirify"状态,配套的评测体系位于 playground/aspireify-eval/EVAL-RUBRIC.md,评测人员在 polyglot/ 等样本上同样执行aspire init+aspireify技能流程后打分。
对读者而言,可以这样利用本仓库进行实践验证:先在本地按本文步骤把 BoardApp 手动跑通,再尝试在playground/aspireify-eval/dotnet-traditional目录执行aspire init,观察 Agent 如何把.env配置映射为 Aspire 资源、把 4 个 .NET 项目与前端纳管为分布式应用、把 4 个手工终端窗口收敛为一次dotnet run——前后两种运行方式的差异,就是 Aspire 编排价值最直观的体现。
【免费下载链接】aspireAspire is the tool for code-first, extensible, observable dev and deploy.项目地址: https://gitcode.com/GitHub_Trending/as/aspire
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考