Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
110 changes: 110 additions & 0 deletions src/Aspire.Hosting/ApplicationModel/EndpointUpdateContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Net.Sockets;

namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// Provides a mutable callback context for updating an endpoint in polyglot app hosts.
/// </summary>
[AspireExport(ExposeProperties = true)]
internal sealed class EndpointUpdateContext(EndpointAnnotation endpointAnnotation)
{
Comment thread
sebastienros marked this conversation as resolved.
private readonly EndpointAnnotation _endpointAnnotation = endpointAnnotation ?? throw new ArgumentNullException(nameof(endpointAnnotation));

Comment thread
sebastienros marked this conversation as resolved.
/// <summary>
/// Gets the endpoint name.
/// </summary>
public string Name => _endpointAnnotation.Name;

/// <summary>
/// Gets or sets the network protocol.
/// </summary>
public ProtocolType Protocol
{
get => _endpointAnnotation.Protocol;
set => _endpointAnnotation.Protocol = value;
}

/// <summary>
/// Gets or sets the desired host port.
/// </summary>
public int? Port
{
get => _endpointAnnotation.Port;
set => _endpointAnnotation.Port = value;
}

/// <summary>
/// Gets or sets the target port.
/// </summary>
public int? TargetPort
{
get => _endpointAnnotation.TargetPort;
set => _endpointAnnotation.TargetPort = value;
}

/// <summary>
/// Gets or sets the URI scheme.
/// </summary>
public string UriScheme
{
get => _endpointAnnotation.UriScheme;
set => _endpointAnnotation.UriScheme = value;
}

/// <summary>
/// Gets or sets the target host.
/// </summary>
public string TargetHost
{
get => _endpointAnnotation.TargetHost;
set => _endpointAnnotation.TargetHost = value;
}

/// <summary>
/// Gets or sets the transport.
/// </summary>
public string Transport
{
get => _endpointAnnotation.Transport;
set => _endpointAnnotation.Transport = value;
}

/// <summary>
/// Gets or sets a value indicating whether the endpoint is external.
/// </summary>
public bool IsExternal
{
get => _endpointAnnotation.IsExternal;
set => _endpointAnnotation.IsExternal = value;
}

/// <summary>
/// Gets or sets a value indicating whether the endpoint is proxied.
/// </summary>
public bool IsProxied
{
get => _endpointAnnotation.IsProxied;
set => _endpointAnnotation.IsProxied = value;
}

/// <summary>
/// Gets or sets a value indicating whether the endpoint is excluded from the default reference set.
/// </summary>
public bool ExcludeReferenceEndpoint
{
get => _endpointAnnotation.ExcludeReferenceEndpoint;
set => _endpointAnnotation.ExcludeReferenceEndpoint = value;
}

/// <summary>
/// Gets or sets a value indicating whether TLS is enabled.
/// </summary>
public bool TlsEnabled
{
get => _endpointAnnotation.TlsEnabled;
set => _endpointAnnotation.TlsEnabled = value;
}
}
43 changes: 41 additions & 2 deletions src/Aspire.Hosting/ResourceBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1280,9 +1280,9 @@ private static void ApplyEndpoints<T>(this IResourceBuilder<T> builder, IResourc
/// });
/// </code>
/// </example>
/// <para>This method is not available in polyglot app hosts. Use the parameter-based overload instead.</para>
/// <para>This method is not available in polyglot app hosts. Use the callback-based endpoint mutation export instead.</para>
/// </remarks>
[AspireExportIgnore(Reason = "EndpointAnnotation has read-only properties AllocatedEndpointSnapshot and AllAllocatedEndpoints that are not ATS-compatible. Callback-free variant is exported.")]
[AspireExportIgnore(Reason = "Polyglot app hosts use the internal withEndpointCallback export, which exposes EndpointUpdateContext instead of EndpointAnnotation.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "<Pending>")]
public static IResourceBuilder<T> WithEndpoint<T>(this IResourceBuilder<T> builder, [EndpointName] string endpointName, Action<EndpointAnnotation> callback, bool createIfNotExists = true) where T : IResourceWithEndpoints
{
Expand Down Expand Up @@ -1317,6 +1317,45 @@ public static IResourceBuilder<T> WithEndpoint<T>(this IResourceBuilder<T> build
return builder;
}

[AspireExport(Description = "Updates a named endpoint via callback")]
internal static IResourceBuilder<T> WithEndpointCallback<T>(this IResourceBuilder<T> builder, [EndpointName] string endpointName, Action<EndpointUpdateContext> callback, bool createIfNotExists = true) where T : IResourceWithEndpoints
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(endpointName);
ArgumentNullException.ThrowIfNull(callback);

return builder.WithEndpoint(endpointName, endpoint => callback(new EndpointUpdateContext(endpoint)), createIfNotExists);
}

[AspireExport(Description = "Updates an HTTP endpoint via callback")]
internal static IResourceBuilder<T> WithHttpEndpointCallback<T>(this IResourceBuilder<T> builder, Action<EndpointUpdateContext> callback, [EndpointName] string? name = null, bool createIfNotExists = true) where T : IResourceWithEndpoints
Comment thread
sebastienros marked this conversation as resolved.
Comment thread
sebastienros marked this conversation as resolved.
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(callback);

return builder.WithWellKnownEndpointCallback(callback, name ?? "http", createIfNotExists, static (resourceBuilder, endpointName) => resourceBuilder.WithHttpEndpoint(name: endpointName));
}

[AspireExport(Description = "Updates an HTTPS endpoint via callback")]
internal static IResourceBuilder<T> WithHttpsEndpointCallback<T>(this IResourceBuilder<T> builder, Action<EndpointUpdateContext> callback, [EndpointName] string? name = null, bool createIfNotExists = true) where T : IResourceWithEndpoints
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(callback);

return builder.WithWellKnownEndpointCallback(callback, name ?? "https", createIfNotExists, static (resourceBuilder, endpointName) => resourceBuilder.WithHttpsEndpoint(name: endpointName));
}

private static IResourceBuilder<T> WithWellKnownEndpointCallback<T>(this IResourceBuilder<T> builder, Action<EndpointUpdateContext> callback, string endpointName, bool createIfNotExists, Action<IResourceBuilder<T>, string> createEndpoint) where T : IResourceWithEndpoints
{
if (createIfNotExists &&
!builder.Resource.Annotations.OfType<EndpointAnnotation>().Any(endpoint => string.Equals(endpoint.Name, endpointName, StringComparisons.EndpointAnnotationName)))
{
createEndpoint(builder, endpointName);
}

return builder.WithEndpoint(endpointName, endpoint => callback(new EndpointUpdateContext(endpoint)), createIfNotExists: false);
}

/// <summary>
/// Exposes an endpoint on a resource. A reference to this endpoint can be retrieved using <see cref="ResourceBuilderExtensions.GetEndpoint{T}(IResourceBuilder{T}, string, NetworkIdentifier)"/>.
/// The endpoint name will be the scheme name if not specified.
Expand Down
Loading
Loading