diff --git a/Directory.Packages.props b/Directory.Packages.props
index a825f3f4ca6..fadc826b0d9 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -70,6 +70,7 @@
+
diff --git a/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs b/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs
index 5271a2fea5b..bf2cbff2c97 100644
--- a/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs
+++ b/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs
@@ -9,7 +9,7 @@
builder.AddProject("api")
.WithExternalHttpEndpoints()
- .WithReference(db);
+ .WithReference(db).WaitFor(db);
#if !SKIP_DASHBOARD_REFERENCE
// This project is only added in playground projects to support development/debugging
diff --git a/src/Aspire.Hosting.Azure.CosmosDB/Aspire.Hosting.Azure.CosmosDB.csproj b/src/Aspire.Hosting.Azure.CosmosDB/Aspire.Hosting.Azure.CosmosDB.csproj
index 8c225ab9bbf..e0fa2341733 100644
--- a/src/Aspire.Hosting.Azure.CosmosDB/Aspire.Hosting.Azure.CosmosDB.csproj
+++ b/src/Aspire.Hosting.Azure.CosmosDB/Aspire.Hosting.Azure.CosmosDB.csproj
@@ -14,10 +14,13 @@
+
+
+
diff --git a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs
index 64fdf876c5d..91fb4fe2cdc 100644
--- a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs
+++ b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs
@@ -3,10 +3,14 @@
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Azure;
+using Aspire.Hosting.Azure.Cosmos;
+using Azure.Identity;
using Azure.Provisioning;
using Azure.Provisioning.CosmosDB;
using Azure.Provisioning.KeyVaults;
using Azure.ResourceManager.CosmosDB.Models;
+using Microsoft.Azure.Cosmos;
+using Microsoft.Extensions.DependencyInjection;
using System.Diagnostics.CodeAnalysis;
namespace Aspire.Hosting;
@@ -69,9 +73,52 @@ public static IResourceBuilder AddAzureCosmosDB(this IDis
};
var resource = new AzureCosmosDBResource(name, configureConstruct);
+
+ CosmosClient? cosmosClient = null;
+
+ builder.Eventing.Subscribe(resource, async (@event, ct) =>
+ {
+ var connectionString = await resource.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false);
+
+ if (connectionString == null)
+ {
+ throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{resource.Name}' resource but the connection string was null.");
+ }
+
+ cosmosClient = CreateCosmosClient(connectionString);
+ });
+
+ var healthCheckKey = $"{name}_check";
+ builder.Services.AddHealthChecks().AddAzureCosmosDB(sp =>
+ {
+ return cosmosClient ?? throw new InvalidOperationException("CosmosClient is not initialized.");
+ }, name: healthCheckKey);
+
return builder.AddResource(resource)
.WithParameter(AzureBicepResource.KnownParameters.KeyVaultName)
- .WithManifestPublishingCallback(resource.WriteToManifest);
+ .WithManifestPublishingCallback(resource.WriteToManifest)
+ .WithHealthCheck(healthCheckKey);
+
+ static CosmosClient CreateCosmosClient(string connectionString)
+ {
+ var clientOptions = new CosmosClientOptions();
+ clientOptions.CosmosClientTelemetryOptions.DisableDistributedTracing = true;
+
+ if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri))
+ {
+ return new CosmosClient(uri.OriginalString, new DefaultAzureCredential(), clientOptions);
+ }
+ else
+ {
+ if (CosmosUtils.IsEmulatorConnectionString(connectionString))
+ {
+ clientOptions.ConnectionMode = ConnectionMode.Gateway;
+ clientOptions.LimitToEndpoint = true;
+ }
+
+ return new CosmosClient(connectionString, clientOptions);
+ }
+ }
}
///
diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureCosmosDBEmulatorFunctionalTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureCosmosDBEmulatorFunctionalTests.cs
new file mode 100644
index 00000000000..9bd631a85cd
--- /dev/null
+++ b/tests/Aspire.Hosting.Azure.Tests/AzureCosmosDBEmulatorFunctionalTests.cs
@@ -0,0 +1,59 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// 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.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Aspire.Hosting.Azure.Tests;
+
+public class AzureCosmosDBEmulatorFunctionalTests(ITestOutputHelper testOutputHelper)
+{
+ [Fact]
+ [RequiresDocker]
+ public async Task VerifyWaitForOnCosmosDBEmulatorBlocksDependentResources()
+ {
+ // Cosmos can be pretty slow to spin up, lets give it plenty of time.
+ var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
+ using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper);
+
+ var healthCheckTcs = new TaskCompletionSource();
+ builder.Services.AddHealthChecks().AddAsyncCheck("blocking_check", () =>
+ {
+ return healthCheckTcs.Task;
+ });
+
+ var resource = builder.AddAzureCosmosDB("resource")
+ .RunAsEmulator()
+ .WithHealthCheck("blocking_check");
+
+ var dependentResource = builder.AddAzureCosmosDB("dependentresource")
+ .RunAsEmulator()
+ .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();
+ }
+
+}