From e42fb7b590cd869751f2e6347459dd3fe9da1bea Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Tue, 10 Sep 2024 17:00:55 +1000 Subject: [PATCH 01/15] WaitFor: SQL Server --- .../SqlServerEndToEnd.AppHost/Program.cs | 11 ++++--- .../WaitForSandbox.AppHost.csproj | 1 + .../Aspire.Hosting.SqlServer.csproj | 4 +++ .../SqlServerBuilderExtensions.cs | 31 ++++++++++++++++++- 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs b/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs index d8b4244796..2f57e1669f 100644 --- a/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs @@ -3,13 +3,16 @@ var builder = DistributedApplication.CreateBuilder(args); -var db1 = builder.AddSqlServer("sql1").PublishAsAzureSqlDatabase().AddDatabase("db1"); -var db2 = builder.AddSqlServer("sql2").PublishAsContainer().AddDatabase("db2"); +var sql1 = builder.AddSqlServer("sql1").PublishAsAzureSqlDatabase(); +var db1 = sql1.AddDatabase("db1"); + +var sql2 = builder.AddSqlServer("sql2").PublishAsContainer(); +var db2 = sql2.AddDatabase("db2"); builder.AddProject("api") .WithExternalHttpEndpoints() - .WithReference(db1) - .WithReference(db2); + .WithReference(db1).WaitFor(sql1) + .WithReference(db2).WaitFor(sql2); #if !SKIP_DASHBOARD_REFERENCE // This project is only added in playground projects to support development/debugging diff --git a/playground/waitfor/WaitForSandbox.AppHost/WaitForSandbox.AppHost.csproj b/playground/waitfor/WaitForSandbox.AppHost/WaitForSandbox.AppHost.csproj index 7297c1b48c..d4faa83d62 100644 --- a/playground/waitfor/WaitForSandbox.AppHost/WaitForSandbox.AppHost.csproj +++ b/playground/waitfor/WaitForSandbox.AppHost/WaitForSandbox.AppHost.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Aspire.Hosting.SqlServer/Aspire.Hosting.SqlServer.csproj b/src/Aspire.Hosting.SqlServer/Aspire.Hosting.SqlServer.csproj index 883aed4995..7e591b062f 100644 --- a/src/Aspire.Hosting.SqlServer/Aspire.Hosting.SqlServer.csproj +++ b/src/Aspire.Hosting.SqlServer/Aspire.Hosting.SqlServer.csproj @@ -17,6 +17,10 @@ + + + + diff --git a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs index 41f454431b..47a73114f7 100644 --- a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs +++ b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs @@ -3,6 +3,7 @@ using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Utils; +using Microsoft.Extensions.DependencyInjection; namespace Aspire.Hosting; @@ -28,6 +29,33 @@ public static IResourceBuilder AddSqlServer(this IDistr var passwordParameter = password?.Resource ?? ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, $"{name}-password", minLower: 1, minUpper: 1, minNumeric: 1); var sqlServer = new SqlServerServerResource(name, passwordParameter); + + string? connectionString = null; + + builder.Eventing.Subscribe(sqlServer, async (@event, ct) => + { + connectionString = await sqlServer.GetConnectionStringAsync(ct).ConfigureAwait(false); + + var lookup = builder.Resources.OfType().ToDictionary(d => d.Name); + + foreach (var databaseName in sqlServer.Databases) + { + if (!lookup.TryGetValue(databaseName.Key, out var databaseResource)) + { + throw new DistributedApplicationException($"Database resource '{databaseName}' under SQL Server resource '{sqlServer.Name}' not in model."); + } + + var connectionStringAvailableEvent = new ConnectionStringAvailableEvent(databaseResource, @event.Services); + await builder.Eventing.PublishAsync(connectionStringAvailableEvent, ct).ConfigureAwait(false); + + var beforeResourceStartedEvent = new BeforeResourceStartedEvent(databaseResource, @event.Services); + await builder.Eventing.PublishAsync(beforeResourceStartedEvent, ct).ConfigureAwait(false); + } + }); + + var healthCheckKey = $"{name}_check"; + builder.Services.AddHealthChecks().AddSqlServer(sp => connectionString ?? throw new InvalidOperationException("Connection string is unavailable"), name: healthCheckKey); + return builder.AddResource(sqlServer) .WithEndpoint(port: port, targetPort: 1433, name: SqlServerServerResource.PrimaryEndpointName) .WithImage(SqlServerContainerImageTags.Image, SqlServerContainerImageTags.Tag) @@ -36,7 +64,8 @@ public static IResourceBuilder AddSqlServer(this IDistr .WithEnvironment(context => { context.EnvironmentVariables["MSSQL_SA_PASSWORD"] = sqlServer.PasswordParameter; - }); + }) + .WithHealthCheck(healthCheckKey); } /// From ae2eb9176415e0a3ef8053fcd1a21f010dcaf5c2 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Tue, 10 Sep 2024 19:27:44 +1000 Subject: [PATCH 02/15] Expanding playground to work with WaitFor. --- Aspire.sln | 14 +++ .../SqlServerEndToEnd.ApiService/Program.cs | 39 ++----- .../SqlServerEndToEnd.ApiService.csproj | 1 + .../SqlServerEndToEnd.AppHost/Program.cs | 9 +- .../SqlServerEndToEnd.AppHost.csproj | 3 + .../SqlServerEndToEnd.Common/Entry.cs | 9 ++ .../SqlServerEndToEnd.Common/MyDb1Context.cs | 11 ++ .../SqlServerEndToEnd.Common/MyDb2Context.cs | 18 +++ .../SqlServerEndToEnd.Common.csproj | 13 +++ .../SqlServerEndToEnd.DbSetup/Program.cs | 22 ++++ .../Properties/launchSettings.json | 12 ++ .../SqlServerEndToEnd.DbSetup.csproj | 18 +++ .../SqlServerBuilderExtensions.cs | 14 ++- .../SqlServerFunctionalTests.cs | 106 ++++++++++++++++++ 14 files changed, 259 insertions(+), 30 deletions(-) create mode 100644 playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/Entry.cs create mode 100644 playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/MyDb1Context.cs create mode 100644 playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/MyDb2Context.cs create mode 100644 playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/SqlServerEndToEnd.Common.csproj create mode 100644 playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/Program.cs create mode 100644 playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/Properties/launchSettings.json create mode 100644 playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/SqlServerEndToEnd.DbSetup.csproj diff --git a/Aspire.sln b/Aspire.sln index 29e219afab..fe783e6803 100644 --- a/Aspire.sln +++ b/Aspire.sln @@ -604,6 +604,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WaitForSandbox.DbSetup", "p EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WaitForSandbox.Common", "playground\waitfor\WaitForSandbox.Common\WaitForSandbox.Common.csproj", "{F0C976EF-EE26-4EA9-B324-0CD21DCEA140}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlServerEndToEnd.Common", "playground\SqlServerEndToEnd\SqlServerEndToEnd.Common\SqlServerEndToEnd.Common.csproj", "{1997067D-8EF2-43B3-AB13-9B2E12B52709}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlServerEndToEnd.DbSetup", "playground\SqlServerEndToEnd\SqlServerEndToEnd.DbSetup\SqlServerEndToEnd.DbSetup.csproj", "{125C081D-7E5B-4F35-B5CD-E2B56140380F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1590,6 +1594,14 @@ Global {F0C976EF-EE26-4EA9-B324-0CD21DCEA140}.Debug|Any CPU.Build.0 = Debug|Any CPU {F0C976EF-EE26-4EA9-B324-0CD21DCEA140}.Release|Any CPU.ActiveCfg = Release|Any CPU {F0C976EF-EE26-4EA9-B324-0CD21DCEA140}.Release|Any CPU.Build.0 = Release|Any CPU + {1997067D-8EF2-43B3-AB13-9B2E12B52709}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1997067D-8EF2-43B3-AB13-9B2E12B52709}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1997067D-8EF2-43B3-AB13-9B2E12B52709}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1997067D-8EF2-43B3-AB13-9B2E12B52709}.Release|Any CPU.Build.0 = Release|Any CPU + {125C081D-7E5B-4F35-B5CD-E2B56140380F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {125C081D-7E5B-4F35-B5CD-E2B56140380F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {125C081D-7E5B-4F35-B5CD-E2B56140380F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {125C081D-7E5B-4F35-B5CD-E2B56140380F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1881,6 +1893,8 @@ Global {C554C480-3DA7-4D62-A09A-3F3F743D7A66} = {3FF3F00C-95C0-46FC-B2BE-A3920C71E393} {37BC5B4A-3F96-4BB0-92E6-81666F4324E4} = {3FF3F00C-95C0-46FC-B2BE-A3920C71E393} {F0C976EF-EE26-4EA9-B324-0CD21DCEA140} = {3FF3F00C-95C0-46FC-B2BE-A3920C71E393} + {1997067D-8EF2-43B3-AB13-9B2E12B52709} = {2CA6AB88-21EF-4488-BB1B-3A5BAD5FE2AD} + {125C081D-7E5B-4F35-B5CD-E2B56140380F} = {2CA6AB88-21EF-4488-BB1B-3A5BAD5FE2AD} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6DCEDFEC-988E-4CB3-B45B-191EB5086E0C} diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.ApiService/Program.cs b/playground/SqlServerEndToEnd/SqlServerEndToEnd.ApiService/Program.cs index e362eecd54..15d818db14 100644 --- a/playground/SqlServerEndToEnd/SqlServerEndToEnd.ApiService/Program.cs +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.ApiService/Program.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore; +using SqlServerEndToEnd.Common; var builder = WebApplication.CreateBuilder(args); @@ -21,39 +22,23 @@ await db1Context.Database.EnsureCreatedAsync(); await db2Context.Database.EnsureCreatedAsync(); - var entry = new Entry(); - await db1Context.Entries.AddAsync(entry); + var entry1 = new Entry(); + await db1Context.Entries.AddAsync(entry1); await db1Context.SaveChangesAsync(); - var entries = await db1Context.Entries.ToListAsync(); + var entry2 = new Entry(); + await db2Context.Entries.AddAsync(entry2); + await db2Context.SaveChangesAsync(); + + var entries1 = await db1Context.Entries.ToListAsync(); + var entries2 = await db2Context.Entries.ToListAsync(); return new { - totalEntries = entries.Count, - entries = entries + totalEntries = entries1.Count + entries2.Count, + entries1 = entries1, + entries2 = entries2 }; }); app.Run(); - -public class MyDb1Context(DbContextOptions options) : DbContext(options) -{ - public DbSet Entries { get; set; } -} - -public class MyDb2Context(DbContextOptions options) : DbContext(options) -{ - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity().HasKey(e => e.Id); - } - - public DbSet Entries { get; set; } -} - -public class Entry -{ - public Guid Id { get; set; } = Guid.NewGuid(); -} diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.ApiService/SqlServerEndToEnd.ApiService.csproj b/playground/SqlServerEndToEnd/SqlServerEndToEnd.ApiService/SqlServerEndToEnd.ApiService.csproj index 8af39b96e2..f8b53644e5 100644 --- a/playground/SqlServerEndToEnd/SqlServerEndToEnd.ApiService/SqlServerEndToEnd.ApiService.csproj +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.ApiService/SqlServerEndToEnd.ApiService.csproj @@ -9,6 +9,7 @@ + diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs b/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs index 2f57e1669f..d85b19bb8c 100644 --- a/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs @@ -9,10 +9,15 @@ var sql2 = builder.AddSqlServer("sql2").PublishAsContainer(); var db2 = sql2.AddDatabase("db2"); +var dbsetup = builder.AddProject("dbsetup") + .WithReference(db1).WaitFor(sql1) + .WithReference(db2).WaitFor(sql2); + builder.AddProject("api") .WithExternalHttpEndpoints() - .WithReference(db1).WaitFor(sql1) - .WithReference(db2).WaitFor(sql2); + .WithReference(db1) + .WithReference(db2) + .WaitForCompletion(dbsetup); #if !SKIP_DASHBOARD_REFERENCE // This project is only added in playground projects to support development/debugging diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/SqlServerEndToEnd.AppHost.csproj b/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/SqlServerEndToEnd.AppHost.csproj index bc29780bb7..ce8a4e8d9b 100644 --- a/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/SqlServerEndToEnd.AppHost.csproj +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/SqlServerEndToEnd.AppHost.csproj @@ -6,6 +6,7 @@ enable enable true + 209ffcfc-80c8-470f-87d4-ef96525f2cdc @@ -18,6 +19,8 @@ + + diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/Entry.cs b/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/Entry.cs new file mode 100644 index 0000000000..91f709c04e --- /dev/null +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/Entry.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace SqlServerEndToEnd.Common; + +public class Entry +{ + public Guid Id { get; set; } = Guid.NewGuid(); +} diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/MyDb1Context.cs b/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/MyDb1Context.cs new file mode 100644 index 0000000000..cfd78bf30e --- /dev/null +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/MyDb1Context.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore; + +namespace SqlServerEndToEnd.Common; + +public class MyDb1Context(DbContextOptions options) : DbContext(options) +{ + public DbSet Entries { get; set; } +} diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/MyDb2Context.cs b/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/MyDb2Context.cs new file mode 100644 index 0000000000..ca3bb49040 --- /dev/null +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/MyDb2Context.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore; + +namespace SqlServerEndToEnd.Common; + +public class MyDb2Context(DbContextOptions options) : DbContext(options) +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity().HasKey(e => e.Id); + } + + public DbSet Entries { get; set; } +} diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/SqlServerEndToEnd.Common.csproj b/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/SqlServerEndToEnd.Common.csproj new file mode 100644 index 0000000000..7c5a9499ce --- /dev/null +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/SqlServerEndToEnd.Common.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/Program.cs b/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/Program.cs new file mode 100644 index 0000000000..844e616151 --- /dev/null +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/Program.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore; +using SqlServerEndToEnd.Common; + +var builder = WebApplication.CreateBuilder(args); +builder.AddSqlServerDbContext("db1"); +builder.AddSqlServerDbContext("db2"); +using var app = builder.Build(); +using var scope = app.Services.CreateScope(); +using var db1 = scope.ServiceProvider.GetRequiredService(); +using var db2 = scope.ServiceProvider.GetRequiredService(); + +foreach (var db in new DbContext[] { db1, db2 }) +{ + var created = await db.Database.EnsureCreatedAsync(); + if (created) + { + Console.WriteLine("Database created!"); + } +} diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/Properties/launchSettings.json b/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/Properties/launchSettings.json new file mode 100644 index 0000000000..f279c3bdef --- /dev/null +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "SqlServerEndToEnd.DbSetup": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:49994;http://localhost:49995" + } + } +} \ No newline at end of file diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/SqlServerEndToEnd.DbSetup.csproj b/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/SqlServerEndToEnd.DbSetup.csproj new file mode 100644 index 0000000000..a0a97bdd65 --- /dev/null +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/SqlServerEndToEnd.DbSetup.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + diff --git a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs index 47a73114f7..09709d6619 100644 --- a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs +++ b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs @@ -85,7 +85,19 @@ public static IResourceBuilder AddDatabase(this IReso builder.Resource.AddDatabase(name, databaseName); var sqlServerDatabase = new SqlServerDatabaseResource(name, databaseName, builder.Resource); - return builder.ApplicationBuilder.AddResource(sqlServerDatabase); + + string? connectionString = null; + + builder.ApplicationBuilder.Eventing.Subscribe(sqlServerDatabase, async (@event, ct) => + { + connectionString = await sqlServerDatabase.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false); + }); + + var healthCheckKey = $"{name}_check"; + builder.ApplicationBuilder.Services.AddHealthChecks().AddSqlServer(sp => connectionString!, name: healthCheckKey); + + return builder.ApplicationBuilder.AddResource(sqlServerDatabase) + .WithHealthCheck(healthCheckKey); } /// diff --git a/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs b/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs index ba99f9816f..df80a1379f 100644 --- a/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs +++ b/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs @@ -2,12 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Components.Common.Tests; +using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Utils; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; +using Npgsql; using Polly; using Xunit; using Xunit.Abstractions; @@ -16,6 +19,109 @@ namespace Aspire.Hosting.SqlServer.Tests; public class SqlServerFunctionalTests(ITestOutputHelper testOutputHelper) { + [Fact] + [RequiresDocker] + public async Task VerifyWaitForOnSqlServerBlocksDependentResources() + { + var cts = new CancellationTokenSource(TimeSpan.FromMinutes(3)); + using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(testOutputHelper); + + var healthCheckTcs = new TaskCompletionSource(); + builder.Services.AddHealthChecks().AddAsyncCheck("blocking_check", () => + { + return healthCheckTcs.Task; + }); + + var resource = builder.AddSqlServer("postgres") + .WithHealthCheck("blocking_check"); + + var dependentResource = builder.AddSqlServer("dependentresource") + .WaitFor(resource); + + using var app = builder.Build(); + + var pendingStart = app.StartAsync(cts.Token); + + var rns = app.Services.GetRequiredService(); + + await rns.WaitForResourceAsync(resource.Resource.Name, KnownResourceStates.Running, cts.Token); + + await rns.WaitForResourceAsync(dependentResource.Resource.Name, KnownResourceStates.Waiting, cts.Token); + + healthCheckTcs.SetResult(HealthCheckResult.Healthy()); + + await rns.WaitForResourceAsync(resource.Resource.Name, (re => re.Snapshot.HealthStatus == HealthStatus.Healthy), cts.Token); + + await rns.WaitForResourceAsync(dependentResource.Resource.Name, KnownResourceStates.Running, cts.Token); + + await pendingStart; + + await app.StopAsync(); + } + + [Fact] + [RequiresDocker] + public async Task VerifyWaitForOnPostgresDatabaseBlocksDependentResources() + { + var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); + using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(testOutputHelper); + + var healthCheckTcs = new TaskCompletionSource(); + builder.Services.AddHealthChecks().AddAsyncCheck("blocking_check", () => + { + return healthCheckTcs.Task; + }); + + var postgres = builder.AddPostgres("postgres") + .WithHealthCheck("blocking_check"); + + var db = postgres.AddDatabase("db"); + + var dependentResource = builder.AddPostgres("dependentresource") + .WaitFor(db); // Wait on the database instead of the server! + + using var app = builder.Build(); + + var pendingStart = app.StartAsync(cts.Token); + + var rns = app.Services.GetRequiredService(); + + // What for the postgres server to start. + await rns.WaitForResourceAsync(postgres.Resource.Name, KnownResourceStates.Running, cts.Token); + + // The database should adopt the state of the parent resource. + await rns.WaitForResourceAsync(db.Resource.Name, KnownResourceStates.Running, cts.Token); + + // Wait for the dependent resource to be in the Waiting state. + await rns.WaitForResourceAsync(dependentResource.Resource.Name, KnownResourceStates.Waiting, cts.Token); + + // Now unblock the health check. + healthCheckTcs.SetResult(HealthCheckResult.Healthy()); + + // ... and wait for the resource as a whole to move into the health state. + await rns.WaitForResourceAsync(postgres.Resource.Name, (re => re.Snapshot.HealthStatus == HealthStatus.Healthy), cts.Token); + + // Create the database. + var connectionString = await postgres.Resource.GetConnectionStringAsync(cts.Token); + using var connection = new NpgsqlConnection(connectionString); + await connection.OpenAsync(cts.Token); + + var command = connection.CreateCommand(); + command.CommandText = "CREATE DATABASE db;"; + await command.ExecuteNonQueryAsync(cts.Token); + + // ... then wait for the database to turn healthy. + await rns.WaitForResourceAsync(db.Resource.Name, (re => re.Snapshot.HealthStatus == HealthStatus.Healthy), cts.Token); + + // ... then the dependent resource should be able to move into a running state. + await rns.WaitForResourceAsync(dependentResource.Resource.Name, KnownResourceStates.Running, cts.Token); + + await pendingStart; // Startup should now complete. + + // ... but we'll shut everything down immediately because we are done. + await app.StopAsync(); + } + [Fact] [RequiresDocker] public async Task VerifySqlServerResource() From 1ec98be103fe8708b75861621e8be4f584f7f287 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Tue, 10 Sep 2024 20:42:56 +1000 Subject: [PATCH 03/15] Add database dependencies. --- .../SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs b/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs index d85b19bb8c..9b33a4d021 100644 --- a/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs @@ -15,8 +15,8 @@ builder.AddProject("api") .WithExternalHttpEndpoints() - .WithReference(db1) - .WithReference(db2) + .WithReference(db1).WaitFor(db1) + .WithReference(db2).WaitFor(db2) .WaitForCompletion(dbsetup); #if !SKIP_DASHBOARD_REFERENCE From fb8f413823c801a7bdb13426d3af4e7c8f477d72 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Tue, 10 Sep 2024 21:12:43 +1000 Subject: [PATCH 04/15] WIP: Debugging Azure provisioning of resource. --- .../SqlServerEndToEnd.AppHost/Program.cs | 2 +- .../SqlServerFunctionalTests.cs | 35 +++++++------------ .../TestDistributedApplicationBuilder.cs | 5 +++ 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs b/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs index 9b33a4d021..98b436b150 100644 --- a/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs @@ -3,7 +3,7 @@ var builder = DistributedApplication.CreateBuilder(args); -var sql1 = builder.AddSqlServer("sql1").PublishAsAzureSqlDatabase(); +var sql1 = builder.AddSqlServer("sql1").AsAzureSqlDatabase(); var db1 = sql1.AddDatabase("db1"); var sql2 = builder.AddSqlServer("sql2").PublishAsContainer(); diff --git a/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs b/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs index df80a1379f..49bf9a4242 100644 --- a/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs +++ b/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs @@ -10,7 +10,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; -using Npgsql; using Polly; using Xunit; using Xunit.Abstractions; @@ -24,7 +23,7 @@ public class SqlServerFunctionalTests(ITestOutputHelper testOutputHelper) public async Task VerifyWaitForOnSqlServerBlocksDependentResources() { var cts = new CancellationTokenSource(TimeSpan.FromMinutes(3)); - using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(testOutputHelper); + using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var healthCheckTcs = new TaskCompletionSource(); builder.Services.AddHealthChecks().AddAsyncCheck("blocking_check", () => @@ -32,7 +31,7 @@ public async Task VerifyWaitForOnSqlServerBlocksDependentResources() return healthCheckTcs.Task; }); - var resource = builder.AddSqlServer("postgres") + var resource = builder.AddSqlServer("resource") .WithHealthCheck("blocking_check"); var dependentResource = builder.AddSqlServer("dependentresource") @@ -61,10 +60,10 @@ public async Task VerifyWaitForOnSqlServerBlocksDependentResources() [Fact] [RequiresDocker] - public async Task VerifyWaitForOnPostgresDatabaseBlocksDependentResources() + public async Task VerifyWaitForOnSqlServerBlocksDependentResources() { var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); - using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(testOutputHelper); + using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var healthCheckTcs = new TaskCompletionSource(); builder.Services.AddHealthChecks().AddAsyncCheck("blocking_check", () => @@ -72,13 +71,13 @@ public async Task VerifyWaitForOnPostgresDatabaseBlocksDependentResources() return healthCheckTcs.Task; }); - var postgres = builder.AddPostgres("postgres") + var resource = builder.AddSqlServer("resource") .WithHealthCheck("blocking_check"); - var db = postgres.AddDatabase("db"); + var db = resource.AddDatabase("db"); - var dependentResource = builder.AddPostgres("dependentresource") - .WaitFor(db); // Wait on the database instead of the server! + var dependentResource = builder.AddSqlServer("dependentresource") + .WaitFor(db); using var app = builder.Build(); @@ -86,39 +85,31 @@ public async Task VerifyWaitForOnPostgresDatabaseBlocksDependentResources() var rns = app.Services.GetRequiredService(); - // What for the postgres server to start. - await rns.WaitForResourceAsync(postgres.Resource.Name, KnownResourceStates.Running, cts.Token); + await rns.WaitForResourceAsync(resource.Resource.Name, KnownResourceStates.Running, cts.Token); - // The database should adopt the state of the parent resource. await rns.WaitForResourceAsync(db.Resource.Name, KnownResourceStates.Running, cts.Token); - // Wait for the dependent resource to be in the Waiting state. await rns.WaitForResourceAsync(dependentResource.Resource.Name, KnownResourceStates.Waiting, cts.Token); - // Now unblock the health check. healthCheckTcs.SetResult(HealthCheckResult.Healthy()); - // ... and wait for the resource as a whole to move into the health state. - await rns.WaitForResourceAsync(postgres.Resource.Name, (re => re.Snapshot.HealthStatus == HealthStatus.Healthy), cts.Token); + await rns.WaitForResourceAsync(resource.Resource.Name, (re => re.Snapshot.HealthStatus == HealthStatus.Healthy), cts.Token); // Create the database. - var connectionString = await postgres.Resource.GetConnectionStringAsync(cts.Token); - using var connection = new NpgsqlConnection(connectionString); + var connectionString = await resource.Resource.GetConnectionStringAsync(cts.Token); + using var connection = new SqlConnection(connectionString); await connection.OpenAsync(cts.Token); var command = connection.CreateCommand(); command.CommandText = "CREATE DATABASE db;"; await command.ExecuteNonQueryAsync(cts.Token); - // ... then wait for the database to turn healthy. await rns.WaitForResourceAsync(db.Resource.Name, (re => re.Snapshot.HealthStatus == HealthStatus.Healthy), cts.Token); - // ... then the dependent resource should be able to move into a running state. await rns.WaitForResourceAsync(dependentResource.Resource.Name, KnownResourceStates.Running, cts.Token); - await pendingStart; // Startup should now complete. + await pendingStart; - // ... but we'll shut everything down immediately because we are done. await app.StopAsync(); } diff --git a/tests/Aspire.Hosting.Tests/Utils/TestDistributedApplicationBuilder.cs b/tests/Aspire.Hosting.Tests/Utils/TestDistributedApplicationBuilder.cs index f3ef9291f6..826f72ce83 100644 --- a/tests/Aspire.Hosting.Tests/Utils/TestDistributedApplicationBuilder.cs +++ b/tests/Aspire.Hosting.Tests/Utils/TestDistributedApplicationBuilder.cs @@ -45,6 +45,11 @@ public static TestDistributedApplicationBuilder Create(params string[] args) return new TestDistributedApplicationBuilder(options => options.Args = args); } + public static TestDistributedApplicationBuilder Create(ITestOutputHelper testOutputHelper, params string[] args) + { + return new TestDistributedApplicationBuilder(options => options.Args = args, testOutputHelper); + } + public static TestDistributedApplicationBuilder Create(Action? configureOptions, ITestOutputHelper? testOutputHelper = null) { return new TestDistributedApplicationBuilder(configureOptions, testOutputHelper); From 995e8756176a64b52f142f37de3fa48405a0ee15 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Wed, 11 Sep 2024 14:25:58 +1000 Subject: [PATCH 05/15] Fix for threading issue in ResourceNotificationService. --- .../SqlServerEndToEnd.AppHost/Program.cs | 2 +- .../ResourceNotificationService.cs | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs b/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs index 98b436b150..9b33a4d021 100644 --- a/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs @@ -3,7 +3,7 @@ var builder = DistributedApplication.CreateBuilder(args); -var sql1 = builder.AddSqlServer("sql1").AsAzureSqlDatabase(); +var sql1 = builder.AddSqlServer("sql1").PublishAsAzureSqlDatabase(); var db1 = sql1.AddDatabase("db1"); var sql2 = builder.AddSqlServer("sql2").PublishAsContainer(); diff --git a/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs b/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs index 6d807c346e..eef0cfcb86 100644 --- a/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs +++ b/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs @@ -134,6 +134,8 @@ public async Task WaitForResourceAsync(string resourceName, Func< throw new OperationCanceledException($"The operation was cancelled before the resource met the predicate condition."); } + private readonly object _onResourceUpdatedLock = new(); + /// /// Watch for changes to the state for all resources. /// @@ -156,7 +158,10 @@ public async IAsyncEnumerable WatchAsync([EnumeratorCancellation] void WriteToChannel(ResourceEvent resourceEvent) => channel.Writer.TryWrite(resourceEvent); - OnResourceUpdated += WriteToChannel; + lock (_onResourceUpdatedLock) + { + OnResourceUpdated += WriteToChannel; + } try { @@ -167,7 +172,10 @@ void WriteToChannel(ResourceEvent resourceEvent) => } finally { - OnResourceUpdated -= WriteToChannel; + lock (_onResourceUpdatedLock) + { + OnResourceUpdated -= WriteToChannel; + } channel.Writer.TryComplete(); } @@ -212,10 +220,11 @@ public Task PublishUpdateAsync(IResource resource, string resourceId, Func $"{e.Name} = {e.Value}")), string.Join(", ", newState.Urls.Select(u => $"{u.Name} = {u.Url}")), string.Join(", ", newState.Properties.Select(p => $"{p.Name} = {p.Value}"))); } From 3ee077e5032345e7dd649924a122c2a1e1fd1823 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Wed, 11 Sep 2024 20:38:35 +1000 Subject: [PATCH 06/15] Fix Azure SQL spin up. --- .../SqlServerEndToEnd.AppHost/Program.cs | 8 ++++++-- playground/waitfor/WaitForSandbox.AppHost/Program.cs | 2 +- .../waitfor/WaitForSandbox.AppHost/appsettings.json | 3 ++- src/Aspire.Hosting/Health/ResourceHealthCheckScheduler.cs | 2 +- src/Aspire.Hosting/ResourceBuilderExtensions.cs | 4 ++-- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs b/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs index 9b33a4d021..64c005c15f 100644 --- a/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.AppHost/Program.cs @@ -3,10 +3,14 @@ var builder = DistributedApplication.CreateBuilder(args); -var sql1 = builder.AddSqlServer("sql1").PublishAsAzureSqlDatabase(); +var sql1 = builder.AddSqlServer("sql1") + .PublishAsAzureSqlDatabase(); + var db1 = sql1.AddDatabase("db1"); -var sql2 = builder.AddSqlServer("sql2").PublishAsContainer(); +var sql2 = builder.AddSqlServer("sql2") + .PublishAsContainer(); + var db2 = sql2.AddDatabase("db2"); var dbsetup = builder.AddProject("dbsetup") diff --git a/playground/waitfor/WaitForSandbox.AppHost/Program.cs b/playground/waitfor/WaitForSandbox.AppHost/Program.cs index 9ed60e654c..32fda422c2 100644 --- a/playground/waitfor/WaitForSandbox.AppHost/Program.cs +++ b/playground/waitfor/WaitForSandbox.AppHost/Program.cs @@ -4,7 +4,7 @@ var builder = DistributedApplication.CreateBuilder(args); var pg = builder.AddPostgres("pg") -// .AsAzurePostgresFlexibleServer() + .PublishAsAzurePostgresFlexibleServer() .WithPgAdmin(); var db = pg.AddDatabase("db"); diff --git a/playground/waitfor/WaitForSandbox.AppHost/appsettings.json b/playground/waitfor/WaitForSandbox.AppHost/appsettings.json index 31c092aa45..c0359d991e 100644 --- a/playground/waitfor/WaitForSandbox.AppHost/appsettings.json +++ b/playground/waitfor/WaitForSandbox.AppHost/appsettings.json @@ -3,7 +3,8 @@ "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning", - "Aspire.Hosting.Dcp": "Warning" + "Aspire.Hosting.Dcp": "Warning", + "Aspire.Hosting": "Trace" } } } diff --git a/src/Aspire.Hosting/Health/ResourceHealthCheckScheduler.cs b/src/Aspire.Hosting/Health/ResourceHealthCheckScheduler.cs index ca520f32ad..fb68e8d8ec 100644 --- a/src/Aspire.Hosting/Health/ResourceHealthCheckScheduler.cs +++ b/src/Aspire.Hosting/Health/ResourceHealthCheckScheduler.cs @@ -37,7 +37,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) await foreach (var resourceEvent in resourceEvents) { - if (resourceEvent.Snapshot.State == KnownResourceStates.Running) + if (resourceEvent.Snapshot.State?.Text == KnownResourceStates.Running) { // Each time we receive an event that tells us that the resource is // running we need to enable the health check annotation. diff --git a/src/Aspire.Hosting/ResourceBuilderExtensions.cs b/src/Aspire.Hosting/ResourceBuilderExtensions.cs index 40c16557ae..72804b81ad 100644 --- a/src/Aspire.Hosting/ResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting/ResourceBuilderExtensions.cs @@ -608,7 +608,7 @@ public static IResourceBuilder WaitFor(this IResourceBuilder builder, I var resourceEvent = await rns.WaitForResourceAsync(dependency.Resource.Name, re => IsContinuableState(re.Snapshot), cancellationToken: ct).ConfigureAwait(false); var snapshot = resourceEvent.Snapshot; - if (snapshot.State == KnownResourceStates.FailedToStart) + if (snapshot.State?.Text == KnownResourceStates.FailedToStart) { resourceLogger.LogError( "Dependency resource '{ResourceName}' failed to start.", @@ -692,7 +692,7 @@ public static IResourceBuilder WaitForCompletion(this IResourceBuilder var resourceEvent = await rns.WaitForResourceAsync(dependency.Resource.Name, re => IsKnownTerminalState(re.Snapshot), cancellationToken: ct).ConfigureAwait(false); var snapshot = resourceEvent.Snapshot; - if (snapshot.State == KnownResourceStates.FailedToStart) + if (snapshot.State?.Text == KnownResourceStates.FailedToStart) { resourceLogger.LogError( "Dependency resource '{ResourceName}' failed to start.", From a006418bab80d827477cb131e376691b3915065e Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Thu, 12 Sep 2024 12:12:52 +1000 Subject: [PATCH 07/15] Fix up test name. --- .../Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs b/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs index 49bf9a4242..3ed59a337d 100644 --- a/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs +++ b/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs @@ -60,7 +60,7 @@ public async Task VerifyWaitForOnSqlServerBlocksDependentResources() [Fact] [RequiresDocker] - public async Task VerifyWaitForOnSqlServerBlocksDependentResources() + public async Task VerifyWaitForOnSqlServerDatabaseBlocksDependentResources() { var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); From 83db8a21ecef477486d2fca7ea9d2ca2434301a6 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Thu, 12 Sep 2024 16:43:20 +1000 Subject: [PATCH 08/15] Update SqlServerFunctionalTests.cs Co-authored-by: David Fowler --- .../Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs b/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs index 3ed59a337d..c89c11bddf 100644 --- a/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs +++ b/tests/Aspire.Hosting.SqlServer.Tests/SqlServerFunctionalTests.cs @@ -104,7 +104,7 @@ public async Task VerifyWaitForOnSqlServerDatabaseBlocksDependentResources() command.CommandText = "CREATE DATABASE db;"; await command.ExecuteNonQueryAsync(cts.Token); - await rns.WaitForResourceAsync(db.Resource.Name, (re => re.Snapshot.HealthStatus == HealthStatus.Healthy), cts.Token); + await rns.WaitForResourceAsync(db.Resource.Name, re => re.Snapshot.HealthStatus == HealthStatus.Healthy, cts.Token); await rns.WaitForResourceAsync(dependentResource.Resource.Name, KnownResourceStates.Running, cts.Token); From d0ebd8ecaf0f480d8301aebdaa06631d1a15b6b4 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Thu, 12 Sep 2024 16:43:34 +1000 Subject: [PATCH 09/15] Update SqlServerEndToEnd.Common.csproj Co-authored-by: Ankit Jain --- .../SqlServerEndToEnd.Common/SqlServerEndToEnd.Common.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/SqlServerEndToEnd.Common.csproj b/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/SqlServerEndToEnd.Common.csproj index 7c5a9499ce..28a88f8503 100644 --- a/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/SqlServerEndToEnd.Common.csproj +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/SqlServerEndToEnd.Common.csproj @@ -1,7 +1,7 @@ - net8.0 + $(DefaultTargetFramework) enable enable From 1b4841aff9b533009a3968347e12b5e9491d177d Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Thu, 12 Sep 2024 16:43:41 +1000 Subject: [PATCH 10/15] Update SqlServerEndToEnd.DbSetup.csproj Co-authored-by: Ankit Jain --- .../SqlServerEndToEnd.DbSetup/SqlServerEndToEnd.DbSetup.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/SqlServerEndToEnd.DbSetup.csproj b/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/SqlServerEndToEnd.DbSetup.csproj index a0a97bdd65..b1da29b5c4 100644 --- a/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/SqlServerEndToEnd.DbSetup.csproj +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/SqlServerEndToEnd.DbSetup.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + $(DefaultTargetFramework) enable enable From 4fc453f4a297c9cf44596552bc520b2f7db2e9a6 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Thu, 12 Sep 2024 16:44:09 +1000 Subject: [PATCH 11/15] Update SqlServerBuilderExtensions.cs Co-authored-by: Ankit Jain --- src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs index 09709d6619..c59eff66de 100644 --- a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs +++ b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs @@ -42,7 +42,7 @@ public static IResourceBuilder AddSqlServer(this IDistr { if (!lookup.TryGetValue(databaseName.Key, out var databaseResource)) { - throw new DistributedApplicationException($"Database resource '{databaseName}' under SQL Server resource '{sqlServer.Name}' not in model."); + throw new DistributedApplicationException($"Database resource '{databaseName}' under SQL Server resource '{sqlServer.Name}' was not found in the model."); } var connectionStringAvailableEvent = new ConnectionStringAvailableEvent(databaseResource, @event.Services); From e8f40c189a233304c1242a0d96350980fe2debc6 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Thu, 12 Sep 2024 17:07:47 +1000 Subject: [PATCH 12/15] PR feedback. --- .../PostgresBuilderExtensions.cs | 10 ++++++++++ src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs | 5 +++++ .../SqlServerBuilderExtensions.cs | 10 ++++++++++ 3 files changed, 25 insertions(+) diff --git a/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs b/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs index 28ef4c508c..2164b25c61 100644 --- a/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs +++ b/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs @@ -54,6 +54,11 @@ public static IResourceBuilder AddPostgres(this IDistrib { connectionString = await postgresServer.GetConnectionStringAsync(ct).ConfigureAwait(false); + if (connectionString == null) + { + throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{postgresServer}' resource but the connection string was null."); + } + var lookup = builder.Resources.OfType().ToDictionary(d => d.Name); foreach (var databaseName in postgresServer.Databases) @@ -133,6 +138,11 @@ public static IResourceBuilder AddDatabase(this IResou builder.ApplicationBuilder.Eventing.Subscribe(postgresDatabase, async (@event, ct) => { connectionString = await postgresDatabase.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false); + + if (connectionString == null) + { + throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{postgresDatabase}' resource but the connection string was null."); + } }); var healthCheckKey = $"{name}_check"; diff --git a/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs b/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs index 7e765915a1..b858ca9085 100644 --- a/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs +++ b/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs @@ -44,6 +44,11 @@ public static IResourceBuilder AddRedis(this IDistributedApplicat builder.Eventing.Subscribe(redis, async (@event, ct) => { connectionString = await redis.GetConnectionStringAsync(ct).ConfigureAwait(false); + + if (connectionString == null) + { + throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{redis}' resource but the connection string was null."); + } }); var healthCheckKey = $"{name}_check"; diff --git a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs index 09709d6619..d1670805f6 100644 --- a/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs +++ b/src/Aspire.Hosting.SqlServer/SqlServerBuilderExtensions.cs @@ -36,6 +36,11 @@ public static IResourceBuilder AddSqlServer(this IDistr { connectionString = await sqlServer.GetConnectionStringAsync(ct).ConfigureAwait(false); + if (connectionString == null) + { + throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{sqlServer}' resource but the connection string was null."); + } + var lookup = builder.Resources.OfType().ToDictionary(d => d.Name); foreach (var databaseName in sqlServer.Databases) @@ -91,6 +96,11 @@ public static IResourceBuilder AddDatabase(this IReso builder.ApplicationBuilder.Eventing.Subscribe(sqlServerDatabase, async (@event, ct) => { connectionString = await sqlServerDatabase.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false); + + if (connectionString == null) + { + throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{sqlServerDatabase}' resource but the connection string was null."); + } }); var healthCheckKey = $"{name}_check"; From aa479bd21ec4f7dc6a3cc3fa7f321ffe9e8a4e56 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Thu, 12 Sep 2024 18:23:27 +1000 Subject: [PATCH 13/15] Revert "Update SqlServerEndToEnd.DbSetup.csproj" This reverts commit 1b4841aff9b533009a3968347e12b5e9491d177d. --- .../SqlServerEndToEnd.DbSetup/SqlServerEndToEnd.DbSetup.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/SqlServerEndToEnd.DbSetup.csproj b/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/SqlServerEndToEnd.DbSetup.csproj index b1da29b5c4..a0a97bdd65 100644 --- a/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/SqlServerEndToEnd.DbSetup.csproj +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/SqlServerEndToEnd.DbSetup.csproj @@ -2,7 +2,7 @@ Exe - $(DefaultTargetFramework) + net8.0 enable enable From 3b3e597db1466a2909f74b39374a4ba7ebc474bd Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Thu, 12 Sep 2024 18:23:35 +1000 Subject: [PATCH 14/15] Revert "Update SqlServerEndToEnd.Common.csproj" This reverts commit d0ebd8ecaf0f480d8301aebdaa06631d1a15b6b4. --- .../SqlServerEndToEnd.Common/SqlServerEndToEnd.Common.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/SqlServerEndToEnd.Common.csproj b/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/SqlServerEndToEnd.Common.csproj index 28a88f8503..7c5a9499ce 100644 --- a/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/SqlServerEndToEnd.Common.csproj +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/SqlServerEndToEnd.Common.csproj @@ -1,7 +1,7 @@ - $(DefaultTargetFramework) + net8.0 enable enable From d80c9e937a725dcb98eae5bcb038d6bfa46275d9 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Thu, 12 Sep 2024 18:33:12 +1000 Subject: [PATCH 15/15] DefaultFramework. --- .../SqlServerEndToEnd.Common/SqlServerEndToEnd.Common.csproj | 2 +- .../SqlServerEndToEnd.DbSetup/SqlServerEndToEnd.DbSetup.csproj | 2 +- .../WaitForSandbox.ApiService/WaitForSandbox.ApiService.csproj | 2 +- .../WaitForSandbox.AppHost/WaitForSandbox.AppHost.csproj | 2 +- .../waitfor/WaitForSandbox.Common/WaitForSandbox.Common.csproj | 2 +- .../WaitForSandbox.DbSetup/WaitForSandbox.DbSetup.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/SqlServerEndToEnd.Common.csproj b/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/SqlServerEndToEnd.Common.csproj index 7c5a9499ce..28a88f8503 100644 --- a/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/SqlServerEndToEnd.Common.csproj +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.Common/SqlServerEndToEnd.Common.csproj @@ -1,7 +1,7 @@ - net8.0 + $(DefaultTargetFramework) enable enable diff --git a/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/SqlServerEndToEnd.DbSetup.csproj b/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/SqlServerEndToEnd.DbSetup.csproj index a0a97bdd65..b1da29b5c4 100644 --- a/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/SqlServerEndToEnd.DbSetup.csproj +++ b/playground/SqlServerEndToEnd/SqlServerEndToEnd.DbSetup/SqlServerEndToEnd.DbSetup.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + $(DefaultTargetFramework) enable enable diff --git a/playground/waitfor/WaitForSandbox.ApiService/WaitForSandbox.ApiService.csproj b/playground/waitfor/WaitForSandbox.ApiService/WaitForSandbox.ApiService.csproj index 6b01c816d0..d0104da3df 100644 --- a/playground/waitfor/WaitForSandbox.ApiService/WaitForSandbox.ApiService.csproj +++ b/playground/waitfor/WaitForSandbox.ApiService/WaitForSandbox.ApiService.csproj @@ -1,7 +1,7 @@ - net8.0 + $(DefaultTargetFramework) enable enable diff --git a/playground/waitfor/WaitForSandbox.AppHost/WaitForSandbox.AppHost.csproj b/playground/waitfor/WaitForSandbox.AppHost/WaitForSandbox.AppHost.csproj index d4faa83d62..1d38425e7f 100644 --- a/playground/waitfor/WaitForSandbox.AppHost/WaitForSandbox.AppHost.csproj +++ b/playground/waitfor/WaitForSandbox.AppHost/WaitForSandbox.AppHost.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + $(DefaultTargetFramework) enable enable true diff --git a/playground/waitfor/WaitForSandbox.Common/WaitForSandbox.Common.csproj b/playground/waitfor/WaitForSandbox.Common/WaitForSandbox.Common.csproj index 9899ed8b85..b47e95f6aa 100644 --- a/playground/waitfor/WaitForSandbox.Common/WaitForSandbox.Common.csproj +++ b/playground/waitfor/WaitForSandbox.Common/WaitForSandbox.Common.csproj @@ -1,7 +1,7 @@ - net8.0 + $(DefaultTargetFramework) enable enable diff --git a/playground/waitfor/WaitForSandbox.DbSetup/WaitForSandbox.DbSetup.csproj b/playground/waitfor/WaitForSandbox.DbSetup/WaitForSandbox.DbSetup.csproj index 3c478632e2..aa06176671 100644 --- a/playground/waitfor/WaitForSandbox.DbSetup/WaitForSandbox.DbSetup.csproj +++ b/playground/waitfor/WaitForSandbox.DbSetup/WaitForSandbox.DbSetup.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + $(DefaultTargetFramework) enable enable