Clean Architecture 解决方案中的 Aspire Host:用 .NET Aspire 一键编排 SQL Server、SMTP 与 Web 应用的完整指南
【免费下载链接】CleanArchitectureClean Architecture Solution Template: A proven Clean Architecture Template for ASP.NET Core 10项目地址: https://gitcode.com/GitHub_Trending/cl/CleanArchitecture
本文基于 Clean.Architecture.AspireHost 的 README 展开,讲解如何用 .NET Aspire 作为启动项目,一条命令拉起 SQL Server 容器、Papercut SMTP 邮件测试容器和 Web 应用,并自动完成连接字符串注入、数据库等待与迁移;读完后可掌握 Aspire 资源图(Resource Graph)的编写方式、ContainerLifetime.Persistent持久化配置,以及绕过 Aspire 直接运行 Web 项目时的数据库回退机制。
1. Aspire Host 的定位与项目构成
在 Clean.Architecture.slnx 解决方案中,Clean.Architecture.AspireHost 是一个独立的 Aspire App Host 可执行项目,负责编排应用及其所有依赖(数据库、邮件服务器)。其项目文件的关键配置如下(Clean.Architecture.AspireHost.csproj):
<Project Sdk="Aspire.AppHost.Sdk/13.3.5"> <PropertyGroup> <OutputType>Exe</OutputType> <IsAspireHost>true</IsAspireHost> </PropertyGroup> <ItemGroup> <PackageReference Include="Aspire.Hosting.SqlServer" /> <PackageReference Include="MessagePack" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\Clean.Architecture.Web\Clean.Architecture.Web.csproj" /> </ItemGroup> <ItemGroup> <!-- The IsAspireProjectResource attribute tells .NET Aspire to treat this reference as a standard project reference and not attempt to generate a metadata file --> <ProjectReference Include="..\Clean.Architecture.ServiceDefaults\Clean.Architecture.ServiceDefaults.csproj" IsAspireProjectResource="false" /> </ItemGroup> </Project>几个要点:
IsAspireHost=true加上Aspire.AppHost.Sdk使其成为 Aspire 编排入口,编译产物为Exe;- 通过
Aspire.Hosting.SqlServer包获得 SQL Server 容器的类型化扩展(AddSqlServer); - 对 Clean.Architecture.Web 使用项目引用(Aspire 会为其生成
Projects.Clean_Architecture_Web访问器类型); - 对 ServiceDefaults 项目标记
IsAspireProjectResource="false",按源码注释说明,这是告诉 Aspire 以普通项目引用处理它,不生成资源元数据文件。
仓库根目录的 aspire.config.json 则声明了 app host 的路径,让 Aspire 工具链能直接定位启动项目:
{ "appHost": { "path": "src/Clean.Architecture.AspireHost/Clean.Architecture.AspireHost.csproj" } }2. 资源图解析:AppHost.cs 编排了哪些资源
AppHost.cs 是整个编排的核心,全文如下:
using System.Net.Sockets; var builder = DistributedApplication.CreateBuilder(args); // Add SQL Server container var sqlServer = builder.AddSqlServer("sqlserver") .WithLifetime(ContainerLifetime.Persistent); // Add the database var cleanArchDb = sqlServer.AddDatabase("cleanarchitecture"); // Papercut SMTP container for email testing var papercut = builder.AddContainer("papercut", "jijiechen/papercut", "latest") .WithEndpoint("smtp", e => { e.TargetPort = 25; // container port e.Port = 25; // host port e.Protocol = ProtocolType.Tcp; e.UriScheme = "smtp"; }) .WithEndpoint("ui", e => { e.TargetPort = 37408; e.Port = 37408; e.UriScheme = "http"; }); // Add the web project with the database connection builder.AddProject<Projects.Clean_Architecture_Web>("web") .WithReference(cleanArchDb) .WithEnvironment("ASPNETCORE_ENVIRONMENT", builder.Environment.EnvironmentName) .WithEnvironment("Papercut__Smtp__Url", papercut.GetEndpoint("smtp")) .WaitFor(cleanArchDb) .WaitFor(papercut); builder .Build() .Run();从源码结构看,资源图包含三类资源及其依赖关系:
- SQL Server 容器(资源名
sqlserver):AddSqlServer("sqlserver")创建一个 SQL Server 容器资源;.WithLifetime(ContainerLifetime.Persistent)将生命周期设为持久化(详见第 6 节)。 - 逻辑数据库(资源名
cleanarchitecture):sqlServer.AddDatabase("cleanarchitecture")基于容器派生出一个数据库资源,这是 Web 项目通过WithReference引用的对象。 - Papercut SMTP 容器:使用
jijiechen/papercut镜像(Papercut 是带 Web 管理界面的 SMTP 邮件捕获/测试工具),暴露两个端点:smtp:容器端口 25 映射到宿主机 25,协议 TCP,URI 方案smtp,供应用发信;ui:端口 37408(Papercut 的 Web 管理界面),供人工查看收到的邮件。
- Web 项目(资源名
web):通过AddProject<Projects.Clean_Architecture_Web>("web")接入,并施加了四项编排配置:.WithReference(cleanArchDb):把cleanarchitecture数据库的连接信息注入 Web 进程(以ConnectionStrings__cleanarchitecture环境变量形式提供,即 README 中提到的"连接字符串由 Aspire 自动提供");.WithEnvironment("ASPNETCORE_ENVIRONMENT", builder.Environment.EnvironmentName):把宿主环境名透传给 Web,保证环境配置一致;.WithEnvironment("Papercut__Smtp__Url", papercut.GetEndpoint("smtp")):注入 Papercut 的 SMTP 端点,使 Web 的邮件发送配置(MailserverConfiguration 绑定的Mailserver配置节,默认localhost:25,见 appsettings.json)在 Aspire 编排下指向 Papercut 实例;.WaitFor(cleanArchDb)与.WaitFor(papercut):声明启动顺序依赖,Web 会等待数据库与 SMTP 就绪后再启动,避免应用抢跑导致的连接失败。
3. 运行应用:以 Aspire Host 为启动项目
README 给出的操作步骤与源码完全对应:
- 将
Clean.Architecture.AspireHost设为启动项目; - 运行应用(F5 调试或 Ctrl+F5 不调试);
- Aspire Dashboard 会自动打开,展示所有运行中的资源,包括 SQL Server 容器、Papercut 容器和 Web 应用;
- Web 应用自动连接到 SQL Server 容器中的
cleanarchitecture数据库。
launchSettings.json 中预置了https与http两个启动配置文件。以默认的https配置为例:
{ "profiles": { "https": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "applicationUrl": "https://localhost:17143;http://localhost:15258", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "DOTNET_ENVIRONMENT": "Development", "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21007", "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22245" } }, "http": { "commandName": "Project", "applicationUrl": "http://localhost:15258", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "DOTNET_ENVIRONMENT": "Development", "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19187", "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20134" } } } }applicationUrl:Aspire Host 自身暴露的端点(资源服务监听地址);DOTNET_DASHBOARD_OTLP_ENDPOINT_URL与DOTNET_RESOURCE_SERVICE_ENDPOINT_URL:Aspire Dashboard 的遥测与资源服务端口,由 SDK 自动写入。
appsettings.json 仅配置了日志级别,并特意压低Aspire.Hosting.Dcp(Dashboard Control Plane)的日志到Warning,减少启动期噪音:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning", "Aspire.Hosting.Dcp": "Warning" } } }另外,仓库中的 AspireIntegrationTests 目前是占位测试工程,为后续按 Aspire 官方测试指南补充宿主级集成测试预留了位置;针对该编排链路的验证仍以功能/集成测试工程(tests/Clean.Architecture.FunctionalTests 等)为主。
4. 连接字符串的注入与三级回退机制
README 的关键论断是:通过 Aspire 运行时,连接字符串由 Aspire 自动提供,会覆盖 appsettings.json 中的DefaultConnection;连接名为 "cleanarchitecture",并被 Web 项目引用。
这句话在源码中有明确的印证链。Web 项目的 appsettings.json 中定义了两条本地连接串:
{ "ConnectionStrings": { // When running through Aspire, the connection string is provided automatically by the AspireHost // This DefaultConnection is used only when running the Web project directly on Windows (without Aspire) // You can configure this to point to your local SQL Server instance // Note: On Linux/macOS, this connection is skipped and SQLite is used instead "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=cleanarchitecture;Trusted_Connection=True;MultipleActiveResultSets=true", // SQLite is used as a fallback when running on Linux/macOS or when running standalone on Windows without Aspire "SqliteConnection": "Data Source=database.sqlite" }, ... }而真正消费这些连接串的是 InfrastructureServiceExtensions.cs 中的注册逻辑:
// Try to get connection strings in order of priority: // 1. "cleanarchitecture" - provided by Aspire when using .WithReference(cleanArchDb) // 2. "DefaultConnection" - SQL Server (Windows only by default, can be forced with USE_SQL_SERVER=true) // 3. "SqliteConnection" - fallback to SQLite bool isWindows = OperatingSystem.IsWindows(); bool forceSqlServer = Environment.GetEnvironmentVariable("USE_SQL_SERVER") == "true"; string? connectionString = config.GetConnectionString("cleanarchitecture") ?? ((isWindows || forceSqlServer) ? config.GetConnectionString("DefaultConnection") : null) ?? config.GetConnectionString("SqliteConnection"); Guard.Against.Null(connectionString);可以归纳出一条三级优先级的选择链:
| 优先级 | 连接串名称 | 来源 | 生效条件 | 使用的 Provider |
|---|---|---|---|---|
| 1 | cleanarchitecture | Aspire 通过.WithReference(cleanArchDb)注入 | 以 Aspire Host 启动时恒存在 | UseSqlServer |
| 2 | DefaultConnection | appsettings.json(localDB 实例) | Windows 平台,或显式设置环境变量USE_SQL_SERVER=true | UseSqlServer |
| 3 | SqliteConnection | appsettings.json(database.sqlite文件) | 上述条件均不满足时(如 Linux/macOS 直接运行 Web) | UseSqlite |
即:只要走 Aspire 编排,注入的cleanarchitecture连接串必然排在第一位,"覆盖 appsettings.json" 的效果由此实现;数据库 Provider 的选择同样由这条链决定(存在cleanarchitecture或满足条件的DefaultConnection时用 SQL Server,否则回退 SQLite)。
5. 不经过 Aspire 直接运行 Web 项目时的行为
README 说明:直接运行 Web 项目(不经过 AspireHost)时,会回退使用 appsettings.json 中的 SQLite 连接串。结合第 4 节的选择链,实际行为是:
- Linux / macOS上直接运行 Web:
cleanarchitecture不存在,且非 Windows 平台不会取DefaultConnection,因此最终使用SqliteConnection(Data Source=database.sqlite)。 - Windows上直接运行 Web:优先取
DefaultConnection(localDB 的 SQL Server 实例);若不存在该连接串(例如被移除),仍会继续回退到 SQLite。 - 在任意平台上设置环境变量
USE_SQL_SERVER=true,可以强制在非 Windows 环境走DefaultConnection的 SQL Server 路径——这是对容器化或 CI 场景的一个逃生舱。
数据库初始化行为由 MiddlewareConfig.cs 中的启动中间件控制:在 Development 环境或Database:ApplyMigrationsOnStartup为true时自动执行迁移并填充种子数据(SeedData)。其中对 SQLite 采用EnsureCreatedAsync()建库(本地开发常见做法),对 SQL Server 执行MigrateAsync()。因此 README 中"通过 Aspire 运行时,若数据库不存在会自动在 SQL Server 容器内创建"的说法,本质上是 Aspire 负责建库 + Web 启动时自动迁移二者配合的结果。
测试环境另有 appsettings.Testing.json,将SqliteConnection覆盖为:memory:,供功能测试使用内存库。
6. 创建迁移与更新数据库(EF Core CLI)
README 明确说明:现有迁移为 SQLite 创建,同样兼容 SQL Server。以下命令均在Web 项目目录下执行,因为启动项目(-s)必须是能构建出AppDbContext的 Web 项目,而上下文本身定义在 Infrastructure 项目(-p指定其 csproj):
创建新迁移:
dotnet ef migrations add MigrationName -c AppDbContext -p ../Clean.Architecture.Infrastructure/Clean.Architecture.Infrastructure.csproj -s Clean.Architecture.Web.csproj -o Data/Migrations更新数据库:
dotnet ef database update -c AppDbContext -p ../Clean.Architecture.Infrastructure/Clean.Architecture.Infrastructure.csproj -s Clean.Architecture.Web.csproj参数说明:
-c AppDbContext:目标上下文类型 AppDbContext;-p ../Clean.Architecture.Infrastructure/Clean.Architecture.Infrastructure.csproj:包含上下文的项目;-s Clean.Architecture.Web.csproj:启动项目(提供 DI 与配置来源);-o Data/Migrations:迁移输出目录,对应现有的 Data/Migrations 目录(含初始迁移及 UseDbGeneratedIds 等后续迁移)。
需要留意的一点:通过 Aspire 运行时,Web 进程中的连接串指向 SQL Server 容器,CLI 命令若在同目录执行、读取同样的 appsettings,其实际连接的数据库取决于当时环境中的连接串解析结果,建议在与目标一致的运行环境下执行迁移操作。
7. 容器持久化与数据库重置
README 最后一段指出:SQL Server 容器配置为ContainerLifetime.Persistent,数据会在应用多次运行之间保留。对应 AppHost.cs 中的:
var sqlServer = builder.AddSqlServer("sqlserver") .WithLifetime(ContainerLifetime.Persistent);Persistent生命周期意味着 Aspire 在应用停止后不会删除该容器,SQL Server 中的数据卷得以跨运行保留。对比之下,若使用默认的FixedInterval或Once生命周期,容器会随宿主进程退出而被清理。
重置数据库(丢弃持久化数据)的两种方式(摘自 README):
- 在 Aspire Dashboard 中删除该容器;
- 使用 Docker CLI:
docker rm <container-name>(容器名以 Dashboard 中显示的资源名为准)。
删除后再次启动 Aspire Host,容器会按镜像与持久化策略重新创建,Web 启动时的自动迁移会重新初始化数据库结构。
8. 小结:这套编排方案的可复制要点
从 src/Clean.Architecture.AspireHost 目录可以提炼出在 Clean Architecture 分层解决方案中引入 Aspire 的几个模式:
- 编排入口独立成项目:App Host 只依赖被编排项目(Web)与 ServiceDefaults,不承载业务代码;
- 用
AddDatabase派生逻辑数据库,再通过WithReference把连接字符串以ConnectionStrings__cleanarchitecture形式注入应用,让应用侧以"连接串名称"而非"具体连接串"编程; - 应用侧保留多级回退(Aspire 注入 → 本地 SQL Server → SQLite),使"走 Aspire"与"直接跑 Web"两种开发方式都能开箱即用,且互不冲突;
WaitFor显式声明启动依赖,配合 Web 启动时的自动迁移与种子数据,省去手工建库步骤;ContainerLifetime.Persistent+ Dashboard/Docker 重置流程,兼顾开发期数据连续性与可复现性;- Papercut SMTP 容器让领域事件的邮件通知链路(如 IEmailSender 的 SMTP 实现 MimeKitEmailSender)在本地也有可验证的闭环——发件可经
smtp端点,收件可经 37408 端点的 Web 界面查看。
【免费下载链接】CleanArchitectureClean Architecture Solution Template: A proven Clean Architecture Template for ASP.NET Core 10项目地址: https://gitcode.com/GitHub_Trending/cl/CleanArchitecture
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考