File: ResourceTraitBuilderExtensions.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.Globalization;
using Aspire.Dashboard.Model;
using Aspire.Hosting.ApplicationModel;
 
namespace Aspire.Hosting;
 
/// <summary>Provides methods for adding resource metadata and dashboard defaults.</summary>
public static class ResourceTraitBuilderExtensions
{
    /// <summary>Adds immutable, non-sensitive metadata to a resource. Repeated names add multiple values.</summary>
    /// <typeparam name="T">The resource type.</typeparam>
    /// <param name="builder">The resource builder.</param>
    /// <param name="name">The case-sensitive trait name.</param>
    /// <param name="value">The trait value. Do not include secrets.</param>
    /// <returns>The resource builder.</returns>
    [AspireExport]
    public static IResourceBuilder<T> WithTrait<T>(this IResourceBuilder<T> builder, string name, string value) where T : IResource
    {
        ArgumentNullException.ThrowIfNull(builder);
        var annotation = new TraitAnnotation(name, value);
 
        if (name == KnownResourceTraits.DashboardPosition)
        {
            // Only the position trait is single-valued. Other traits deliberately accumulate.
            foreach (var previous in builder.Resource.Annotations.OfType<TraitAnnotation>().Where(t => t.Name == name).ToArray())
            {
                builder.Resource.Annotations.Remove(previous);
            }
        }
 
        return builder.WithAnnotation(annotation);
    }
 
    /// <summary>Adds AppHost-defined dashboard tags that users cannot remove. Repeated calls add tags.</summary>
    /// <typeparam name="T">The resource type.</typeparam>
    /// <param name="builder">The resource builder.</param>
    /// <param name="tags">The tags, each up to 50 characters with single spaces between words.</param>
    /// <returns>The resource builder.</returns>
    [AspireExportIgnore(Reason = "ReadOnlySpan is not supported by ATS; use the withTags array adapter for polyglot SDKs.")]
    public static IResourceBuilder<T> WithTags<T>(this IResourceBuilder<T> builder, params ReadOnlySpan<string> tags) where T : IResource
    {
        ArgumentNullException.ThrowIfNull(builder);
 
        foreach (var tag in tags)
        {
            builder.WithTrait(KnownResourceTraits.DashboardTag, tag);
        }
 
        return builder;
    }
 
    /// <summary>Adds AppHost-defined dashboard tags that users cannot remove. Repeated calls add tags.</summary>
    /// <param name="builder">The resource builder.</param>
    /// <param name="tags">The tags, each up to 50 characters with single spaces between words.</param>
    /// <returns>The resource builder.</returns>
    [AspireExport("withTags")]
    internal static IResourceBuilder<T> WithTagsForPolyglot<T>(this IResourceBuilder<T> builder, string[] tags) where T : IResource
    {
        ArgumentNullException.ThrowIfNull(tags);
 
        return builder.WithTags(tags.AsSpan());
    }
 
    /// <summary>Sets the default resource position in the dashboard. Saved dashboard ordering takes precedence.</summary>
    /// <typeparam name="T">The resource type.</typeparam>
    /// <param name="builder">The resource builder.</param>
    /// <param name="position">The default position. Lower values appear first.</param>
    /// <returns>The resource builder.</returns>
    [AspireExport]
    public static IResourceBuilder<T> WithDashboardPosition<T>(this IResourceBuilder<T> builder, int position) where T : IResource =>
        builder.WithTrait(KnownResourceTraits.DashboardPosition, position.ToString(CultureInfo.InvariantCulture));
}