File: ApplicationModel\ProjectLaunchDefaultsAnnotation.cs
Web Access
Project: src\src\Aspire.Hosting\Aspire.Hosting.csproj (Aspire.Hosting)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics.CodeAnalysis;
 
namespace Aspire.Hosting.ApplicationModel;
 
/// <summary>
/// Marks a resource as launched through the .NET SDK, so that Aspire treats it like a C# project.
/// </summary>
/// <remarks>
/// This is a public annotation rather than an interface so resources in other assemblies can opt into the C# project-defaults behavior
/// without implementing a specific interface.
/// </remarks>
[Experimental("ASPIREPROJECTS001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")]
public sealed class ProjectLaunchDefaultsAnnotation : IResourceAnnotation
{
    private IProjectMetadata? _appliedProjectMetadata;
 
    /// <summary>
    /// The config host for each endpoint that originated from Kestrel configuration. Used when
    /// rebuilding the <c>Kestrel__Endpoints__*__Url</c> override environment variables.
    /// </summary>
    internal Dictionary<EndpointAnnotation, string> KestrelEndpointAnnotationHosts { get; } = [];
 
    /// <summary>
    /// The https endpoint that was added as a default. It is excluded from the port and Kestrel
    /// override environment because the target (e.g. a container) likely won't listen on https.
    /// </summary>
    internal EndpointAnnotation? DefaultHttpsEndpoint { get; set; }
 
    /// <summary>
    /// Whether any endpoints originated from Kestrel configuration.
    /// </summary>
    internal bool HasKestrelEndpoints => KestrelEndpointAnnotationHosts.Count > 0;
 
    /// <summary>
    /// Records the project metadata used to materialize project defaults.
    /// </summary>
    internal bool TrySetAppliedProjectMetadata(IProjectMetadata projectMetadata)
    {
        if (_appliedProjectMetadata is not null)
        {
            return false;
        }
 
        _appliedProjectMetadata = projectMetadata;
        return true;
    }
 
    /// <summary>
    /// Validates that project metadata has not changed since project defaults were materialized.
    /// </summary>
    internal void ValidateProjectMetadata(IResource resource, IProjectMetadata projectMetadata)
    {
        if (_appliedProjectMetadata is not null && !ReferenceEquals(projectMetadata, _appliedProjectMetadata))
        {
            throw new InvalidOperationException(
                $"Resource '{resource.Name}' project metadata was replaced after project defaults were applied. " +
                $"Project metadata must remain unchanged after {nameof(ProjectResourceBuilderExtensions.WithProjectDefaults)} configures the resource.");
        }
    }
}
 
internal static class ProjectLaunchDefaultsExtensions
{
    /// <summary>
    /// Determines whether endpoint environment variables should be injected for the given endpoint.
    /// Only http/https endpoints without an explicit target-port environment variable are eligible,
    /// and any <see cref="EndpointEnvironmentInjectionFilterAnnotation"/> may further exclude them.
    /// </summary>
    [AspireExportIgnore(Reason = "Endpoint environment injection filtering is internal .NET launch wiring and is not part of the ATS surface.")]
    public static bool ShouldInjectEndpointEnvironment(this IResource resource, EndpointReference e)
    {
        var endpoint = e.EndpointAnnotation;
 
        if (endpoint.UriScheme is not ("http" or "https") ||    // Only process http and https endpoints
            endpoint.TargetPortEnvironmentVariable is not null) // Skip if target port env variable was set
        {
            return false;
        }
 
        // If any filter rejects the endpoint, skip it
        return !resource.Annotations.OfType<EndpointEnvironmentInjectionFilterAnnotation>()
            .Select(a => a.Filter)
            .Any(f => !f(endpoint));
    }
}