File: RequiredCommandResourceExtensions.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;
using Aspire.Hosting.ApplicationModel;
 
namespace Aspire.Hosting;
 
/// <summary>
/// Provides extension methods for adding required command annotations to resources.
/// </summary>
public static class RequiredCommandResourceExtensions
{
    /// <summary>
    /// Declares that a resource requires a specific command/executable to be available on the local machine PATH before it can start.
    /// </summary>
    /// <typeparam name="T">The resource type.</typeparam>
    /// <param name="builder">The resource builder.</param>
    /// <param name="command">The command string (file name or path) that should be validated.</param>
    /// <param name="helpLink">An optional help link URL to guide users when the command is missing.</param>
    /// <returns>The resource builder.</returns>
    /// <remarks>
    /// The command is considered valid if either:
    /// 1. It is an absolute or relative path (contains a directory separator) that points to an existing file, or
    /// 2. It is discoverable on the current process PATH (respecting PATHEXT on Windows).
    /// If the command is not found, a warning message will be logged but the resource will be allowed to attempt to start.
    /// </remarks>
    public static IResourceBuilder<T> WithRequiredCommand<T>(
        this IResourceBuilder<T> builder,
        string command,
        string? helpLink = null) where T : IResource
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentNullException.ThrowIfNull(command);
 
#pragma warning disable ASPIRECOMMAND001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
        builder.WithAnnotation(new RequiredCommandAnnotation(command)
        {
            HelpLink = helpLink
        });
#pragma warning restore ASPIRECOMMAND001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
 
        return builder;
    }
 
    /// <summary>
    /// Declares that a resource requires a specific command/executable to be available on the local machine PATH before it can start,
    /// with custom validation logic.
    /// </summary>
    /// <typeparam name="T">The resource type.</typeparam>
    /// <param name="builder">The resource builder.</param>
    /// <param name="command">The command string (file name or path) that should be validated.</param>
    /// <param name="validationCallback">A callback that validates the resolved command path. Receives a <see cref="RequiredCommandValidationContext"/> and returns a <see cref="RequiredCommandValidationResult"/>.</param>
    /// <param name="helpLink">An optional help link URL to guide users when the command is missing or fails validation.</param>
    /// <returns>The resource builder.</returns>
    /// <remarks>
    /// The command is first resolved to a full path. If found, the validation callback is invoked with the context containing the resolved path and service provider.
    /// The callback should return a <see cref="RequiredCommandValidationResult"/> indicating whether the command is valid.
    /// If the command is not found or fails validation, a warning message will be logged but the resource will be allowed to attempt to start.
    /// </remarks>
    [Experimental("ASPIRECOMMAND001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")]
    public static IResourceBuilder<T> WithRequiredCommand<T>(
        this IResourceBuilder<T> builder,
        string command,
        Func<RequiredCommandValidationContext, Task<RequiredCommandValidationResult>> validationCallback,
        string? helpLink = null) where T : IResource
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentNullException.ThrowIfNull(command);
        ArgumentNullException.ThrowIfNull(validationCallback);
 
        builder.WithAnnotation(new RequiredCommandAnnotation(command)
        {
            ValidationCallback = validationCallback,
            HelpLink = helpLink
        });
 
        return builder;
    }
}