Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 3 additions & 13 deletions src/Aspire.Hosting/ApplicationModel/ConnectionStringReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,12 @@ public class ConnectionStringReference(IResourceWithConnectionString resource, b

ValueTask<string?> IValueProvider.GetValueAsync(CancellationToken cancellationToken)
{
return this.GetNetworkValueAsync(null, cancellationToken);
return Resource.GetValueAsync(cancellationToken);
}

ValueTask<string?> IValueProvider.GetValueAsync(ValueProviderContext context, CancellationToken cancellationToken)
async ValueTask<string?> IValueProvider.GetValueAsync(ValueProviderContext context, CancellationToken cancellationToken)
{
return context.Network switch
{
NetworkIdentifier networkContext => GetNetworkValueAsync(networkContext, cancellationToken),
_ => GetNetworkValueAsync(null, cancellationToken)
};
}

private async ValueTask<string?> GetNetworkValueAsync(NetworkIdentifier? networkContext, CancellationToken cancellationToken)
{
ValueProviderContext vpc = new() { Network = networkContext };
var value = await Resource.GetValueAsync(vpc, cancellationToken).ConfigureAwait(false);
var value = await Resource.GetValueAsync(context, cancellationToken).ConfigureAwait(false);

if (string.IsNullOrEmpty(value) && !Optional)
{
Expand Down
20 changes: 5 additions & 15 deletions src/Aspire.Hosting/ApplicationModel/EndpointReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ public class EndpointReferenceExpression(EndpointReference endpointReference, En
/// <exception cref="InvalidOperationException">Throws when the selected <see cref="EndpointProperty"/> enumeration is not known.</exception>
public ValueTask<string?> GetValueAsync(CancellationToken cancellationToken = default)
{
return GetNetworkValueAsync(null, cancellationToken);
return GetValueAsync(new(), cancellationToken);
}

/// <summary>
Expand All @@ -275,33 +275,23 @@ public class EndpointReferenceExpression(EndpointReference endpointReference, En
/// <param name="cancellationToken">A <see cref="CancellationToken"/>.</param>
/// <returns>A <see cref="string"/> containing the selected <see cref="EndpointProperty"/> value.</returns>
/// <exception cref="InvalidOperationException">Throws when the selected <see cref="EndpointProperty"/> enumeration is not known.</exception>
public ValueTask<string?> GetValueAsync(ValueProviderContext context, CancellationToken cancellationToken = default)
public async ValueTask<string?> GetValueAsync(ValueProviderContext context, CancellationToken cancellationToken = default)
{
return context.Network switch
{
NetworkIdentifier networkID => GetNetworkValueAsync(networkID, cancellationToken),
_ => GetNetworkValueAsync(null, cancellationToken)
};
}
var networkContext = context.GetNetworkIdentifier();


private async ValueTask<string?> GetNetworkValueAsync(NetworkIdentifier? context, CancellationToken cancellationToken = default)
{
return Property switch
{
EndpointProperty.Scheme => new(Endpoint.Scheme),
EndpointProperty.IPV4Host when context is null || context == KnownNetworkIdentifiers.LocalhostNetwork => "127.0.0.1",
EndpointProperty.IPV4Host when networkContext == KnownNetworkIdentifiers.LocalhostNetwork => "127.0.0.1",
EndpointProperty.TargetPort when Endpoint.TargetPort is int port => new(port.ToString(CultureInfo.InvariantCulture)),
_ => await ResolveValueWithAllocatedAddress().ConfigureAwait(false)
};

async ValueTask<string?> ResolveValueWithAllocatedAddress()
{
var effectiveContext = context ?? Endpoint.ContextNetworkID;

// 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 nes = Endpoint.EndpointAnnotation.AllAllocatedEndpoints.Where(nes => nes.NetworkID == effectiveContext).FirstOrDefault();
var nes = Endpoint.EndpointAnnotation.AllAllocatedEndpoints.Where(nes => nes.NetworkID == networkContext).FirstOrDefault();
if (nes is null)
{
return null;
Expand Down
71 changes: 53 additions & 18 deletions src/Aspire.Hosting/ApplicationModel/HostUrl.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Hosting.Dcp;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace Aspire.Hosting.ApplicationModel;

/// <summary>
Expand All @@ -13,26 +17,19 @@ public record HostUrl(string Url) : IValueProvider, IManifestExpressionProvider
string IManifestExpressionProvider.ValueExpression => Url;

// Returns the url
ValueTask<string?> IValueProvider.GetValueAsync(System.Threading.CancellationToken _) => GetNetworkValueAsync(null);
ValueTask<string?> IValueProvider.GetValueAsync(System.Threading.CancellationToken cancellationToken) => ((IValueProvider)this).GetValueAsync(new(), cancellationToken);

// Returns the url
ValueTask<string?> IValueProvider.GetValueAsync(ValueProviderContext context, CancellationToken _)
async ValueTask<string?> IValueProvider.GetValueAsync(ValueProviderContext context, CancellationToken cancellationToken)
{
return context.Network switch
{
NetworkIdentifier networkContext => GetNetworkValueAsync(networkContext),
_ => GetNetworkValueAsync(null)
};
}
var networkContext = context.GetNetworkIdentifier();

private ValueTask<string?> GetNetworkValueAsync(NetworkIdentifier? context)
{
// HostUrl is a bit of a hack that is not modeled as an expression
// So in this one case, we need to fix up the container host name 'manually'
// Internally, this is only used for OTEL_EXPORTER_OTLP_ENDPOINT, but HostUrl
// is public, so we don't control how it is used

if (context is null || context == KnownNetworkIdentifiers.LocalhostNetwork)
if (networkContext == KnownNetworkIdentifiers.LocalhostNetwork)
{
return new(Url);
}
Expand All @@ -44,14 +41,52 @@ public record HostUrl(string Url) : IValueProvider, IManifestExpressionProvider
var uri = new UriBuilder(Url);
if (uri.Host is "localhost" or "127.0.0.1" or "[::1]")
{
var hasEndingSlash = Url.EndsWith('/');
uri.Host = KnownHostNames.DefaultContainerTunnelHostName;
retval = uri.ToString();

// Remove trailing slash if we didn't have one before (UriBuilder always adds one)
if (!hasEndingSlash && retval.EndsWith('/'))
if (context.ExecutionContext is { } && context.ExecutionContext.IsRunMode)
{
retval = retval[..^1];
var options = context.ExecutionContext.ServiceProvider.GetRequiredService<IOptions<DcpOptions>>();

var infoService = context.ExecutionContext.ServiceProvider.GetRequiredService<IDcpDependencyCheckService>();
var dcpInfo = await infoService.GetDcpInfoAsync(cancellationToken: cancellationToken).ConfigureAwait(false);

var hasEndingSlash = Url.EndsWith('/');
uri.Host = options.Value.EnableAspireContainerTunnel == true ? KnownHostNames.DefaultContainerTunnelHostName : dcpInfo?.Containers?.ContainerHostName ?? KnownHostNames.DockerDesktopHostBridge;

if (options.Value.EnableAspireContainerTunnel)
{
// We need to consider that both the host and port may need to be remapped
var model = context.ExecutionContext.ServiceProvider.GetRequiredService<DistributedApplicationModel>();
var targetResource = model.Resources.FirstOrDefault(r =>
{
// Find a non-container resource with an endpoint matching the original localhost:port
return !r.IsContainer() &&
r is IResourceWithEndpoints &&
r.TryGetEndpoints(out var endpoints) &&
endpoints.Any(ep => ep.DefaultNetworkID == KnownNetworkIdentifiers.LocalhostNetwork && ep.Port == uri.Port);
});

if (targetResource is IResourceWithEndpoints resourceWithEndpoints)
{
var originalEndpoint = resourceWithEndpoints.GetEndpoints().FirstOrDefault(ep => ep.ContextNetworkID == KnownNetworkIdentifiers.LocalhostNetwork && ep.Port == uri.Port);
if (originalEndpoint is not null)
{
// Find the mapped endpoint for the target network context
var mappedEndpoint = resourceWithEndpoints.GetEndpoint(originalEndpoint.EndpointName, networkContext);
if (mappedEndpoint is not null)
{
// Update the port to the mapped port
uri.Port = mappedEndpoint.Port;
}
}
}
}

retval = uri.ToString();

// Remove trailing slash if we didn't have one before (UriBuilder always adds one)
if (!hasEndingSlash && retval.EndsWith('/'))
{
retval = retval[..^1];
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,8 @@ public interface IResourceWithConnectionString : IResource, IManifestExpressionP

ValueTask<string?> IValueProvider.GetValueAsync(CancellationToken cancellationToken) => GetConnectionStringAsync(cancellationToken);

ValueTask<string?> IValueProvider.GetValueAsync(ValueProviderContext context, CancellationToken cancellationToken) => context.Network switch
{
NetworkIdentifier networkContext => ConnectionStringExpression.GetValueAsync(new ValueProviderContext { Network = networkContext }, cancellationToken),
_ => GetConnectionStringAsync(cancellationToken),
};
ValueTask<string?> IValueProvider.GetValueAsync(ValueProviderContext context, CancellationToken cancellationToken) =>
ConnectionStringExpression.GetValueAsync(context, cancellationToken);

/// <summary>
/// Describes the connection string format string used for this resource.
Expand Down
4 changes: 2 additions & 2 deletions src/Aspire.Hosting/ApplicationModel/IValueProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ namespace Aspire.Hosting.ApplicationModel;
public class ValueProviderContext
{
/// <summary>
/// Additional services that can be used during value resolution.
/// The execution context for the distributed application.
/// </summary>
public IServiceProvider? Services { get; init; }
public DistributedApplicationExecutionContext? ExecutionContext { get; init; }

/// <summary>
/// The resource that is requesting the value.
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Hosting/ApplicationModel/ReferenceExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ private ReferenceExpression(string format, IValueProvider[] valueProviders, stri
/// <param name="cancellationToken">A <see cref="CancellationToken"/>.</param>
public ValueTask<string?> GetValueAsync(CancellationToken cancellationToken)
{
return this.GetValueAsync(new ValueProviderContext(), cancellationToken);
return this.GetValueAsync(new(), cancellationToken);
}

internal static ReferenceExpression Create(string format, IValueProvider[] valueProviders, string[] manifestExpressions, string?[] stringFormats)
Expand Down
Loading
Loading