File: Ats\BuilderExports.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 Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Eventing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
 
namespace Aspire.Hosting.Ats;
 
/// <summary>
/// ATS exports for <see cref="IDistributedApplicationBuilder"/> properties and related types.
/// </summary>
/// <remarks>
/// <para>
/// The <see cref="IDistributedApplicationBuilder"/> is the central type for defining Aspire applications.
/// This class exposes its properties (Configuration, Environment, AppHostDirectory) and provides
/// capabilities to interact with them when direct property export is insufficient.
/// </para>
/// <para>
/// <strong>Builder Properties:</strong>
/// <list type="bullet">
///   <item><description><c>Configuration</c> - Application configuration (connection strings, settings)</description></item>
///   <item><description><c>Environment</c> - Host environment info (property getters plus environment checks)</description></item>
///   <item><description><c>AppHostDirectory</c> - Directory containing the app host</description></item>
/// </list>
/// </para>
/// <para>
/// <strong>Lifecycle Events:</strong>
/// <list type="bullet">
///   <item><description><c>subscribeBeforeStart</c> - Called before the application starts</description></item>
///   <item><description><c>subscribeAfterResourcesCreated</c> - Called after resources are created</description></item>
/// </list>
/// </para>
/// </remarks>
internal static class BuilderExports
{
    // Note: Configuration, Environment, AppHostDirectory, and ExecutionContext are accessed via property getters
    // on IDistributedApplicationBuilder which has [AspireExport(ExposeProperties = true)].
 
    #region Configuration
 
    /// <summary>
    /// Gets the application configuration.
    /// </summary>
    /// <param name="builder">The distributed application builder.</param>
    /// <returns>The configuration handle.</returns>
    [AspireExport("getConfiguration", Description = "Gets the application configuration")]
    public static IConfiguration GetConfiguration(this IDistributedApplicationBuilder builder)
    {
        ArgumentNullException.ThrowIfNull(builder);
 
        return builder.Configuration;
    }
 
    /// <summary>
    /// Gets a configuration value by key.
    /// </summary>
    /// <param name="configuration">The configuration handle.</param>
    /// <param name="key">The configuration key (e.g., "ConnectionStrings:Default").</param>
    /// <returns>The configuration value, or null if not found.</returns>
    [AspireExport("getConfigValue", Description = "Gets a configuration value by key")]
    public static string? GetConfigValue(this IConfiguration configuration, string key)
    {
        return configuration[key];
    }
 
    /// <summary>
    /// Gets a connection string by name.
    /// </summary>
    /// <param name="configuration">The configuration handle.</param>
    /// <param name="name">The connection string name.</param>
    /// <returns>The connection string value, or null if not found.</returns>
    [AspireExport("getConnectionString", Description = "Gets a connection string by name")]
    public static string? GetConnectionString(this IConfiguration configuration, string name)
    {
        ArgumentNullException.ThrowIfNull(configuration);
        ArgumentNullException.ThrowIfNull(name);
 
        return global::Microsoft.Extensions.Configuration.ConfigurationExtensions.GetConnectionString(configuration, name);
    }
 
    /// <summary>
    /// Gets a configuration section by key.
    /// </summary>
    /// <param name="configuration">The configuration handle.</param>
    /// <param name="key">The configuration key.</param>
    /// <returns>The configuration section handle.</returns>
    [AspireExport("getSection", Description = "Gets a configuration section by key")]
    public static IConfigurationSection GetSection(this IConfiguration configuration, string key)
    {
        ArgumentNullException.ThrowIfNull(configuration);
        ArgumentNullException.ThrowIfNull(key);
 
        return configuration.GetSection(key);
    }
 
    /// <summary>
    /// Gets the child sections of a configuration handle.
    /// </summary>
    /// <param name="configuration">The configuration handle.</param>
    /// <returns>The child sections.</returns>
    [AspireExport("getChildren", Description = "Gets child configuration sections")]
    public static IConfigurationSection[] GetChildren(this IConfiguration configuration)
    {
        ArgumentNullException.ThrowIfNull(configuration);
 
        return [.. configuration.GetChildren()];
    }
 
    /// <summary>
    /// Checks whether a configuration section exists.
    /// </summary>
    /// <param name="configuration">The configuration handle.</param>
    /// <param name="key">The configuration key.</param>
    /// <returns><see langword="true"/> when the section exists; otherwise, <see langword="false"/>.</returns>
    [AspireExport("exists", Description = "Checks whether a configuration section exists")]
    public static bool Exists(this IConfiguration configuration, string key)
    {
        ArgumentNullException.ThrowIfNull(configuration);
        ArgumentNullException.ThrowIfNull(key);
 
        return configuration.GetSection(key).Exists();
    }
 
    #endregion
 
    #region Host Environment
 
    /// <summary>
    /// Checks if the environment is Development.
    /// </summary>
    /// <param name="environment">The host environment handle.</param>
    /// <returns>True if running in Development environment.</returns>
    [AspireExport("isDevelopment", Description = "Checks if running in Development environment")]
    public static bool IsDevelopment(this IHostEnvironment environment)
    {
        ArgumentNullException.ThrowIfNull(environment);
 
        return global::Microsoft.Extensions.Hosting.HostEnvironmentEnvExtensions.IsDevelopment(environment);
    }
 
    /// <summary>
    /// Checks if the environment is Production.
    /// </summary>
    /// <param name="environment">The host environment handle.</param>
    /// <returns>True if running in Production environment.</returns>
    [AspireExport("isProduction", Description = "Checks if running in Production environment")]
    public static bool IsProduction(this IHostEnvironment environment)
    {
        ArgumentNullException.ThrowIfNull(environment);
 
        return global::Microsoft.Extensions.Hosting.HostEnvironmentEnvExtensions.IsProduction(environment);
    }
 
    /// <summary>
    /// Checks if the environment is Staging.
    /// </summary>
    /// <param name="environment">The host environment handle.</param>
    /// <returns>True if running in Staging environment.</returns>
    [AspireExport("isStaging", Description = "Checks if running in Staging environment")]
    public static bool IsStaging(this IHostEnvironment environment)
    {
        ArgumentNullException.ThrowIfNull(environment);
 
        return global::Microsoft.Extensions.Hosting.HostEnvironmentEnvExtensions.IsStaging(environment);
    }
 
    /// <summary>
    /// Checks if the environment matches the specified name.
    /// </summary>
    /// <param name="environment">The host environment handle.</param>
    /// <param name="environmentName">The environment name to compare against.</param>
    /// <returns>True if the environment matches the specified name.</returns>
    [AspireExport("isEnvironment", Description = "Checks if the environment matches the specified name")]
    public static bool IsEnvironment(this IHostEnvironment environment, string environmentName)
    {
        ArgumentNullException.ThrowIfNull(environment);
        ArgumentNullException.ThrowIfNull(environmentName);
 
        return global::Microsoft.Extensions.Hosting.HostEnvironmentEnvExtensions.IsEnvironment(environment, environmentName);
    }
 
    #endregion
 
    #region Lifecycle Events
 
    /// <summary>
    /// Subscribes to the BeforeStart event, which fires before the application starts.
    /// </summary>
    /// <remarks>
    /// This event provides access to the service provider and distributed application model,
    /// allowing you to perform final configuration or validation before resources start.
    /// </remarks>
    /// <param name="builder">The builder handle.</param>
    /// <param name="callback">A callback that receives the exported event when the event fires.</param>
    /// <returns>A subscription handle that can be used to unsubscribe.</returns>
    [AspireExport("subscribeBeforeStart", Description = "Subscribes to the BeforeStart event")]
    public static DistributedApplicationEventSubscription SubscribeBeforeStart(
        this IDistributedApplicationBuilder builder,
        Func<BeforeStartEvent, Task> callback)
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentNullException.ThrowIfNull(callback);
 
        return builder.Eventing.Subscribe<BeforeStartEvent>(async (@event, ct) =>
        {
            await callback(@event).ConfigureAwait(false);
        });
    }
 
    /// <summary>
    /// Subscribes to the AfterResourcesCreated event, which fires after all resources are created.
    /// </summary>
    /// <remarks>
    /// At this point, all resources have been instantiated but may not yet be running.
    /// This is useful for performing cross-resource configuration.
    /// </remarks>
    /// <param name="builder">The builder handle.</param>
    /// <param name="callback">A callback that receives the exported event when the event fires.</param>
    /// <returns>A subscription handle that can be used to unsubscribe.</returns>
    [AspireExport("subscribeAfterResourcesCreated", Description = "Subscribes to the AfterResourcesCreated event")]
    public static DistributedApplicationEventSubscription SubscribeAfterResourcesCreated(
        this IDistributedApplicationBuilder builder,
        Func<AfterResourcesCreatedEvent, Task> callback)
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentNullException.ThrowIfNull(callback);
 
        return builder.Eventing.Subscribe<AfterResourcesCreatedEvent>(async (@event, ct) =>
        {
            await callback(@event).ConfigureAwait(false);
        });
    }
 
    #endregion
}