File: ApplicationModel\ProjectResourceExtensions.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;
 
#pragma warning disable ASPIREPROJECTS001 // ProjectLaunchDefaultsAnnotation is experimental.
 
namespace Aspire.Hosting.ApplicationModel;
 
/// <summary>
/// Provides extension methods for <see cref="DistributedApplicationModel"/> to work with <see cref="ProjectResource"/> instances.
/// </summary>
public static class ProjectResourceExtensions
{
    /// <summary>
    /// Returns all project resources in the distributed application model.
    /// </summary>
    /// <param name="model">The distributed application model.</param>
    /// <returns>An enumerable collection of project resources.</returns>
    [AspireExportIgnore(Reason = "Application model inspection helper — not part of the ATS surface.")]
    public static IEnumerable<ProjectResource> GetProjectResources(this DistributedApplicationModel model)
    {
        ArgumentNullException.ThrowIfNull(model);
 
        return model.Resources.OfType<ProjectResource>();
    }
 
    /// <summary>
    /// Gets the project metadata for the specified project resource.
    /// </summary>
    /// <param name="projectResource">The project resource.</param>
    /// <returns>The project metadata.</returns>
    /// <remarks>
    /// A project resource must carry exactly one <see cref="IProjectMetadata"/> annotation. Project metadata
    /// cannot be replaced after project defaults have been applied because launch settings, endpoints, and
    /// rebuild behavior are derived from that annotation.
    /// </remarks>
    /// <exception cref="InvalidOperationException">Thrown when the project resource doesn't have exactly one project metadata annotation.</exception>
    [AspireExportIgnore(Reason = "Project metadata is a .NET-specific contract and is not part of the ATS surface.")]
    public static IProjectMetadata GetProjectMetadata(this ProjectResource projectResource)
    {
        ArgumentNullException.ThrowIfNull(projectResource);
 
        return GetProjectMetadata((IResource)projectResource);
    }
 
    internal static IProjectMetadata GetProjectMetadata(this IResource projectResource)
    {
        if (!projectResource.TryGetProjectMetadata(out var projectMetadata))
        {
            throw new InvalidOperationException($"Resource '{projectResource.Name}' does not carry an {nameof(IProjectMetadata)} annotation.");
        }
 
        return projectMetadata;
    }
 
    internal static bool TryGetProjectMetadata(this IResource projectResource, [NotNullWhen(true)] out IProjectMetadata? projectMetadata)
    {
        ArgumentNullException.ThrowIfNull(projectResource);
 
        projectMetadata = projectResource.Annotations.OfType<IProjectMetadata>().ToArray() switch
        {
            [] => null,
            [var metadata] => metadata,
            _ => throw new InvalidOperationException(
                $"Resource '{projectResource.Name}' carries more than one {nameof(IProjectMetadata)} annotation. " +
                "Project resources must carry exactly one stable project metadata annotation.")
        };
 
        if (projectMetadata is null)
        {
            return false;
        }
 
        foreach (var launchDefaults in projectResource.Annotations.OfType<ProjectLaunchDefaultsAnnotation>())
        {
            launchDefaults.ValidateProjectMetadata(projectResource, projectMetadata);
        }
 
        return true;
    }
}