Skip to content
2 changes: 2 additions & 0 deletions playground/yarp/Yarp.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
.WithTransformPathRemovePrefix("/api");
});

frontendService.WithReference(gateway);

#if !SKIP_DASHBOARD_REFERENCE
// This project is only added in playground projects to support development/debugging
// of the dashboard. It is not required in end developer code. Comment out this code
Expand Down
13 changes: 13 additions & 0 deletions playground/yarp/Yarp.Frontend/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,21 @@
// Add services to the container.
builder.AddServiceDefaults();

builder.Services.AddHttpClient("gateway-client", client =>
{
client.BaseAddress = new Uri("https+http://gateway");
});

var app = builder.Build();

app.UseFileServer();

app.MapGet("/api/weatherforecast", async (IHttpClientFactory httpClientFactory) =>
{
var client = httpClientFactory.CreateClient("gateway-client");
var response = await client.GetAsync("/api/weatherforecast");
response.EnsureSuccessStatusCode();
return Results.Content(await response.Content.ReadAsStringAsync(), "application/json");
});

app.Run();
44 changes: 33 additions & 11 deletions src/Aspire.Hosting/ApplicationModel/AllocatedEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,17 @@ public class AllocatedEndpoint
/// </summary>
/// <param name="endpoint">The endpoint.</param>
/// <param name="address">The IP address of the endpoint.</param>
/// <param name="containerHostAddress">The address of the container host.</param>
/// <param name="port">The port number of the endpoint.</param>
/// <param name="targetPortExpression">A string representing how to retrieve the target port of the <see cref="AllocatedEndpoint"/> instance.</param>
/// <param name="bindingMode">The binding mode of the endpoint.</param>
public AllocatedEndpoint(EndpointAnnotation endpoint, string address, int port, EndpointBindingMode bindingMode, string? containerHostAddress = null, string? targetPortExpression = null)
/// <param name="networkID">The network identifier for the network associated with the endpoint.</param>
public AllocatedEndpoint(
EndpointAnnotation endpoint,
string address, int port,
EndpointBindingMode bindingMode,
string? targetPortExpression = null,
NetworkIdentifier? networkID = null
)
{
ArgumentNullException.ThrowIfNull(endpoint);
ArgumentOutOfRangeException.ThrowIfLessThan(port, 1, nameof(port));
Expand All @@ -55,21 +61,37 @@ public AllocatedEndpoint(EndpointAnnotation endpoint, string address, int port,
Endpoint = endpoint;
Address = address;
BindingMode = bindingMode;
ContainerHostAddress = containerHostAddress;
Port = port;
TargetPortExpression = targetPortExpression;
NetworkID = networkID ?? endpoint.DefaultNetworkID;
}

/// <summary>
/// Initializes a new instance of the <see cref="AllocatedEndpoint"/> class.
/// </summary>
/// <param name="endpoint">The endpoint.</param>
/// <param name="address">The IP address of the endpoint.</param>
/// <param name="containerHostAddress">The address of the container host.</param>
/// <param name="port">The port number of the endpoint.</param>
/// <param name="targetPortExpression">A string representing how to retrieve the target port of the <see cref="AllocatedEndpoint"/> instance.</param>
public AllocatedEndpoint(EndpointAnnotation endpoint, string address, int port, string? containerHostAddress = null, string? targetPortExpression = null)
: this(endpoint, address, port, EndpointBindingMode.SingleAddress, containerHostAddress, targetPortExpression)
/// <param name="bindingMode">The binding mode of the endpoint.</param>
public AllocatedEndpoint(
EndpointAnnotation endpoint,
string address, int port,
EndpointBindingMode bindingMode,
string? targetPortExpression = null
): this(endpoint, address, port, bindingMode, targetPortExpression, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="AllocatedEndpoint"/> class.
/// </summary>
/// <param name="endpoint">The endpoint.</param>
/// <param name="address">The IP address of the endpoint.</param>
/// <param name="port">The port number of the endpoint.</param>
/// <param name="targetPortExpression">A string representing how to retrieve the target port of the <see cref="AllocatedEndpoint"/> instance.</param>
public AllocatedEndpoint(EndpointAnnotation endpoint, string address, int port, string? targetPortExpression = null)
: this(endpoint, address, port, EndpointBindingMode.SingleAddress, targetPortExpression)
{
}

Expand All @@ -89,11 +111,6 @@ public AllocatedEndpoint(EndpointAnnotation endpoint, string address, int port,
/// </summary>
public EndpointBindingMode BindingMode { get; private set; }

/// <summary>
/// The address of the container host. This is only set for containerized services.
/// </summary>
public string? ContainerHostAddress { get; private set; }

/// <summary>
/// The port used by the endpoint
/// </summary>
Expand All @@ -119,6 +136,11 @@ public AllocatedEndpoint(EndpointAnnotation endpoint, string address, int port,
/// </summary>
public string? TargetPortExpression { get; }

/// <summary>
/// Gets the network identifier for the network associated with the <see cref="AllocatedEndpoint"/> instance.
/// </summary>
public NetworkIdentifier NetworkID { get; private set; }

/// <summary>
/// Returns a string representation of the allocated endpoint URI.
/// </summary>
Expand Down
20 changes: 18 additions & 2 deletions src/Aspire.Hosting/ApplicationModel/ConnectionStringReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,24 @@ public class ConnectionStringReference(IResourceWithConnectionString resource, b

IEnumerable<object> IValueWithReferences.References => [Resource];

async ValueTask<string?> IValueProvider.GetValueAsync(CancellationToken cancellationToken)
ValueTask<string?> IValueProvider.GetValueAsync(CancellationToken cancellationToken)
{
var value = await Resource.GetValueAsync(cancellationToken).ConfigureAwait(false);
return this.GetNetworkValueAsync(null, cancellationToken);
}

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);

if (string.IsNullOrEmpty(value) && !Optional)
{
Expand All @@ -34,4 +49,5 @@ public class ConnectionStringReference(IResourceWithConnectionString resource, b
}

internal void ThrowConnectionStringUnavailableException() => throw new DistributedApplicationException($"The connection string for the resource '{Resource.Name}' is not available.");

}
111 changes: 108 additions & 3 deletions src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Collections.Concurrent;
using System.Net.Sockets;
using System.Collections;

namespace Aspire.Hosting.ApplicationModel;

Expand All @@ -20,18 +22,65 @@ public sealed class EndpointAnnotation : IResourceAnnotation
private bool _portSetToNull;
private int? _targetPort;
private bool _targetPortSetToNull;
private readonly NetworkIdentifier _networkID;

/// <summary>
/// Initializes a new instance of <see cref="EndpointAnnotation"/>.
/// </summary>
/// <param name="protocol">Network protocol: TCP or UDP are supported today, others possibly in future.</param>
/// <param name="uriScheme">If a service is URI-addressable, this is the URI scheme to use for constructing service URI.</param>
/// <param name="transport">Transport that is being used (e.g. http, http2, http3 etc).</param>
/// <param name="name">Name of the service.</param>
/// <param name="port">Desired port for the service.</param>
/// <param name="targetPort">This is the port the resource is listening on. If the endpoint is used for the container, it is the container port.</param>
/// <param name="isExternal">Indicates that this endpoint should be exposed externally at publish time.</param>
/// <param name="isProxied">Specifies if the endpoint will be proxied by DCP. Defaults to true.</param>
public EndpointAnnotation(
ProtocolType protocol,
string? uriScheme = null,
string? transport = null,
[EndpointName] string? name = null,
int? port = null,
int? targetPort = null,
bool? isExternal = null,
bool isProxied = true
) : this(
protocol,
null,
uriScheme,
transport,
name,
port,
targetPort,
isExternal,
isProxied
)
{ }

/// <summary>
/// Initializes a new instance of <see cref="EndpointAnnotation"/>.
/// </summary>
/// <param name="protocol">Network protocol: TCP or UDP are supported today, others possibly in future.</param>
/// <param name="networkID">The ID of the network that is the "default" network for the Endpoint.
/// <param name="uriScheme">If a service is URI-addressable, this is the URI scheme to use for constructing service URI.</param>
/// <param name="transport">Transport that is being used (e.g. http, http2, http3 etc).</param>
/// <param name="name">Name of the service.</param>
/// <param name="port">Desired port for the service.</param>
/// <param name="targetPort">This is the port the resource is listening on. If the endpoint is used for the container, it is the container port.</param>
/// <param name="isExternal">Indicates that this endpoint should be exposed externally at publish time.</param>
/// <param name="isProxied">Specifies if the endpoint will be proxied by DCP. Defaults to true.</param>
public EndpointAnnotation(ProtocolType protocol, string? uriScheme = null, string? transport = null, [EndpointName] string? name = null, int? port = null, int? targetPort = null, bool? isExternal = null, bool isProxied = true)
/// Clients connected to the same network can reach the endpoint without any routing or network address translation.</param>
public EndpointAnnotation(
ProtocolType protocol,
NetworkIdentifier? networkID,
string? uriScheme = null,
string? transport = null,
[EndpointName] string? name = null,
int? port = null,
int? targetPort = null,
bool? isExternal = null,
bool isProxied = true
)
{
// If the URI scheme is null, we'll adopt either udp:// or tcp:// based on the
// protocol. If the name is null, we'll use the URI scheme as the default. This
Expand All @@ -51,6 +100,8 @@ public EndpointAnnotation(ProtocolType protocol, string? uriScheme = null, strin
_targetPort = targetPort;
IsExternal = isExternal ?? false;
IsProxied = isProxied;
_networkID = networkID ?? KnownNetworkIdentifiers.LocalhostNetwork;
AllAllocatedEndpoints.TryAdd(_networkID, AllocatedEndpointSnapshot);
}

/// <summary>
Expand Down Expand Up @@ -142,7 +193,12 @@ public string Transport
internal string? TargetPortEnvironmentVariable { get; set; }

/// <summary>
/// Gets or sets the allocated endpoint.
/// Gets the ID of the network that is the "default" network for the Endpoint (the one the Endpoint is associated with and can be reached without routing or network address translation).
/// </summary>
public NetworkIdentifier DefaultNetworkID => _networkID;

/// <summary>
/// Gets or sets the default <see cref="AllocatedEndpoint"/> for this Endpoint.
/// </summary>
public AllocatedEndpoint? AllocatedEndpoint
{
Expand Down Expand Up @@ -173,7 +229,56 @@ public AllocatedEndpoint? AllocatedEndpoint
}

/// <summary>
/// Gets the allocated endpoint snapshot.
/// Gets the <see cref="AllocatedEndpointSnapshot"/> for the default <see cref="AllocatedEndpoint"/>.
/// </summary>
public ValueSnapshot<AllocatedEndpoint> AllocatedEndpointSnapshot { get; } = new();

/// <summary>
/// Gets the list of all AllocatedEndpoints associated with this Endpoint.
/// </summary>
public NetworkEndpointSnapshotList AllAllocatedEndpoints { get; } = new();
}

/// <summary>
/// Represents an AllocatedEndpoint snapshot associated with a specific network.
/// </summary>
/// <param name="Snapshot">AllocatedEndpoint snapshot</param>
/// <param name="NetworkID">The ID of the network that is associated with the AllocatedEndpoint snapshot.</param>
public record class NetworkEndpointSnapshot(ValueSnapshot<AllocatedEndpoint> Snapshot, NetworkIdentifier NetworkID);

/// <summary>
/// Holds a list of <see cref="NetworkEndpointSnapshot"/> for an Endpoint, providing thread-safe enumeration and addition.
/// </summary>
public class NetworkEndpointSnapshotList : IEnumerable<NetworkEndpointSnapshot>
{
private readonly ConcurrentBag<NetworkEndpointSnapshot> _snapshots = new();

/// <summary>
/// Provides a thread-safe enumerator over the network endpoint snapshots.
/// </summary>
public IEnumerator<NetworkEndpointSnapshot> GetEnumerator()
{
return _snapshots.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

/// <summary>
/// Adds an AllocatedEndpoint snapshot for a specific network if one does not already exist.
/// </summary>
public bool TryAdd(NetworkIdentifier networkID, ValueSnapshot<AllocatedEndpoint> snapshot)
{
lock (_snapshots)
{
if (_snapshots.Any(s => s.NetworkID.Equals(networkID)))
{
return false;
}
_snapshots.Add(new NetworkEndpointSnapshot(snapshot, networkID));
return true;
}
}
}
Loading