diff --git a/src/Aspire.Hosting.Azure.EventHubs/AzureEventHubsResource.cs b/src/Aspire.Hosting.Azure.EventHubs/AzureEventHubsResource.cs index c7d5be412e9..89b24c28114 100644 --- a/src/Aspire.Hosting.Azure.EventHubs/AzureEventHubsResource.cs +++ b/src/Aspire.Hosting.Azure.EventHubs/AzureEventHubsResource.cs @@ -52,7 +52,7 @@ internal ReferenceExpression GetConnectionString(string? eventHub = null, string if (IsEmulator) { - builder.Append($"Endpoint=sb://{EmulatorEndpoint.Property(EndpointProperty.Host)}:{EmulatorEndpoint.Property(EndpointProperty.Port)};SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true"); + builder.Append($"Endpoint=sb://{EmulatorEndpoint.Property(EndpointProperty.HostAndPort)};SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true"); } else { diff --git a/src/Aspire.Hosting.Azure.ServiceBus/AzureServiceBusResource.cs b/src/Aspire.Hosting.Azure.ServiceBus/AzureServiceBusResource.cs index ea8b8dc95cb..8d241429558 100644 --- a/src/Aspire.Hosting.Azure.ServiceBus/AzureServiceBusResource.cs +++ b/src/Aspire.Hosting.Azure.ServiceBus/AzureServiceBusResource.cs @@ -33,7 +33,7 @@ public class AzureServiceBusResource(string name, Action public ReferenceExpression ConnectionStringExpression => IsEmulator - ? ReferenceExpression.Create($"Endpoint=sb://{EmulatorEndpoint.Property(EndpointProperty.Host)}:{EmulatorEndpoint.Property(EndpointProperty.Port)};SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;") + ? ReferenceExpression.Create($"Endpoint=sb://{EmulatorEndpoint.Property(EndpointProperty.HostAndPort)};SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;") : ReferenceExpression.Create($"{ServiceBusEndpoint}"); void IResourceWithAzureFunctionsConfig.ApplyAzureFunctionsConfiguration(IDictionary target, string connectionName) diff --git a/src/Aspire.Hosting.Elasticsearch/ElasticsearchResource.cs b/src/Aspire.Hosting.Elasticsearch/ElasticsearchResource.cs index bac9b49d20c..39ed392163c 100644 --- a/src/Aspire.Hosting.Elasticsearch/ElasticsearchResource.cs +++ b/src/Aspire.Hosting.Elasticsearch/ElasticsearchResource.cs @@ -52,6 +52,6 @@ public ElasticsearchResource(string name, ParameterResource password) : base(nam /// Gets the connection string expression for the Elasticsearch /// public ReferenceExpression ConnectionStringExpression => - ReferenceExpression.Create($"http://{UserName}:{PasswordParameter}@{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}"); + ReferenceExpression.Create($"http://{UserName}:{PasswordParameter}@{PrimaryEndpoint.Property(EndpointProperty.HostAndPort)}"); } diff --git a/src/Aspire.Hosting.Garnet/GarnetResource.cs b/src/Aspire.Hosting.Garnet/GarnetResource.cs index 3a1c3281c17..eaf0e9733bc 100644 --- a/src/Aspire.Hosting.Garnet/GarnetResource.cs +++ b/src/Aspire.Hosting.Garnet/GarnetResource.cs @@ -24,9 +24,8 @@ public class GarnetResource(string name) : ContainerResource(ThrowIfNull(name)), /// /// Gets the connection string expression for the Garnet server. /// - public ReferenceExpression ConnectionStringExpression => - ReferenceExpression.Create( - $"{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}"); + public ReferenceExpression ConnectionStringExpression => + ReferenceExpression.Create($"{PrimaryEndpoint.Property(EndpointProperty.HostAndPort)}"); private static string ThrowIfNull([NotNull] string? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) => argument ?? throw new ArgumentNullException(paramName); diff --git a/src/Aspire.Hosting.Kafka/KafkaBuilderExtensions.cs b/src/Aspire.Hosting.Kafka/KafkaBuilderExtensions.cs index ff641d30db9..3eb550f4256 100644 --- a/src/Aspire.Hosting.Kafka/KafkaBuilderExtensions.cs +++ b/src/Aspire.Hosting.Kafka/KafkaBuilderExtensions.cs @@ -140,7 +140,7 @@ static void ConfigureKafkaUIContainer(EnvironmentCallbackContext context, Endpoi // In run mode, Kafka UI assumes Kafka is being accessed over a default Aspire container network and hardcodes the host as the Kafka resource name // This will need to be refactored once updated service discovery APIs are available ? ReferenceExpression.Create($"{endpoint.Resource.Name}:{endpoint.Property(EndpointProperty.TargetPort)}") - : ReferenceExpression.Create($"{endpoint.Property(EndpointProperty.Host)}:{endpoint.Property(EndpointProperty.Port)}"); + : ReferenceExpression.Create($"{endpoint.Property(EndpointProperty.HostAndPort)}"); context.EnvironmentVariables.Add($"KAFKA_CLUSTERS_{index}_NAME", endpoint.Resource.Name); context.EnvironmentVariables.Add($"KAFKA_CLUSTERS_{index}_BOOTSTRAPSERVERS", bootstrapServers); @@ -218,8 +218,7 @@ private static void ConfigureKafkaContainer(EnvironmentCallbackContext context, // In run mode, PLAINTEXT_INTERNAL assumes kafka is being accessed over a default Aspire container network and hardcodes the resource address // This will need to be refactored once updated service discovery APIs are available ? ReferenceExpression.Create($"PLAINTEXT://localhost:29092,PLAINTEXT_HOST://localhost:{primaryEndpoint.Property(EndpointProperty.Port)},PLAINTEXT_INTERNAL://{resource.Name}:{internalEndpoint.Property(EndpointProperty.TargetPort)}") - : ReferenceExpression.Create( - $"PLAINTEXT://{primaryEndpoint.Property(EndpointProperty.Host)}:29092,PLAINTEXT_HOST://{primaryEndpoint.Property(EndpointProperty.Host)}:{primaryEndpoint.Property(EndpointProperty.Port)},PLAINTEXT_INTERNAL://{internalEndpoint.Property(EndpointProperty.Host)}:{internalEndpoint.Property(EndpointProperty.Port)}"); + : ReferenceExpression.Create($"PLAINTEXT://{primaryEndpoint.Property(EndpointProperty.Host)}:29092,PLAINTEXT_HOST://{primaryEndpoint.Property(EndpointProperty.HostAndPort)},PLAINTEXT_INTERNAL://{internalEndpoint.Property(EndpointProperty.HostAndPort)}"); context.EnvironmentVariables["KAFKA_ADVERTISED_LISTENERS"] = advertisedListeners; } diff --git a/src/Aspire.Hosting.Kafka/KafkaServerResource.cs b/src/Aspire.Hosting.Kafka/KafkaServerResource.cs index 51a1042db60..1329731d645 100644 --- a/src/Aspire.Hosting.Kafka/KafkaServerResource.cs +++ b/src/Aspire.Hosting.Kafka/KafkaServerResource.cs @@ -35,6 +35,5 @@ public class KafkaServerResource(string name) : ContainerResource(name), IResour /// Gets the connection string expression for the Kafka broker. /// public ReferenceExpression ConnectionStringExpression => - ReferenceExpression.Create( - $"{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}"); + ReferenceExpression.Create($"{PrimaryEndpoint.Property(EndpointProperty.HostAndPort)}"); } diff --git a/src/Aspire.Hosting.MongoDB/MongoDBServerResource.cs b/src/Aspire.Hosting.MongoDB/MongoDBServerResource.cs index 85474de1791..36ba85ea005 100644 --- a/src/Aspire.Hosting.MongoDB/MongoDBServerResource.cs +++ b/src/Aspire.Hosting.MongoDB/MongoDBServerResource.cs @@ -63,7 +63,7 @@ internal ReferenceExpression BuildConnectionString(string? databaseName = null) builder.Append($"{UserNameReference}:{PasswordParameter}@"); } - builder.Append($"{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}"); + builder.Append($"{PrimaryEndpoint.Property(EndpointProperty.HostAndPort)}"); if (databaseName is not null) { diff --git a/src/Aspire.Hosting.Nats/NatsServerResource.cs b/src/Aspire.Hosting.Nats/NatsServerResource.cs index 117c628d0d4..5665992d13a 100644 --- a/src/Aspire.Hosting.Nats/NatsServerResource.cs +++ b/src/Aspire.Hosting.Nats/NatsServerResource.cs @@ -65,7 +65,7 @@ internal ReferenceExpression BuildConnectionString() builder.Append($"{UserNameReference}:{PasswordParameter}@"); } - builder.Append($"{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}"); + builder.Append($"{PrimaryEndpoint.Property(EndpointProperty.HostAndPort)}"); return builder.Build(); } diff --git a/src/Aspire.Hosting.Oracle/OracleDatabaseServerResource.cs b/src/Aspire.Hosting.Oracle/OracleDatabaseServerResource.cs index e066fea7370..1684d061f13 100644 --- a/src/Aspire.Hosting.Oracle/OracleDatabaseServerResource.cs +++ b/src/Aspire.Hosting.Oracle/OracleDatabaseServerResource.cs @@ -38,7 +38,7 @@ public OracleDatabaseServerResource(string name, ParameterResource password) : b /// public ReferenceExpression ConnectionStringExpression => ReferenceExpression.Create( - $"user id=system;password={PasswordParameter};data source={PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}"); + $"user id=system;password={PasswordParameter};data source={PrimaryEndpoint.Property(EndpointProperty.HostAndPort)}"); private readonly Dictionary _databases = new(StringComparers.ResourceName); diff --git a/src/Aspire.Hosting.RabbitMQ/RabbitMQServerResource.cs b/src/Aspire.Hosting.RabbitMQ/RabbitMQServerResource.cs index 04dc6bb3e16..b9cfb4ba609 100644 --- a/src/Aspire.Hosting.RabbitMQ/RabbitMQServerResource.cs +++ b/src/Aspire.Hosting.RabbitMQ/RabbitMQServerResource.cs @@ -52,5 +52,5 @@ UserNameParameter is not null ? /// public ReferenceExpression ConnectionStringExpression => ReferenceExpression.Create( - $"amqp://{UserNameReference}:{PasswordParameter}@{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}"); + $"amqp://{UserNameReference}:{PasswordParameter}@{PrimaryEndpoint.Property(EndpointProperty.HostAndPort)}"); } diff --git a/src/Aspire.Hosting.Redis/RedisResource.cs b/src/Aspire.Hosting.Redis/RedisResource.cs index ab9528fb55d..cb91caf391c 100644 --- a/src/Aspire.Hosting.Redis/RedisResource.cs +++ b/src/Aspire.Hosting.Redis/RedisResource.cs @@ -19,8 +19,7 @@ public class RedisResource(string name) : ContainerResource(name), IResourceWith public EndpointReference PrimaryEndpoint => _primaryEndpoint ??= new(this, PrimaryEndpointName); private ReferenceExpression ConnectionString => - ReferenceExpression.Create( - $"{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}"); + ReferenceExpression.Create($"{PrimaryEndpoint.Property(EndpointProperty.HostAndPort)}"); /// /// Gets the connection string expression for the Redis server. diff --git a/src/Aspire.Hosting.Valkey/ValkeyResource.cs b/src/Aspire.Hosting.Valkey/ValkeyResource.cs index 7111bd7a756..bb4b57b15c4 100644 --- a/src/Aspire.Hosting.Valkey/ValkeyResource.cs +++ b/src/Aspire.Hosting.Valkey/ValkeyResource.cs @@ -22,6 +22,5 @@ public class ValkeyResource(string name) : ContainerResource(name), IResourceWit /// Gets the connection string expression for the Valkey server. /// public ReferenceExpression ConnectionStringExpression => - ReferenceExpression.Create( - $"{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}"); + ReferenceExpression.Create($"{PrimaryEndpoint.Property(EndpointProperty.HostAndPort)}"); } diff --git a/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs b/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs index a0de3a8eb88..ba472042487 100644 --- a/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs +++ b/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs @@ -48,17 +48,18 @@ public sealed class EndpointReference : IManifestExpressionProvider, IValueProvi /// internal string GetExpression(EndpointProperty property = EndpointProperty.Url) { - var prop = property switch + return property switch { - EndpointProperty.Url => "url", - EndpointProperty.Host or EndpointProperty.IPV4Host => "host", - EndpointProperty.Port => "port", - EndpointProperty.Scheme => "scheme", - EndpointProperty.TargetPort => "targetPort", + EndpointProperty.Url => Binding("url"), + EndpointProperty.Host or EndpointProperty.IPV4Host => Binding("host"), + EndpointProperty.Port => Binding("port"), + EndpointProperty.Scheme => Binding("scheme"), + EndpointProperty.TargetPort => Binding("targetPort"), + EndpointProperty.HostAndPort => $"{Binding("host")}:{Binding("port")}", _ => throw new InvalidOperationException($"The property '{property}' is not supported for the endpoint '{EndpointName}'.") }; - return $"{{{Resource.Name}.bindings.{EndpointName}.{prop}}}"; + string Binding(string prop) => $"{{{Resource.Name}.bindings.{EndpointName}.{prop}}}"; } /// @@ -177,6 +178,7 @@ public class EndpointReferenceExpression(EndpointReference endpointReference, En EndpointProperty.Port => new(Endpoint.Port.ToString(CultureInfo.InvariantCulture)), EndpointProperty.Scheme => new(Endpoint.Scheme), EndpointProperty.TargetPort => new(ComputeTargetPort()), + EndpointProperty.HostAndPort => new($"{Endpoint.Host}:{Endpoint.Port.ToString(CultureInfo.InvariantCulture)}"), _ => throw new InvalidOperationException($"The property '{Property}' is not supported for the endpoint '{Endpoint.EndpointName}'.") }; @@ -227,4 +229,9 @@ public enum EndpointProperty /// The target port of the endpoint. /// TargetPort, + + /// + /// The host and port of the endpoint in the format `{Host}:{Port}`. + /// + HostAndPort } diff --git a/src/Aspire.Hosting/ApplicationModel/ExpressionResolver.cs b/src/Aspire.Hosting/ApplicationModel/ExpressionResolver.cs index ed165bd2c4c..16919d899cc 100644 --- a/src/Aspire.Hosting/ApplicationModel/ExpressionResolver.cs +++ b/src/Aspire.Hosting/ApplicationModel/ExpressionResolver.cs @@ -44,14 +44,12 @@ class HostAndPortPresence { hostAndPortPresence.HasPort = true; } - else if (property == EndpointProperty.Url) + else if (property is EndpointProperty.Url or EndpointProperty.HostAndPort) { hostAndPortPresence.HasHost = hostAndPortPresence.HasPort = true; } - return string.Empty; } - // We need to use the root resource, e.g. AzureStorageResource instead of AzureBlobResource // Otherwise, we get the wrong values for IsContainer and Name var target = endpointReference.Resource.GetRootResource(); @@ -73,6 +71,9 @@ bool HasBothHostAndPort() => endpointReference.Scheme, await EvalEndpointAsync(endpointReference, EndpointProperty.Host).ConfigureAwait(false), await EvalEndpointAsync(endpointReference, EndpointProperty.Port).ConfigureAwait(false)), + (EndpointProperty.HostAndPort, _, _) => string.Format(CultureInfo.InvariantCulture, "{0}:{1}", + await EvalEndpointAsync(endpointReference, EndpointProperty.Host).ConfigureAwait(false), + await EvalEndpointAsync(endpointReference, EndpointProperty.Port).ConfigureAwait(false)), _ => await endpointReference.Property(property).GetValueAsync(cancellationToken).ConfigureAwait(false) }; } diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureProvisioningResourceExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureProvisioningResourceExtensionsTests.cs index b2981bd1c1d..4d8daca5f7f 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureProvisioningResourceExtensionsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureProvisioningResourceExtensionsTests.cs @@ -20,7 +20,7 @@ public async Task AsProvisioningParameterTests() .WithHttpsEndpoint(); var endpointReference = apiProject.GetEndpoint("https"); - var referenceExpression = ReferenceExpression.Create($"prefix:{endpointReference.Property(EndpointProperty.Host)}:{endpointReference.Property(EndpointProperty.Port)}"); + var referenceExpression = ReferenceExpression.Create($"prefix:{endpointReference.Property(EndpointProperty.HostAndPort)}"); var resource1 = builder.AddAzureInfrastructure("resource1", infrastructure => { diff --git a/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs b/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs index 50260cc8d14..3dc31f687eb 100644 --- a/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs +++ b/tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs @@ -24,6 +24,8 @@ public class ExpressionResolverTests [InlineData("OnlyHost", true, true, "Host=localhost;")] // host not replaced since no port [InlineData("OnlyPort", true, false, "Port=12345;")] [InlineData("OnlyPort", true, true, "Port=12345;")] // port not replaced since no host + [InlineData("HostAndPort", true, false, "HostPort=ContainerHostName:12345")] + [InlineData("HostAndPort", true, true, "HostPort=testresource:10000")] // host not replaced since no port [InlineData("PortBeforeHost", true, false, "Port=12345;Host=ContainerHostName;")] [InlineData("PortBeforeHost", true, true, "Port=10000;Host=testresource;")] [InlineData("FullAndPartial", true, false, "Test1=http://ContainerHostName:12345/;Test2=https://localhost:12346/;")] @@ -135,6 +137,7 @@ public TestExpressionResolverResource(string exprName) : base("testresource") { "Url2", ReferenceExpression.Create($"Url={Endpoint1};") }, { "OnlyHost", ReferenceExpression.Create($"Host={Endpoint1.Property(EndpointProperty.Host)};") }, { "OnlyPort", ReferenceExpression.Create($"Port={Endpoint1.Property(EndpointProperty.Port)};") }, + { "HostAndPort", ReferenceExpression.Create($"HostPort={Endpoint1.Property(EndpointProperty.HostAndPort)}") }, { "PortBeforeHost", ReferenceExpression.Create($"Port={Endpoint1.Property(EndpointProperty.Port)};Host={Endpoint1.Property(EndpointProperty.Host)};") }, { "FullAndPartial", ReferenceExpression.Create($"Test1={Endpoint1.Property(EndpointProperty.Scheme)}://{Endpoint1.Property(EndpointProperty.IPV4Host)}:{Endpoint1.Property(EndpointProperty.Port)}/;Test2={Endpoint2.Property(EndpointProperty.Scheme)}://localhost:{Endpoint2.Property(EndpointProperty.Port)}/;") } };