From a3019f54dde2033e4fd05b9ebe16d5c0a29cac02 Mon Sep 17 00:00:00 2001 From: Karol Zadora-Przylecki Date: Wed, 11 Feb 2026 18:04:49 -0800 Subject: [PATCH 01/13] New NetworkEndpointSnapshotList API --- .../ApplicationModel/EndpointAnnotation.cs | 36 +++++++++++++++++++ .../ApplicationModel/EndpointReference.cs | 15 +------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs b/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs index 8ee659e3040..179f0e6e3e0 100644 --- a/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs +++ b/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs @@ -101,7 +101,9 @@ public EndpointAnnotation( IsExternal = isExternal ?? false; IsProxied = isProxied; _networkID = networkID ?? KnownNetworkIdentifiers.LocalhostNetwork; +#pragma warning disable CS0618 // Type or member is obsolete AllAllocatedEndpoints.TryAdd(_networkID, AllocatedEndpointSnapshot); +#pragma warning restore CS0618 // Type or member is obsolete } /// @@ -271,6 +273,7 @@ IEnumerator IEnumerable.GetEnumerator() /// /// Adds an AllocatedEndpoint snapshot for a specific network if one does not already exist. /// + [Obsolete("This method is for internal use only and will be marked internal in a future Aspire release. Use AddOrUpdateAllocatedEndpoint instead.")] public bool TryAdd(NetworkIdentifier networkID, ValueSnapshot snapshot) { lock (_snapshots) @@ -283,4 +286,37 @@ public bool TryAdd(NetworkIdentifier networkID, ValueSnapshot return true; } } + + /// + /// Adds and AllocatedEndpoint value associated with a specific network to the snapshot list. + /// + public void AddOrUpdateAllocatedEndpoint(NetworkIdentifier networkID, AllocatedEndpoint endpoint) + { + var nes = GetSnapshotFor(networkID); + nes.Snapshot.SetValue(endpoint); + } + + /// + /// Gets an AllocatedEndpoint for a given network ID, waiting for it to appear if it is not already present. + /// + public Task GetAllocatedEndpointAsync(NetworkIdentifier networkID, CancellationToken cancellationToken = default) + { + var nes = GetSnapshotFor(networkID); + return nes.Snapshot.GetValueAsync(cancellationToken); + } + + private NetworkEndpointSnapshot GetSnapshotFor(NetworkIdentifier networkID) + { + lock (_snapshots) + { + var nes = _snapshots.FirstOrDefault(s => s.NetworkID.Equals(networkID)); + if (nes is null) + { + nes = new NetworkEndpointSnapshot(new ValueSnapshot(), networkID); + _snapshots.Add(nes); + } + return nes; + } + } + } diff --git a/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs b/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs index 35e758cb74c..0051b2edb75 100644 --- a/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs +++ b/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs @@ -307,21 +307,8 @@ public class EndpointReferenceExpression(EndpointReference endpointReference, En async ValueTask ResolveValueWithAllocatedAddress() { - // We are going to take the first snapshot that matches the context network ID. In general there might be multiple endpoints for a single service, - // and in future we might need some sort of policy to choose between them, but for now we just take the first one. var endpointSnapshots = Endpoint.EndpointAnnotation.AllAllocatedEndpoints; - var nes = endpointSnapshots.Where(nes => nes.NetworkID == networkContext).FirstOrDefault(); - if (nes is null) - { - nes = new NetworkEndpointSnapshot(new ValueSnapshot(), networkContext); - if (!endpointSnapshots.TryAdd(networkContext, nes.Snapshot)) - { - // Someone else added it first, use theirs. - nes = endpointSnapshots.Where(nes => nes.NetworkID == networkContext).First(); - } - } - - var allocatedEndpoint = await nes.Snapshot.GetValueAsync(cancellationToken).ConfigureAwait(false); + var allocatedEndpoint = await endpointSnapshots.GetAllocatedEndpointAsync(networkContext, cancellationToken).ConfigureAwait(false); return Property switch { From 75a90f8736772355f7b2fb913b59fee15d6d8061 Mon Sep 17 00:00:00 2001 From: Karol Zadora-Przylecki Date: Wed, 11 Feb 2026 18:11:32 -0800 Subject: [PATCH 02/13] Make tests use new AllocatedEndpoint API --- .../AzurePostgresExtensionsTests.cs | 4 +--- .../ContainerResourceTests.cs | 10 ++-------- .../AddMilvusTests.cs | 4 +--- .../PostgresMcpBuilderTests.cs | 4 +--- .../AddQdrantTests.cs | 8 ++------ .../Aspire.Hosting.Redis.Tests/AddRedisTests.cs | 16 ++++------------ .../EndpointReferenceTests.cs | 4 +--- .../ExpressionResolverTests.cs | 15 +++------------ .../Aspire.Hosting.Tests/WithEnvironmentTests.cs | 9 ++------- 9 files changed, 17 insertions(+), 57 deletions(-) diff --git a/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs index a7f55cc7a7a..f0b79fa665d 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs @@ -371,9 +371,7 @@ public async Task WithPostgresMcpOnAzureDatabaseRunAsContainerAddsMcpResource() c.WithEndpoint("tcp", e => { e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5432); - var snapshot = new ValueSnapshot(); - snapshot.SetValue(new AllocatedEndpoint(e, "postgres.dev.internal", 5432, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); - e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot); + e.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, new AllocatedEndpoint(e, "postgres.dev.internal", 5432, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); }); }); diff --git a/tests/Aspire.Hosting.Containers.Tests/ContainerResourceTests.cs b/tests/Aspire.Hosting.Containers.Tests/ContainerResourceTests.cs index 06495623fc2..18791a7fa5c 100644 --- a/tests/Aspire.Hosting.Containers.Tests/ContainerResourceTests.cs +++ b/tests/Aspire.Hosting.Containers.Tests/ContainerResourceTests.cs @@ -102,10 +102,7 @@ public async Task AddContainerWithArgs() e.AllocatedEndpoint = new(e, "localhost", 1234, targetPortExpression: "1234"); // For container-container lookup we need to add an AllocatedEndpoint on the container network side - var ccae = new AllocatedEndpoint(e, "c1.dev.internal", 2234, EndpointBindingMode.SingleAddress, targetPortExpression: "2234", KnownNetworkIdentifiers.DefaultAspireContainerNetwork); - var snapshot = new ValueSnapshot(); - snapshot.SetValue(ccae); - e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot); + e.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, new AllocatedEndpoint(e, "c1.dev.internal", 2234, EndpointBindingMode.SingleAddress, targetPortExpression: "2234", KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); }); var c2 = appBuilder.AddContainer("container", "none") @@ -113,10 +110,7 @@ public async Task AddContainerWithArgs() { e.UriScheme = "http"; // We only care about the container-side endpoint for this test - var snapshot = new ValueSnapshot(); - var ae = new AllocatedEndpoint(e, "container.dev.internal", 5678, EndpointBindingMode.SingleAddress, targetPortExpression: "5678", KnownNetworkIdentifiers.DefaultAspireContainerNetwork); - snapshot.SetValue(ae); - e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot); + e.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, new AllocatedEndpoint(e, "container.dev.internal", 5678, EndpointBindingMode.SingleAddress, targetPortExpression: "5678", KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); }) .WithArgs(context => { diff --git a/tests/Aspire.Hosting.Milvus.Tests/AddMilvusTests.cs b/tests/Aspire.Hosting.Milvus.Tests/AddMilvusTests.cs index 433b15b1a46..6eb39f1c5c0 100644 --- a/tests/Aspire.Hosting.Milvus.Tests/AddMilvusTests.cs +++ b/tests/Aspire.Hosting.Milvus.Tests/AddMilvusTests.cs @@ -99,9 +99,7 @@ public async Task MilvusClientAppWithReferenceContainsConnectionStrings() .WithEndpoint("grpc", e => { e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", MilvusPortGrpc); - var snapshot = new ValueSnapshot(); - snapshot.SetValue(new AllocatedEndpoint(e, "my-milvus.dev.internal", MilvusPortGrpc, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); - e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot); + e.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, new AllocatedEndpoint(e, "my-milvus.dev.internal", MilvusPortGrpc, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); }); var projectA = appBuilder.AddProject("projecta", o => o.ExcludeLaunchProfile = true) diff --git a/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresMcpBuilderTests.cs b/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresMcpBuilderTests.cs index e2eb3b0de6d..987bc894af0 100644 --- a/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresMcpBuilderTests.cs +++ b/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresMcpBuilderTests.cs @@ -78,9 +78,7 @@ public async Task WithPostgresMcpOnDatabaseSetsDatabaseUriEnvironmentVariable() .WithEndpoint("tcp", e => { e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5432); - var snapshot = new ValueSnapshot(); - snapshot.SetValue(new AllocatedEndpoint(e, "postgres.dev.internal", 5432, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); - e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot); + e.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, new AllocatedEndpoint(e, "postgres.dev.internal", 5432, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); }) .AddDatabase("db") .WithPostgresMcp(); diff --git a/tests/Aspire.Hosting.Qdrant.Tests/AddQdrantTests.cs b/tests/Aspire.Hosting.Qdrant.Tests/AddQdrantTests.cs index 4d50b6207c1..85e46194301 100644 --- a/tests/Aspire.Hosting.Qdrant.Tests/AddQdrantTests.cs +++ b/tests/Aspire.Hosting.Qdrant.Tests/AddQdrantTests.cs @@ -172,16 +172,12 @@ public async Task QdrantClientAppWithReferenceContainsConnectionStrings() .WithEndpoint("grpc", e => { e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 6334); - var snapshot = new ValueSnapshot(); - snapshot.SetValue(new AllocatedEndpoint(e, "my-qdrant.dev.internal", 6334, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); - e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot); + e.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, new AllocatedEndpoint(e, "my-qdrant.dev.internal", 6334, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); }) .WithEndpoint("http", e => { e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 6333); - var snapshot = new ValueSnapshot(); - snapshot.SetValue(new AllocatedEndpoint(e, "my-qdrant.dev.internal", 6333, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); - e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot); + e.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, new AllocatedEndpoint(e, "my-qdrant.dev.internal", 6333, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); }); var projectA = appBuilder.AddProject("projecta", o => o.ExcludeLaunchProfile = true) diff --git a/tests/Aspire.Hosting.Redis.Tests/AddRedisTests.cs b/tests/Aspire.Hosting.Redis.Tests/AddRedisTests.cs index cace9589d26..2d54dc90ef9 100644 --- a/tests/Aspire.Hosting.Redis.Tests/AddRedisTests.cs +++ b/tests/Aspire.Hosting.Redis.Tests/AddRedisTests.cs @@ -304,23 +304,17 @@ public async Task WithRedisInsightProducesCorrectEnvironmentVariables() redis1.WithEndpoint("tcp", e => { e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5001); - var snapshot = new ValueSnapshot(); - snapshot.SetValue(new AllocatedEndpoint(e, "myredis1.dev.internal", 5001, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); - e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot); + e.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, new AllocatedEndpoint(e, "myredis1.dev.internal", 5001, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); }); redis2.WithEndpoint("tcp", e => { e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5002); - var snapshot = new ValueSnapshot(); - snapshot.SetValue(new AllocatedEndpoint(e, "myredis2.dev.internal", 5002, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); - e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot); + e.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, new AllocatedEndpoint(e, "myredis2.dev.internal", 5002, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); }); redis3.WithEndpoint("tcp", e => { e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5003); - var snapshot = new ValueSnapshot(); - snapshot.SetValue(new AllocatedEndpoint(e, "myredis3.dev.internal", 5003, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); - e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot); + e.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, new AllocatedEndpoint(e, "myredis3.dev.internal", 5003, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); }); var redisInsight = Assert.Single(builder.Resources.OfType()); @@ -734,9 +728,7 @@ public async Task RedisInsightEnvironmentCallbackIsIdempotent() .WithEndpoint("tcp", e => { e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 6379); - var snapshot = new ValueSnapshot(); - snapshot.SetValue(new AllocatedEndpoint(e, "redis.dev.internal", 6379, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); - e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot); + e.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, new AllocatedEndpoint(e, "redis.dev.internal", 6379, EndpointBindingMode.SingleAddress, targetPortExpression: null, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); }) .WithRedisInsight(); diff --git a/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs b/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs index 11e3e3d7de6..71f8daa888a 100644 --- a/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs +++ b/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs @@ -330,9 +330,7 @@ public async Task PropertyResolutionTest(EndpointProperty property, ResourceKind : ("host.docker.internal", port); var containerEndpoint = new AllocatedEndpoint(annotation, containerHost, containerPort, EndpointBindingMode.SingleAddress, targetPortExpression: targetPort.ToString(), KnownNetworkIdentifiers.DefaultAspireContainerNetwork); - var snapshot = new ValueSnapshot(); - snapshot.SetValue(containerEndpoint); - annotation.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot); + annotation.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, containerEndpoint); var expression = destination.GetEndpoint(annotation.Name).Property(property); diff --git a/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs b/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs index 429757d98a8..09e808df0fc 100644 --- a/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs +++ b/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs @@ -96,10 +96,7 @@ public async Task ExpressionResolverGeneratesCorrectEndpointStrings(string exprN if (sourceIsContainer) { // Note: on the container network side the port and target port are always the same for AllocatedEndpoint. - var ae = new AllocatedEndpoint(e, containerHost, 22345, EndpointBindingMode.SingleAddress, targetPortExpression: "22345", KnownNetworkIdentifiers.DefaultAspireContainerNetwork); - var snapshot = new ValueSnapshot(); - snapshot.SetValue(ae); - e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot); + e.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, new AllocatedEndpoint(e, containerHost, 22345, EndpointBindingMode.SingleAddress, targetPortExpression: "22345", KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); } }) .WithEndpoint("endpoint2", e => @@ -108,10 +105,7 @@ public async Task ExpressionResolverGeneratesCorrectEndpointStrings(string exprN e.AllocatedEndpoint = new(e, "localhost", 12346, targetPortExpression: "10001"); if (sourceIsContainer) { - var ae = new AllocatedEndpoint(e, containerHost, 22346, EndpointBindingMode.SingleAddress, targetPortExpression: "22346", KnownNetworkIdentifiers.DefaultAspireContainerNetwork); - var snapshot = new ValueSnapshot(); - snapshot.SetValue(ae); - e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot); + e.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, new AllocatedEndpoint(e, containerHost, 22346, EndpointBindingMode.SingleAddress, targetPortExpression: "22346", KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); } }) .WithEndpoint("endpoint3", e => @@ -120,10 +114,7 @@ public async Task ExpressionResolverGeneratesCorrectEndpointStrings(string exprN e.AllocatedEndpoint = new(e, "host with space", 12347); if (sourceIsContainer) { - var ae = new AllocatedEndpoint(e, containerHost, 22347, EndpointBindingMode.SingleAddress, targetPortExpression: "22346", KnownNetworkIdentifiers.DefaultAspireContainerNetwork); - var snapshot = new ValueSnapshot(); - snapshot.SetValue(ae); - e.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot); + e.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, new AllocatedEndpoint(e, containerHost, 22347, EndpointBindingMode.SingleAddress, targetPortExpression: "22346", KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); } }); diff --git a/tests/Aspire.Hosting.Tests/WithEnvironmentTests.cs b/tests/Aspire.Hosting.Tests/WithEnvironmentTests.cs index c6b9b0a1070..18b57208c57 100644 --- a/tests/Aspire.Hosting.Tests/WithEnvironmentTests.cs +++ b/tests/Aspire.Hosting.Tests/WithEnvironmentTests.cs @@ -229,10 +229,7 @@ public async Task EnvironmentVariableExpressions() { ep.AllocatedEndpoint = new AllocatedEndpoint(ep, "localhost", 17454); - var ae = new AllocatedEndpoint(ep, "container1.dev.internal", 10005, EndpointBindingMode.SingleAddress, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork); - var snapshot = new ValueSnapshot(); - snapshot.SetValue(ae); - ep.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot); + ep.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, new AllocatedEndpoint(ep, "container1.dev.internal", 10005, EndpointBindingMode.SingleAddress, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); }); var endpoint = container.GetEndpoint("primary"); @@ -307,8 +304,7 @@ public async Task EnvironmentVariableWithDynamicTargetPort() .WithHttpEndpoint(name: "primary") .WithEndpoint("primary", ep => { - var endpointSnapshot = new ValueSnapshot(); - endpointSnapshot.SetValue(new AllocatedEndpoint( + ep.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, new AllocatedEndpoint( ep, "localhost", 90, @@ -316,7 +312,6 @@ public async Task EnvironmentVariableWithDynamicTargetPort() """{{- portForServing "container1_primary" -}}""", KnownNetworkIdentifiers.DefaultAspireContainerNetwork )); - ep.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, endpointSnapshot); }); var endpoint = container.GetEndpoint("primary"); From cdbe22048d700720c7fb4ebf0305b1901315a66d Mon Sep 17 00:00:00 2001 From: Karol Zadora-Przylecki Date: Wed, 11 Feb 2026 18:21:31 -0800 Subject: [PATCH 03/13] Fix AllocatedEndpoint API usage in DcpExecutor --- src/Aspire.Hosting/Dcp/DcpExecutor.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Aspire.Hosting/Dcp/DcpExecutor.cs b/src/Aspire.Hosting/Dcp/DcpExecutor.cs index b2298e56954..4edf0ce335d 100644 --- a/src/Aspire.Hosting/Dcp/DcpExecutor.cs +++ b/src/Aspire.Hosting/Dcp/DcpExecutor.cs @@ -994,9 +994,7 @@ private void AddAllocatedEndpointInfo(IEnumerable resourc targetPortExpression: $$$"""{{- portForServing "{{{svc.Metadata.Name}}}" -}}""", KnownNetworkIdentifiers.DefaultAspireContainerNetwork ); - var snapshot = new ValueSnapshot(); - snapshot.SetValue(allocatedEndpoint); - sp.EndpointAnnotation.AllAllocatedEndpoints.TryAdd(allocatedEndpoint.NetworkID, snapshot); + sp.EndpointAnnotation.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(allocatedEndpoint.NetworkID, allocatedEndpoint); } } } @@ -1056,9 +1054,7 @@ ts.Service is not null && targetPortExpression: $$$"""{{- portForServing "{{{ts.Service.Name}}}" -}}""", networkID ); - var snapshot = new ValueSnapshot(); - snapshot.SetValue(tunnelAllocatedEndpoint); - endpoint.AllAllocatedEndpoints.TryAdd(networkID, snapshot); + endpoint.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(networkID, tunnelAllocatedEndpoint); } } } From 483b7c0372eb7f2ae14ace35cd576ec90ea1f738 Mon Sep 17 00:00:00 2001 From: Karol Zadora-Przylecki Date: Wed, 11 Feb 2026 18:54:42 -0800 Subject: [PATCH 04/13] Add test --- .../EndpointReferenceTests.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs b/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs index 71f8daa888a..df48ad8ce68 100644 --- a/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs +++ b/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs @@ -361,6 +361,42 @@ static IResourceWithEndpoints CreateResource(string name, ResourceKind kind) } } + [Fact] + public async Task WaitingForAllocatedEndpointWorks() + { + var resource = new TestResource("test"); + var annotation = new EndpointAnnotation(ProtocolType.Tcp, uriScheme: "http", name: "http"); + resource.Annotations.Add(annotation); + var endpointRef = new EndpointReference(resource, annotation); + + // Signals that the background task has started waiting for the value. + var waitingForValue = new SemaphoreSlim(0, 1); + // Signals that the background task received and verified the value. + var success = new SemaphoreSlim(0, 1); + + // Launch a task that waits for the endpoint value. + _ = Task.Run(async () => + { + waitingForValue.Release(); + + var url = await endpointRef.GetValueAsync(CancellationToken.None); + Assert.Equal("http://localhost:5000", url); + + success.Release(); + }); + + await waitingForValue.WaitAsync(); + + // Introduce a deliberate delay so the endpoint is always provided after the waiter is blocked. + await Task.Delay(500); + + // Now provide the allocated endpoint. + var allocatedEndpoint = new AllocatedEndpoint(annotation, "localhost", 5000); + annotation.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.LocalhostNetwork, allocatedEndpoint); + + Assert.True(await success.WaitAsync(TimeSpan.FromSeconds(10)), "The value-waiting task did not complete in time."); + } + public enum ResourceKind { Host, From 88f3e820b15e614d0ca1eab7556a03fdb44b513f Mon Sep 17 00:00:00 2001 From: Karol Zadora-Przylecki Date: Wed, 11 Feb 2026 18:57:49 -0800 Subject: [PATCH 05/13] Fix inconsistent test value --- tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs b/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs index 09e808df0fc..af16db77edc 100644 --- a/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs +++ b/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs @@ -114,7 +114,7 @@ public async Task ExpressionResolverGeneratesCorrectEndpointStrings(string exprN e.AllocatedEndpoint = new(e, "host with space", 12347); if (sourceIsContainer) { - e.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, new AllocatedEndpoint(e, containerHost, 22347, EndpointBindingMode.SingleAddress, targetPortExpression: "22346", KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); + e.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, new AllocatedEndpoint(e, containerHost, 22347, EndpointBindingMode.SingleAddress, targetPortExpression: "22347", KnownNetworkIdentifiers.DefaultAspireContainerNetwork)); } }); From 96240505b015455f4599cca732b458daecf5c6e5 Mon Sep 17 00:00:00 2001 From: Karol Zadora-Przylecki Date: Wed, 11 Feb 2026 19:00:16 -0800 Subject: [PATCH 06/13] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs b/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs index 179f0e6e3e0..376c39dfd2e 100644 --- a/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs +++ b/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs @@ -288,7 +288,7 @@ public bool TryAdd(NetworkIdentifier networkID, ValueSnapshot } /// - /// Adds and AllocatedEndpoint value associated with a specific network to the snapshot list. + /// Adds or updates an AllocatedEndpoint value associated with a specific network in the snapshot list. /// public void AddOrUpdateAllocatedEndpoint(NetworkIdentifier networkID, AllocatedEndpoint endpoint) { From 6c7a58621897fb13580628bca20eedffc76f2b64 Mon Sep 17 00:00:00 2001 From: Karol Zadora-Przylecki Date: Thu, 12 Feb 2026 18:18:45 -0800 Subject: [PATCH 07/13] Do not use hardcoded delay in test --- .../EndpointReferenceTests.cs | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs b/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs index df48ad8ce68..0c8a4287025 100644 --- a/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs +++ b/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs @@ -369,27 +369,19 @@ public async Task WaitingForAllocatedEndpointWorks() resource.Annotations.Add(annotation); var endpointRef = new EndpointReference(resource, annotation); - // Signals that the background task has started waiting for the value. - var waitingForValue = new SemaphoreSlim(0, 1); - // Signals that the background task received and verified the value. var success = new SemaphoreSlim(0, 1); - // Launch a task that waits for the endpoint value. - _ = Task.Run(async () => - { - waitingForValue.Release(); +#pragma warning disable CA2012 // Use ValueTasks correctly + var awaiter = endpointRef.GetValueAsync(CancellationToken.None).GetAwaiter(); +#pragma warning restore CA2012 // Use ValueTasks correctly - var url = await endpointRef.GetValueAsync(CancellationToken.None); + awaiter.OnCompleted(() => + { + var url = awaiter.GetResult(); Assert.Equal("http://localhost:5000", url); - success.Release(); }); - await waitingForValue.WaitAsync(); - - // Introduce a deliberate delay so the endpoint is always provided after the waiter is blocked. - await Task.Delay(500); - // Now provide the allocated endpoint. var allocatedEndpoint = new AllocatedEndpoint(annotation, "localhost", 5000); annotation.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.LocalhostNetwork, allocatedEndpoint); From cb1da63c647c2265507bbd2774a89cc88e81fcff Mon Sep 17 00:00:00 2001 From: Karol Zadora-Przylecki Date: Thu, 12 Feb 2026 19:39:20 -0800 Subject: [PATCH 08/13] Improve WaitingForAllocatedEndpointWorks test --- .../EndpointReferenceTests.cs | 70 +++++++++++++++---- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs b/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs index 0c8a4287025..c0dab7aaf94 100644 --- a/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs +++ b/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Net.Sockets; +using System.Runtime.CompilerServices; namespace Aspire.Hosting.Tests; @@ -369,24 +370,32 @@ public async Task WaitingForAllocatedEndpointWorks() resource.Annotations.Add(annotation); var endpointRef = new EndpointReference(resource, annotation); - var success = new SemaphoreSlim(0, 1); + var waitStarted = new SemaphoreSlim(0, 1); -#pragma warning disable CA2012 // Use ValueTasks correctly - var awaiter = endpointRef.GetValueAsync(CancellationToken.None).GetAwaiter(); -#pragma warning restore CA2012 // Use ValueTasks correctly - - awaiter.OnCompleted(() => + async ValueTask GetAndCheckEndpointRefValue() { - var url = awaiter.GetResult(); - Assert.Equal("http://localhost:5000", url); - success.Release(); - }); + var url = await endpointRef.GetValueAsync(CancellationToken.None); + return url; + } - // Now provide the allocated endpoint. - var allocatedEndpoint = new AllocatedEndpoint(annotation, "localhost", 5000); - annotation.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.LocalhostNetwork, allocatedEndpoint); +#pragma warning disable CA2012 // Use ValueTasks correctly + var consumer = new WithWaitStartedNotification(waitStarted, GetAndCheckEndpointRefValue().GetAwaiter()); +#pragma warning restore CA2012 // Use ValueTasks correctly - Assert.True(await success.WaitAsync(TimeSpan.FromSeconds(10)), "The value-waiting task did not complete in time."); + await Task.WhenAll + ( + Task.Run(async() => + { + await waitStarted.WaitAsync(); + var allocatedEndpoint = new AllocatedEndpoint(annotation, "localhost", 5000); + annotation.AllAllocatedEndpoints.AddOrUpdateAllocatedEndpoint(KnownNetworkIdentifiers.LocalhostNetwork, allocatedEndpoint); + }), + Task.Run(async () => + { + var url = await consumer; + Assert.Equal("http://localhost:5000", url); + }) + ).WaitAsync(TimeSpan.FromSeconds(10)); } public enum ResourceKind @@ -398,4 +407,37 @@ public enum ResourceKind private sealed class TestResource(string name) : Resource(name), IResourceWithEndpoints { } + + private struct WithWaitStartedNotification + { + private readonly WaitStartedNotificationAwaiter _awaiter; + + public WithWaitStartedNotification(SemaphoreSlim waitStarted, ValueTaskAwaiter inner) + { + _awaiter = new WaitStartedNotificationAwaiter(waitStarted, inner); + } + public WaitStartedNotificationAwaiter GetAwaiter() => _awaiter; + } + + private struct WaitStartedNotificationAwaiter: INotifyCompletion + { + private readonly ValueTaskAwaiter _inner; + private readonly SemaphoreSlim _waitStarted; + + public WaitStartedNotificationAwaiter(SemaphoreSlim waitStarted, ValueTaskAwaiter inner) + { + _waitStarted = waitStarted; + _inner = inner; + } + + public bool IsCompleted => false; // Force continuation + + public void OnCompleted(Action continuation) + { + _waitStarted.Release(); + _inner.OnCompleted(continuation); + } + + public T GetResult() => _inner.GetResult(); + } } From 9b386a6d33d9e2badb2a9209e1be08e8dfa3f03b Mon Sep 17 00:00:00 2001 From: Karol Zadora-Przylecki Date: Thu, 12 Feb 2026 19:47:27 -0800 Subject: [PATCH 09/13] Make new test simpler --- tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs b/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs index c0dab7aaf94..d805cfde31d 100644 --- a/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs +++ b/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs @@ -372,14 +372,8 @@ public async Task WaitingForAllocatedEndpointWorks() var waitStarted = new SemaphoreSlim(0, 1); - async ValueTask GetAndCheckEndpointRefValue() - { - var url = await endpointRef.GetValueAsync(CancellationToken.None); - return url; - } - #pragma warning disable CA2012 // Use ValueTasks correctly - var consumer = new WithWaitStartedNotification(waitStarted, GetAndCheckEndpointRefValue().GetAwaiter()); + var consumer = new WithWaitStartedNotification(waitStarted, endpointRef.GetValueAsync(CancellationToken.None).GetAwaiter()); #pragma warning restore CA2012 // Use ValueTasks correctly await Task.WhenAll From 3b652262313992a36112e5da1176b7a446957a9c Mon Sep 17 00:00:00 2001 From: Karol Zadora-Przylecki Date: Fri, 13 Feb 2026 09:13:08 -0800 Subject: [PATCH 10/13] Clean up AllocatedEndpoint API some more --- .../ApplicationModel/EndpointAnnotation.cs | 8 ++++++++ .../ApplicationModel/EndpointReference.cs | 2 ++ .../EndpointReferenceTests.cs | 15 +++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs b/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs index 376c39dfd2e..89f2c4badb1 100644 --- a/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs +++ b/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs @@ -204,8 +204,10 @@ public string Transport /// public AllocatedEndpoint? AllocatedEndpoint { +#pragma warning disable CS0618 // Type or member is obsolete (AllocatedEndpointSnapshot) get { + if (!AllocatedEndpointSnapshot.IsValueSet) { return null; @@ -225,14 +227,20 @@ public AllocatedEndpoint? AllocatedEndpoint } else { + if (_networkID != value.NetworkID) + { + throw new InvalidOperationException($"The default AllocatedEndpoint's network ID must match the EndpointAnnotation network ID ('{_networkID}'). The attempted AllocatedEndpoint belongs to '{value.NetworkID}'."); + } AllocatedEndpointSnapshot.SetValue(value); } } +#pragma warning restore CS0618 // Type or member is obsolete } /// /// Gets the for the default . /// + [Obsolete("This property will be marked as internal in future Aspire release. Use AllocatedEndpoint and AllAllocatedEndpoints properties to access and change allocated endpoints associated with an EndpointAnnotation.")] public ValueSnapshot AllocatedEndpointSnapshot { get; } = new(); /// diff --git a/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs b/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs index 0051b2edb75..08f3fb8b104 100644 --- a/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs +++ b/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs @@ -141,8 +141,10 @@ public EndpointReferenceExpression Property(EndpointProperty property) /// public string Url => AllocatedEndpoint.UriString; +#pragma warning disable CS0618 // Type or member is obsolete internal ValueSnapshot AllocatedEndpointSnapshot => EndpointAnnotation.AllocatedEndpointSnapshot; +#pragma warning restore CS0618 // Type or member is obsolete internal AllocatedEndpoint AllocatedEndpoint => GetAllocatedEndpoint() diff --git a/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs b/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs index d805cfde31d..c5f8367d193 100644 --- a/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs +++ b/tests/Aspire.Hosting.Tests/EndpointReferenceTests.cs @@ -286,6 +286,21 @@ public void TargetPort_ReturnsNullWhenNotDefined() Assert.Null(targetPort); } + [Fact] + public void AllocatedEndpoint_ThrowsWhenNetworkIdDoesNotMatch() + { + var annotation = new EndpointAnnotation(ProtocolType.Tcp, KnownNetworkIdentifiers.LocalhostNetwork, uriScheme: "http", name: "http"); + + // Create an AllocatedEndpoint with a different network ID. + var mismatchedEndpoint = new AllocatedEndpoint( + annotation, "localhost", 8080, + EndpointBindingMode.SingleAddress, + targetPortExpression: null, + networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork); + + var ex = Assert.Throws(() => annotation.AllocatedEndpoint = mismatchedEndpoint); + } + [Theory] [InlineData(EndpointProperty.Url, ResourceKind.Host, ResourceKind.Host, "blah://localhost:1234")] [InlineData(EndpointProperty.Url, ResourceKind.Host, ResourceKind.Container, "blah://localhost:1234")] From 5dc21ff447fc3d8de46f582d1cc5f6ef981fdae9 Mon Sep 17 00:00:00 2001 From: Karol Zadora-Przylecki Date: Fri, 13 Feb 2026 09:29:48 -0800 Subject: [PATCH 11/13] Suppress obsolete member errors --- .../DevTunnelResourceBuilderExtensions.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Aspire.Hosting.DevTunnels/DevTunnelResourceBuilderExtensions.cs b/src/Aspire.Hosting.DevTunnels/DevTunnelResourceBuilderExtensions.cs index b2386597920..4bd09c4fbb6 100644 --- a/src/Aspire.Hosting.DevTunnels/DevTunnelResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting.DevTunnels/DevTunnelResourceBuilderExtensions.cs @@ -154,7 +154,9 @@ public static IResourceBuilder AddDevTunnel( var exception = new DistributedApplicationException($"Error trying to create the dev tunnel resource '{tunnelResource.TunnelId}' this port belongs to: {ex.Message}", ex); foreach (var portResource in tunnelResource.Ports) { +#pragma warning disable CS0618 // Type or member is obsolete portResource.TunnelEndpointAnnotation.AllocatedEndpointSnapshot.SetException(exception); +#pragma warning restore CS0618 // Type or member is obsolete } throw; } @@ -209,7 +211,9 @@ await notifications.PublishUpdateAsync(portResource, snapshot => snapshot with catch (Exception ex) { portLogger.LogError(ex, "Error trying to create dev tunnel port '{Port}' on tunnel '{Tunnel}': {Error}", portResource.TargetEndpoint.Port, portResource.DevTunnel.TunnelId, ex.Message); +#pragma warning disable CS0618 // Type or member is obsolete portResource.TunnelEndpointAnnotation.AllocatedEndpointSnapshot.SetException(ex); +#pragma warning restore CS0618 // Type or member is obsolete throw; } From d700d18ebcdc9819c1715cdd7d2cf7c0747c7b58 Mon Sep 17 00:00:00 2001 From: Karol Zadora-Przylecki Date: Fri, 13 Feb 2026 09:44:50 -0800 Subject: [PATCH 12/13] Fix MAUI test --- tests/Aspire.Hosting.Maui.Tests/MauiPlatformExtensionsTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Aspire.Hosting.Maui.Tests/MauiPlatformExtensionsTests.cs b/tests/Aspire.Hosting.Maui.Tests/MauiPlatformExtensionsTests.cs index 521adacb4ac..3813adb0a2e 100644 --- a/tests/Aspire.Hosting.Maui.Tests/MauiPlatformExtensionsTests.cs +++ b/tests/Aspire.Hosting.Maui.Tests/MauiPlatformExtensionsTests.cs @@ -636,7 +636,7 @@ public async Task WithOtlpDevTunnel_CleansUpIntermediateEnvironmentVariables(Pla foreach (var endpointAnnotation in endpointAnnotations) { - endpointAnnotation.AllocatedEndpointSnapshot.SetValue(new AllocatedEndpoint(endpointAnnotation, "localhost", 1234)); + endpointAnnotation.AllocatedEndpoint = new AllocatedEndpoint(endpointAnnotation, "localhost", 1234); } var envVars = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync( From f3713e1d98e88d4a498b00d538b2b5a5301227bd Mon Sep 17 00:00:00 2001 From: Karol Zadora-Przylecki Date: Fri, 13 Feb 2026 14:38:38 -0800 Subject: [PATCH 13/13] Validate endpoint passed to AddOrUpdateAllocatedEndpoint() --- src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs b/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs index 89f2c4badb1..cccc6438924 100644 --- a/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs +++ b/src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs @@ -300,6 +300,10 @@ public bool TryAdd(NetworkIdentifier networkID, ValueSnapshot /// public void AddOrUpdateAllocatedEndpoint(NetworkIdentifier networkID, AllocatedEndpoint endpoint) { + if (endpoint.NetworkID != networkID) + { + throw new ArgumentException($"AllocatedEndpoint must use the same network as the {nameof(networkID)} parameter", nameof(endpoint)); + } var nes = GetSnapshotFor(networkID); nes.Snapshot.SetValue(endpoint); }