From 806a6bf6821acac5d657ac25fc18ec399cbacb36 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 18 Oct 2025 08:27:08 +0000
Subject: [PATCH 1/7] Initial plan
From 0e5e3448f75620bc3e014883e04f0afe9fb09013 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 18 Oct 2025 08:47:43 +0000
Subject: [PATCH 2/7] Add IAppHostEnvironment interface and replace
configuration access
Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com>
---
src/Aspire.Hosting/AppHostEnvironment.cs | 66 ++++++++++++++
.../Dashboard/DashboardEventHandlers.cs | 7 +-
.../Dashboard/DashboardOptions.cs | 6 +-
.../Dashboard/DashboardService.cs | 7 +-
src/Aspire.Hosting/Dcp/DcpExecutor.cs | 8 +-
src/Aspire.Hosting/Dcp/DcpNameGenerator.cs | 9 +-
.../DistributedApplicationBuilder.cs | 9 ++
.../DistributedApplicationLifecycle.cs | 5 +-
src/Aspire.Hosting/IAppHostEnvironment.cs | 90 +++++++++++++++++++
.../IDistributedApplicationBuilder.cs | 5 ++
.../OtlpConfigurationExtensions.cs | 3 +-
.../ParameterResourceBuilderExtensions.cs | 4 +-
.../ProjectResourceBuilderExtensions.cs | 2 +-
.../Internal/FileDeploymentStateManager.cs | 5 +-
.../ResourceLoggerForwarderService.cs | 4 +-
.../VersionChecking/VersionCheckService.cs | 6 +-
src/Aspire.Hosting/VolumeNameGenerator.cs | 4 +-
17 files changed, 206 insertions(+), 34 deletions(-)
create mode 100644 src/Aspire.Hosting/AppHostEnvironment.cs
create mode 100644 src/Aspire.Hosting/IAppHostEnvironment.cs
diff --git a/src/Aspire.Hosting/AppHostEnvironment.cs b/src/Aspire.Hosting/AppHostEnvironment.cs
new file mode 100644
index 00000000000..1c40c4bed11
--- /dev/null
+++ b/src/Aspire.Hosting/AppHostEnvironment.cs
@@ -0,0 +1,66 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+
+namespace Aspire.Hosting;
+
+///
+/// Provides information about the AppHost environment.
+///
+internal sealed class AppHostEnvironment : IAppHostEnvironment
+{
+ private readonly IConfiguration _configuration;
+ private readonly IHostEnvironment _hostEnvironment;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration.
+ /// The host environment.
+ public AppHostEnvironment(IConfiguration configuration, IHostEnvironment hostEnvironment)
+ {
+ _configuration = configuration;
+ _hostEnvironment = hostEnvironment;
+ }
+
+ ///
+ public string ProjectName => _configuration["AppHost:DashboardApplicationName"] ?? _hostEnvironment.ApplicationName;
+
+ ///
+ public string Directory => _configuration["AppHost:Directory"]!;
+
+ ///
+ public string Path => _configuration["AppHost:Path"]!;
+
+ ///
+ public string DashboardApplicationName => _configuration["AppHost:DashboardApplicationName"] ?? _hostEnvironment.ApplicationName;
+
+ ///
+ public string Sha256 => _configuration["AppHost:Sha256"]!;
+
+ ///
+ public string PathSha256 => _configuration["AppHost:PathSha256"]!;
+
+ ///
+ public string ProjectNameSha256 => _configuration["AppHost:ProjectNameSha256"]!;
+
+ ///
+ public string? ContainerHostname => _configuration["AppHost:ContainerHostname"];
+
+ ///
+ public string? DefaultLaunchProfileName => _configuration["AppHost:DefaultLaunchProfileName"];
+
+ ///
+ public string? OtlpApiKey => _configuration["AppHost:OtlpApiKey"];
+
+ ///
+ public string? BrowserToken => _configuration["AppHost:BrowserToken"];
+
+ ///
+ public string? ResourceServiceApiKey => _configuration["AppHost:ResourceService:ApiKey"];
+
+ ///
+ public string? ResourceServiceAuthMode => _configuration["AppHost:ResourceService:AuthMode"];
+}
diff --git a/src/Aspire.Hosting/Dashboard/DashboardEventHandlers.cs b/src/Aspire.Hosting/Dashboard/DashboardEventHandlers.cs
index ff229233c88..5dae59afa62 100644
--- a/src/Aspire.Hosting/Dashboard/DashboardEventHandlers.cs
+++ b/src/Aspire.Hosting/Dashboard/DashboardEventHandlers.cs
@@ -26,7 +26,8 @@
namespace Aspire.Hosting.Dashboard;
-internal sealed class DashboardEventHandlers(IConfiguration configuration,
+internal sealed class DashboardEventHandlers(IAppHostEnvironment appHostEnvironment,
+ IConfiguration configuration,
IOptions dashboardOptions,
ILogger distributedApplicationLogger,
IDashboardEndpointProvider dashboardEndpointProvider,
@@ -525,8 +526,8 @@ internal async Task ConfigureEnvironmentVariables(EnvironmentCallbackContext con
}
// Configure resource service API key
- if (string.Equals(configuration["AppHost:ResourceService:AuthMode"], nameof(ResourceServiceAuthMode.ApiKey), StringComparison.OrdinalIgnoreCase)
- && configuration["AppHost:ResourceService:ApiKey"] is { Length: > 0 } resourceServiceApiKey)
+ if (string.Equals(appHostEnvironment.ResourceServiceAuthMode, nameof(ResourceServiceAuthMode.ApiKey), StringComparison.OrdinalIgnoreCase)
+ && appHostEnvironment.ResourceServiceApiKey is { Length: > 0 } resourceServiceApiKey)
{
context.EnvironmentVariables[DashboardConfigNames.ResourceServiceClientAuthModeName.EnvVarName] = nameof(ResourceServiceAuthMode.ApiKey);
context.EnvironmentVariables[DashboardConfigNames.ResourceServiceClientApiKeyName.EnvVarName] = resourceServiceApiKey;
diff --git a/src/Aspire.Hosting/Dashboard/DashboardOptions.cs b/src/Aspire.Hosting/Dashboard/DashboardOptions.cs
index 217b907bf75..4133d14139c 100644
--- a/src/Aspire.Hosting/Dashboard/DashboardOptions.cs
+++ b/src/Aspire.Hosting/Dashboard/DashboardOptions.cs
@@ -19,17 +19,17 @@ internal class DashboardOptions
public bool? TelemetryOptOut { get; set; }
}
-internal class ConfigureDefaultDashboardOptions(IConfiguration configuration, IOptions dcpOptions) : IConfigureOptions
+internal class ConfigureDefaultDashboardOptions(IAppHostEnvironment appHostEnvironment, IOptions dcpOptions, IConfiguration configuration) : IConfigureOptions
{
public void Configure(DashboardOptions options)
{
options.DashboardPath = dcpOptions.Value.DashboardPath;
options.DashboardUrl = configuration[KnownConfigNames.AspNetCoreUrls];
- options.DashboardToken = configuration["AppHost:BrowserToken"];
+ options.DashboardToken = appHostEnvironment.BrowserToken;
options.OtlpGrpcEndpointUrl = configuration.GetString(KnownConfigNames.DashboardOtlpGrpcEndpointUrl, KnownConfigNames.Legacy.DashboardOtlpGrpcEndpointUrl);
options.OtlpHttpEndpointUrl = configuration.GetString(KnownConfigNames.DashboardOtlpHttpEndpointUrl, KnownConfigNames.Legacy.DashboardOtlpHttpEndpointUrl);
- options.OtlpApiKey = configuration["AppHost:OtlpApiKey"];
+ options.OtlpApiKey = appHostEnvironment.OtlpApiKey;
options.AspNetCoreEnvironment = configuration["ASPNETCORE_ENVIRONMENT"] ?? "Production";
diff --git a/src/Aspire.Hosting/Dashboard/DashboardService.cs b/src/Aspire.Hosting/Dashboard/DashboardService.cs
index d3b3faad7bc..733684b39fd 100644
--- a/src/Aspire.Hosting/Dashboard/DashboardService.cs
+++ b/src/Aspire.Hosting/Dashboard/DashboardService.cs
@@ -5,7 +5,6 @@
using Aspire.DashboardService.Proto.V1;
using Grpc.Core;
using Microsoft.AspNetCore.Authorization;
-using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using static Aspire.Hosting.Interaction;
@@ -20,7 +19,7 @@ namespace Aspire.Hosting.Dashboard;
/// required beyond a single request. Longer-scoped data is stored in .
///
[Authorize(Policy = ResourceServiceApiKeyAuthorization.PolicyName)]
-internal sealed partial class DashboardService(DashboardServiceData serviceData, IHostEnvironment hostEnvironment, IHostApplicationLifetime hostApplicationLifetime, IConfiguration configuration, ILogger logger)
+internal sealed partial class DashboardService(DashboardServiceData serviceData, IAppHostEnvironment appHostEnvironment, IHostApplicationLifetime hostApplicationLifetime, ILogger logger)
: Aspire.DashboardService.Proto.V1.DashboardService.DashboardServiceBase
{
// gRPC has a maximum receive size of 4MB. Force logs into batches to avoid exceeding receive size.
@@ -38,8 +37,8 @@ public override Task GetApplicationInformation(
ApplicationInformationRequest request,
ServerCallContext context)
{
- // Read the application name from configuration if available, otherwise fall back to the environment
- var applicationName = configuration["AppHost:DashboardApplicationName"] ?? hostEnvironment.ApplicationName;
+ // Use the dashboard application name from the AppHostEnvironment
+ var applicationName = appHostEnvironment.DashboardApplicationName;
return Task.FromResult(new ApplicationInformationResponse
{
diff --git a/src/Aspire.Hosting/Dcp/DcpExecutor.cs b/src/Aspire.Hosting/Dcp/DcpExecutor.cs
index 54326438ecc..b6e93821c13 100644
--- a/src/Aspire.Hosting/Dcp/DcpExecutor.cs
+++ b/src/Aspire.Hosting/Dcp/DcpExecutor.cs
@@ -93,11 +93,12 @@ internal sealed partial class DcpExecutor : IDcpExecutor, IConsoleLogsService, I
private readonly record struct LogInformationEntry(string ResourceName, bool? LogsAvailable, bool? HasSubscribers);
private readonly Channel _logInformationChannel = Channel.CreateUnbounded(
new UnboundedChannelOptions { SingleReader = true });
+ private readonly IAppHostEnvironment _appHostEnvironment;
public DcpExecutor(ILogger logger,
ILogger distributedApplicationLogger,
DistributedApplicationModel model,
- IHostEnvironment hostEnvironment,
+ IAppHostEnvironment appHostEnvironment,
IKubernetesService kubernetesService,
IConfiguration configuration,
IDistributedApplicationEventing distributedApplicationEventing,
@@ -126,16 +127,17 @@ public DcpExecutor(ILogger logger,
_executionContext = executionContext;
_resourceState = new(model.Resources.ToDictionary(r => r.Name), _appResources);
_snapshotBuilder = new(_resourceState);
- _normalizedApplicationName = NormalizeApplicationName(hostEnvironment.ApplicationName);
+ _normalizedApplicationName = NormalizeApplicationName(appHostEnvironment.ProjectName);
_locations = locations;
_developerCertificateService = developerCertificateService;
+ _appHostEnvironment = appHostEnvironment;
DeleteResourceRetryPipeline = DcpPipelineBuilder.BuildDeleteRetryPipeline(logger);
CreateServiceRetryPipeline = DcpPipelineBuilder.BuildCreateServiceRetryPipeline(options.Value, logger);
WatchResourceRetryPipeline = DcpPipelineBuilder.BuildWatchResourcePipeline(logger);
}
- private string DefaultContainerHostName => _configuration["AppHost:ContainerHostname"] ?? _dcpInfo?.Containers?.ContainerHostName ?? "host.docker.internal";
+ private string DefaultContainerHostName => _appHostEnvironment.ContainerHostname ?? _dcpInfo?.Containers?.ContainerHostName ?? "host.docker.internal";
public async Task RunApplicationAsync(CancellationToken cancellationToken = default)
{
diff --git a/src/Aspire.Hosting/Dcp/DcpNameGenerator.cs b/src/Aspire.Hosting/Dcp/DcpNameGenerator.cs
index d810e9e662e..7e6faf47c74 100644
--- a/src/Aspire.Hosting/Dcp/DcpNameGenerator.cs
+++ b/src/Aspire.Hosting/Dcp/DcpNameGenerator.cs
@@ -4,7 +4,6 @@
using System.Collections.Immutable;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Utils;
-using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
namespace Aspire.Hosting.Dcp;
@@ -16,12 +15,12 @@ internal sealed class DcpNameGenerator
// The length of 8 achieves that while keeping the names relatively short and readable.
// The second purpose of the suffix is to play a role of a unique OpenTelemetry service instance ID.
private const int RandomNameSuffixLength = 8;
- private readonly IConfiguration _configuration;
+ private readonly IAppHostEnvironment _appHostEnvironment;
private readonly IOptions _options;
- public DcpNameGenerator(IConfiguration configuration, IOptions options)
+ public DcpNameGenerator(IAppHostEnvironment appHostEnvironment, IOptions options)
{
- _configuration = configuration;
+ _appHostEnvironment = appHostEnvironment;
_options = options;
}
@@ -115,7 +114,7 @@ public static string GetRandomNameSuffix()
public string GetProjectHashSuffix()
{
// Compute a short hash of the content root path to differentiate between multiple AppHost projects with similar resource names
- var suffix = _configuration["AppHost:Sha256"]!.Substring(0, RandomNameSuffixLength).ToLowerInvariant();
+ var suffix = _appHostEnvironment.Sha256.Substring(0, RandomNameSuffixLength).ToLowerInvariant();
return suffix;
}
diff --git a/src/Aspire.Hosting/DistributedApplicationBuilder.cs b/src/Aspire.Hosting/DistributedApplicationBuilder.cs
index 9edcb5ff193..a2adfbc9d45 100644
--- a/src/Aspire.Hosting/DistributedApplicationBuilder.cs
+++ b/src/Aspire.Hosting/DistributedApplicationBuilder.cs
@@ -60,10 +60,14 @@ public class DistributedApplicationBuilder : IDistributedApplicationBuilder
private readonly DistributedApplicationOptions _options;
private readonly HostApplicationBuilder _innerBuilder;
+ private readonly AppHostEnvironment _appHostEnvironment;
///
public IHostEnvironment Environment => _innerBuilder.Environment;
+ ///
+ public IAppHostEnvironment AppHostEnvironment => _appHostEnvironment;
+
///
public ConfigurationManager Configuration => _innerBuilder.Configuration;
@@ -466,6 +470,11 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options)
}
_innerBuilder.Services.AddSingleton(ExecutionContext);
+ _innerBuilder.Services.AddSingleton();
+
+ // Initialize the AppHostEnvironment for use within the builder
+ _appHostEnvironment = new AppHostEnvironment(_innerBuilder.Configuration, _innerBuilder.Environment);
+
LogBuilderConstructed(this);
}
diff --git a/src/Aspire.Hosting/DistributedApplicationLifecycle.cs b/src/Aspire.Hosting/DistributedApplicationLifecycle.cs
index c2b1c24d77a..7e013629bbb 100644
--- a/src/Aspire.Hosting/DistributedApplicationLifecycle.cs
+++ b/src/Aspire.Hosting/DistributedApplicationLifecycle.cs
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Reflection;
-using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@@ -10,7 +9,7 @@ namespace Aspire.Hosting;
internal sealed class DistributedApplicationLifecycle(
ILogger logger,
- IConfiguration configuration,
+ IAppHostEnvironment appHostEnvironment,
DistributedApplicationExecutionContext executionContext,
LocaleOverrideContext localeOverrideContext) : IHostedLifecycleService
{
@@ -41,7 +40,7 @@ public Task StartingAsync(CancellationToken cancellationToken)
if (executionContext.IsRunMode)
{
logger.LogInformation("Distributed application starting.");
- logger.LogInformation("Application host directory is: {AppHostDirectory}", configuration["AppHost:Directory"]);
+ logger.LogInformation("Application host directory is: {AppHostDirectory}", appHostEnvironment.Directory);
}
if (localeOverrideContext.OverrideErrorMessage is { Length: > 0 } localOverrideError)
diff --git a/src/Aspire.Hosting/IAppHostEnvironment.cs b/src/Aspire.Hosting/IAppHostEnvironment.cs
new file mode 100644
index 00000000000..a49617384ab
--- /dev/null
+++ b/src/Aspire.Hosting/IAppHostEnvironment.cs
@@ -0,0 +1,90 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Aspire.Hosting;
+
+///
+/// Provides information about the AppHost environment.
+///
+public interface IAppHostEnvironment
+{
+ ///
+ /// Gets the name of the AppHost project.
+ ///
+ ///
+ /// This is the project name used in multiple places throughout the application,
+ /// including for generating resource names and configuration keys.
+ ///
+ string ProjectName { get; }
+
+ ///
+ /// Gets the directory of the project where the app host is located.
+ ///
+ string Directory { get; }
+
+ ///
+ /// Gets the full path to the app host.
+ ///
+ string Path { get; }
+
+ ///
+ /// Gets the application name used for the dashboard.
+ ///
+ string DashboardApplicationName { get; }
+
+ ///
+ /// Gets the SHA256 hash of the app host.
+ ///
+ ///
+ /// For backward compatibility, this uses mode-dependent logic:
+ /// - Publish mode: ProjectNameSha (stable across paths)
+ /// - Run mode: PathSha (disambiguates by path)
+ ///
+ string Sha256 { get; }
+
+ ///
+ /// Gets the SHA256 hash based on the app host path.
+ ///
+ ///
+ /// Used for disambiguating projects with the same name in different locations (deployment state).
+ ///
+ string PathSha256 { get; }
+
+ ///
+ /// Gets the SHA256 hash based on the project name.
+ ///
+ ///
+ /// Used for stable naming across deployments regardless of path (Azure Functions, Azure environments).
+ ///
+ string ProjectNameSha256 { get; }
+
+ ///
+ /// Gets the container hostname.
+ ///
+ string? ContainerHostname { get; }
+
+ ///
+ /// Gets the default launch profile name.
+ ///
+ string? DefaultLaunchProfileName { get; }
+
+ ///
+ /// Gets the OTLP API key.
+ ///
+ string? OtlpApiKey { get; }
+
+ ///
+ /// Gets the browser token for the dashboard.
+ ///
+ string? BrowserToken { get; }
+
+ ///
+ /// Gets the resource service API key.
+ ///
+ string? ResourceServiceApiKey { get; }
+
+ ///
+ /// Gets the resource service authentication mode.
+ ///
+ string? ResourceServiceAuthMode { get; }
+}
diff --git a/src/Aspire.Hosting/IDistributedApplicationBuilder.cs b/src/Aspire.Hosting/IDistributedApplicationBuilder.cs
index c3da140e310..44b2f319dde 100644
--- a/src/Aspire.Hosting/IDistributedApplicationBuilder.cs
+++ b/src/Aspire.Hosting/IDistributedApplicationBuilder.cs
@@ -72,6 +72,11 @@ public interface IDistributedApplicationBuilder
///
public IHostEnvironment Environment { get; }
+ ///
+ /// Provides information about the AppHost environment.
+ ///
+ public IAppHostEnvironment AppHostEnvironment { get; }
+
///
public IServiceCollection Services { get; }
diff --git a/src/Aspire.Hosting/OtlpConfigurationExtensions.cs b/src/Aspire.Hosting/OtlpConfigurationExtensions.cs
index 644361bf7fb..6d132e2d4cb 100644
--- a/src/Aspire.Hosting/OtlpConfigurationExtensions.cs
+++ b/src/Aspire.Hosting/OtlpConfigurationExtensions.cs
@@ -80,7 +80,8 @@ private static void RegisterOtlpEnvironment(IResource resource, IConfiguration c
context.EnvironmentVariables["OTEL_RESOURCE_ATTRIBUTES"] = "service.instance.id={{- index .Annotations \"" + CustomResource.OtelServiceInstanceIdAnnotation + "\" -}}";
context.EnvironmentVariables["OTEL_SERVICE_NAME"] = "{{- index .Annotations \"" + CustomResource.OtelServiceNameAnnotation + "\" -}}";
- if (configuration["AppHost:OtlpApiKey"] is { } otlpApiKey)
+ var appHostEnvironment = context.ExecutionContext.ServiceProvider.GetRequiredService();
+ if (appHostEnvironment.OtlpApiKey is { } otlpApiKey)
{
context.EnvironmentVariables["OTEL_EXPORTER_OTLP_HEADERS"] = $"x-otlp-api-key={otlpApiKey}";
}
diff --git a/src/Aspire.Hosting/ParameterResourceBuilderExtensions.cs b/src/Aspire.Hosting/ParameterResourceBuilderExtensions.cs
index 010676c8a9a..1d40c124c3d 100644
--- a/src/Aspire.Hosting/ParameterResourceBuilderExtensions.cs
+++ b/src/Aspire.Hosting/ParameterResourceBuilderExtensions.cs
@@ -139,7 +139,7 @@ public static IResourceBuilder AddParameter(this IDistributed
// If it needs persistence, wrap it in a UserSecretsParameterDefault
if (persist && builder.ExecutionContext.IsRunMode && builder.AppHostAssembly is not null)
{
- value = new UserSecretsParameterDefault(builder.AppHostAssembly, builder.Environment.ApplicationName, name, value);
+ value = new UserSecretsParameterDefault(builder.AppHostAssembly, builder.AppHostEnvironment.ProjectName, name, value);
}
return builder.AddParameter(
@@ -348,7 +348,7 @@ public static ParameterResource CreateGeneratedParameter(
if (builder.ExecutionContext.IsRunMode && builder.AppHostAssembly is not null)
{
- parameterResource.Default = new UserSecretsParameterDefault(builder.AppHostAssembly, builder.Environment.ApplicationName, name, parameterResource.Default);
+ parameterResource.Default = new UserSecretsParameterDefault(builder.AppHostAssembly, builder.AppHostEnvironment.ProjectName, name, parameterResource.Default);
}
return parameterResource;
diff --git a/src/Aspire.Hosting/ProjectResourceBuilderExtensions.cs b/src/Aspire.Hosting/ProjectResourceBuilderExtensions.cs
index 9f9e10ddae5..ec67a7409e4 100644
--- a/src/Aspire.Hosting/ProjectResourceBuilderExtensions.cs
+++ b/src/Aspire.Hosting/ProjectResourceBuilderExtensions.cs
@@ -333,7 +333,7 @@ private static IResourceBuilder WithProjectDefaults(this IResou
}
else
{
- var appHostDefaultLaunchProfileName = builder.ApplicationBuilder.Configuration["AppHost:DefaultLaunchProfileName"]
+ var appHostDefaultLaunchProfileName = builder.ApplicationBuilder.AppHostEnvironment.DefaultLaunchProfileName
?? builder.ApplicationBuilder.Configuration["DOTNET_LAUNCH_PROFILE"];
if (!string.IsNullOrEmpty(appHostDefaultLaunchProfileName))
{
diff --git a/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs b/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs
index 9bdde9a3430..e3bac034340 100644
--- a/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs
+++ b/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs
@@ -5,7 +5,6 @@
using System.Text.Json;
using System.Text.Json.Nodes;
-using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -17,7 +16,7 @@ namespace Aspire.Hosting.Publishing.Internal;
///
public sealed class FileDeploymentStateManager(
ILogger logger,
- IConfiguration configuration,
+ IAppHostEnvironment appHostEnvironment,
IHostEnvironment hostEnvironment,
IOptions publishingOptions) : IDeploymentStateManager
{
@@ -32,7 +31,7 @@ public sealed class FileDeploymentStateManager(
private string? GetDeploymentStatePath()
{
// Use PathSha256 for deployment state to disambiguate projects with the same name in different locations
- var appHostSha = configuration["AppHost:PathSha256"];
+ var appHostSha = appHostEnvironment.PathSha256;
if (string.IsNullOrEmpty(appHostSha))
{
return null;
diff --git a/src/Aspire.Hosting/ResourceLoggerForwarderService.cs b/src/Aspire.Hosting/ResourceLoggerForwarderService.cs
index 1ece60c8375..e03e02804d1 100644
--- a/src/Aspire.Hosting/ResourceLoggerForwarderService.cs
+++ b/src/Aspire.Hosting/ResourceLoggerForwarderService.cs
@@ -15,7 +15,7 @@ namespace Aspire.Hosting;
internal sealed class ResourceLoggerForwarderService(
ResourceNotificationService resourceNotificationService,
ResourceLoggerService resourceLoggerService,
- IHostEnvironment hostEnvironment,
+ IAppHostEnvironment appHostEnvironment,
ILoggerFactory loggerFactory)
: BackgroundService
{
@@ -62,7 +62,7 @@ private async Task WatchResourceLogs(IResource resource, string resourceId, Canc
{
try
{
- var applicationName = hostEnvironment.ApplicationName;
+ var applicationName = appHostEnvironment.ProjectName;
var logger = loggerFactory.CreateLogger($"{applicationName}.Resources.{resource.Name}");
await foreach (var logEvent in resourceLoggerService.WatchAsync(resourceId).WithCancellation(cancellationToken).ConfigureAwait(false))
{
diff --git a/src/Aspire.Hosting/VersionChecking/VersionCheckService.cs b/src/Aspire.Hosting/VersionChecking/VersionCheckService.cs
index 299111a7185..29aa86eaccc 100644
--- a/src/Aspire.Hosting/VersionChecking/VersionCheckService.cs
+++ b/src/Aspire.Hosting/VersionChecking/VersionCheckService.cs
@@ -27,6 +27,7 @@ internal sealed class VersionCheckService : BackgroundService
private readonly IInteractionService _interactionService;
private readonly ILogger _logger;
private readonly IConfiguration _configuration;
+ private readonly IAppHostEnvironment _appHostEnvironment;
private readonly DistributedApplicationOptions _options;
private readonly IPackageFetcher _packageFetcher;
private readonly DistributedApplicationExecutionContext _executionContext;
@@ -34,12 +35,13 @@ internal sealed class VersionCheckService : BackgroundService
private readonly SemVersion? _appHostVersion;
public VersionCheckService(IInteractionService interactionService, ILogger logger,
- IConfiguration configuration, DistributedApplicationOptions options, IPackageFetcher packageFetcher,
+ IConfiguration configuration, IAppHostEnvironment appHostEnvironment, DistributedApplicationOptions options, IPackageFetcher packageFetcher,
DistributedApplicationExecutionContext executionContext, TimeProvider timeProvider, IPackageVersionProvider packageVersionProvider)
{
_interactionService = interactionService;
_logger = logger;
_configuration = configuration;
+ _appHostEnvironment = appHostEnvironment;
_options = options;
_packageFetcher = packageFetcher;
_executionContext = executionContext;
@@ -97,7 +99,7 @@ private async Task CheckForLatestAsync(CancellationToken cancellationToken)
SemVersion? storedKnownLatestVersion = null;
if (checkForLatestVersion)
{
- var appHostDirectory = _configuration["AppHost:Directory"]!;
+ var appHostDirectory = _appHostEnvironment.Directory;
SecretsStore.TrySetUserSecret(_options.Assembly, LastCheckDateKey, now.ToString("o", CultureInfo.InvariantCulture));
packages = await _packageFetcher.TryFetchPackagesAsync(appHostDirectory, cancellationToken).ConfigureAwait(false);
diff --git a/src/Aspire.Hosting/VolumeNameGenerator.cs b/src/Aspire.Hosting/VolumeNameGenerator.cs
index 8b62f2c5ea5..3b2d5db357e 100644
--- a/src/Aspire.Hosting/VolumeNameGenerator.cs
+++ b/src/Aspire.Hosting/VolumeNameGenerator.cs
@@ -28,8 +28,8 @@ public static string Generate(IResourceBuilder builder, string suffix) whe
// Create volume name like "{Sanitize(appname).Lower()}-{sha256.Lower()}-postgres-data"
// Compute a short hash of the content root path to differentiate between multiple AppHost projects with similar volume names
- var safeApplicationName = Sanitize(builder.ApplicationBuilder.Environment.ApplicationName).ToLowerInvariant();
- var applicationHash = builder.ApplicationBuilder.Configuration["AppHost:Sha256"]![..10].ToLowerInvariant();
+ var safeApplicationName = Sanitize(builder.ApplicationBuilder.AppHostEnvironment.ProjectName).ToLowerInvariant();
+ var applicationHash = builder.ApplicationBuilder.AppHostEnvironment.Sha256[..10].ToLowerInvariant();
var resourceName = builder.Resource.Name;
return $"{safeApplicationName}-{applicationHash}-{resourceName}-{suffix}";
}
From 532ae4ecdf5503da001328613209098904c1d781 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 18 Oct 2025 09:03:40 +0000
Subject: [PATCH 3/7] Add tests for IAppHostEnvironment and fix test files
Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com>
---
.../DistributedApplicationTestingBuilder.cs | 4 +
src/Aspire.Hosting/Dcp/DcpExecutor.cs | 1 +
.../Dashboard/DashboardLifecycleHookTests.cs | 3 +-
.../Dashboard/DashboardServiceTests.cs | 25 +++---
.../Dcp/DcpExecutorTests.cs | 6 +-
.../DistributedApplicationBuilderTests.cs | 84 +++++++++++++++++++
.../Utils/TestAppHostEnvironment.cs | 33 ++++++++
.../VersionCheckServiceTests.cs | 5 +-
8 files changed, 143 insertions(+), 18 deletions(-)
create mode 100644 tests/Aspire.Hosting.Tests/Utils/TestAppHostEnvironment.cs
diff --git a/src/Aspire.Hosting.Testing/DistributedApplicationTestingBuilder.cs b/src/Aspire.Hosting.Testing/DistributedApplicationTestingBuilder.cs
index 97ba3575ba2..470e5c48b4a 100644
--- a/src/Aspire.Hosting.Testing/DistributedApplicationTestingBuilder.cs
+++ b/src/Aspire.Hosting.Testing/DistributedApplicationTestingBuilder.cs
@@ -234,6 +234,8 @@ private sealed class Builder(SuspendingDistributedApplicationFactory factory, Di
public IHostEnvironment Environment => innerBuilder.Environment;
+ public IAppHostEnvironment AppHostEnvironment => innerBuilder.AppHostEnvironment;
+
public IServiceCollection Services => innerBuilder.Services;
public DistributedApplicationExecutionContext ExecutionContext => innerBuilder.ExecutionContext;
@@ -386,6 +388,8 @@ static Assembly FindApplicationAssembly()
public IHostEnvironment Environment => _innerBuilder.Environment;
+ public IAppHostEnvironment AppHostEnvironment => _innerBuilder.AppHostEnvironment;
+
public IServiceCollection Services => _innerBuilder.Services;
public DistributedApplicationExecutionContext ExecutionContext => _innerBuilder.ExecutionContext;
diff --git a/src/Aspire.Hosting/Dcp/DcpExecutor.cs b/src/Aspire.Hosting/Dcp/DcpExecutor.cs
index b6e93821c13..3af29b0ee46 100644
--- a/src/Aspire.Hosting/Dcp/DcpExecutor.cs
+++ b/src/Aspire.Hosting/Dcp/DcpExecutor.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
#pragma warning disable ASPIREEXTENSION001
+#pragma warning disable IDE0005 // IConfiguration is used in this file
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Data;
diff --git a/tests/Aspire.Hosting.Tests/Dashboard/DashboardLifecycleHookTests.cs b/tests/Aspire.Hosting.Tests/Dashboard/DashboardLifecycleHookTests.cs
index 0aec7b47796..f732546be6e 100644
--- a/tests/Aspire.Hosting.Tests/Dashboard/DashboardLifecycleHookTests.cs
+++ b/tests/Aspire.Hosting.Tests/Dashboard/DashboardLifecycleHookTests.cs
@@ -505,6 +505,7 @@ private static DashboardEventHandlers CreateHook(
var rewriter = new CodespacesUrlRewriter(codespacesOptions);
return new DashboardEventHandlers(
+ new TestAppHostEnvironment(configuration),
configuration,
dashboardOptions,
NullLogger.Instance,
@@ -513,7 +514,7 @@ private static DashboardEventHandlers CreateHook(
resourceNotificationService,
resourceLoggerService,
loggerFactory ?? NullLoggerFactory.Instance,
- new DcpNameGenerator(configuration, Options.Create(new DcpOptions())),
+ new DcpNameGenerator(new TestAppHostEnvironment(configuration), Options.Create(new DcpOptions())),
new TestHostApplicationLifetime(),
new Hosting.Eventing.DistributedApplicationEventing(),
rewriter
diff --git a/tests/Aspire.Hosting.Tests/Dashboard/DashboardServiceTests.cs b/tests/Aspire.Hosting.Tests/Dashboard/DashboardServiceTests.cs
index 6f27e61cc3d..81b2bfa3a59 100644
--- a/tests/Aspire.Hosting.Tests/Dashboard/DashboardServiceTests.cs
+++ b/tests/Aspire.Hosting.Tests/Dashboard/DashboardServiceTests.cs
@@ -40,7 +40,7 @@ public async Task WatchResourceConsoleLogs_NoFollow_ResultsEnd()
var resourceNotificationService = CreateResourceNotificationService(resourceLoggerService);
var dashboardServiceData = CreateDashboardServiceData(resourceLoggerService: resourceLoggerService, resourceNotificationService: resourceNotificationService);
- var dashboardService = new DashboardServiceImpl(dashboardServiceData, new TestHostEnvironment(), new TestHostApplicationLifetime(), new ConfigurationBuilder().Build(), NullLogger.Instance);
+ var dashboardService = new DashboardServiceImpl(dashboardServiceData, new TestAppHostEnvironment(), new TestHostApplicationLifetime(), NullLogger.Instance);
var logger = resourceLoggerService.GetLogger("test-resource");
@@ -93,7 +93,7 @@ public async Task WatchResourceConsoleLogs_LargePendingData_BatchResults()
var resourceLoggerService = new ResourceLoggerService();
var resourceNotificationService = CreateResourceNotificationService(resourceLoggerService);
var dashboardServiceData = CreateDashboardServiceData(resourceLoggerService: resourceLoggerService, resourceNotificationService: resourceNotificationService);
- var dashboardService = new DashboardServiceImpl(dashboardServiceData, new TestHostEnvironment(), new TestHostApplicationLifetime(), new ConfigurationBuilder().Build(), NullLogger.Instance);
+ var dashboardService = new DashboardServiceImpl(dashboardServiceData, new TestAppHostEnvironment(), new TestHostApplicationLifetime(), NullLogger.Instance);
var logger = resourceLoggerService.GetLogger("test-resource");
@@ -145,7 +145,7 @@ public async Task WatchResources_ResourceHasCommands_CommandsSentWithResponse()
var resourceLoggerService = new ResourceLoggerService();
var resourceNotificationService = CreateResourceNotificationService(resourceLoggerService);
using var dashboardServiceData = CreateDashboardServiceData(loggerFactory: loggerFactory, resourceLoggerService: resourceLoggerService, resourceNotificationService: resourceNotificationService);
- var dashboardService = new DashboardServiceImpl(dashboardServiceData, new TestHostEnvironment(), new TestHostApplicationLifetime(), new ConfigurationBuilder().Build(), loggerFactory.CreateLogger());
+ var dashboardService = new DashboardServiceImpl(dashboardServiceData, new TestAppHostEnvironment(), new TestHostApplicationLifetime(), loggerFactory.CreateLogger());
var testResource = new TestResource("test-resource");
using var applicationBuilder = TestDistributedApplicationBuilder.Create(testOutputHelper: testOutputHelper);
@@ -230,7 +230,7 @@ public async Task WatchInteractions_PromptMessageBoxAsync_CompleteOnResponse(boo
new ServiceCollection().BuildServiceProvider(),
new Microsoft.Extensions.Configuration.ConfigurationBuilder().Build());
using var dashboardServiceData = CreateDashboardServiceData(loggerFactory: loggerFactory, interactionService: interactionService);
- var dashboardService = new DashboardServiceImpl(dashboardServiceData, new TestHostEnvironment(), new TestHostApplicationLifetime(), new ConfigurationBuilder().Build(), loggerFactory.CreateLogger());
+ var dashboardService = new DashboardServiceImpl(dashboardServiceData, new TestAppHostEnvironment(), new TestHostApplicationLifetime(), loggerFactory.CreateLogger());
var cts = new CancellationTokenSource();
var context = TestServerCallContext.Create(cancellationToken: cts.Token);
@@ -300,7 +300,7 @@ public async Task WatchInteractions_NoExplicitLabel_LabelIsName()
new ServiceCollection().BuildServiceProvider(),
new Microsoft.Extensions.Configuration.ConfigurationBuilder().Build());
using var dashboardServiceData = CreateDashboardServiceData(loggerFactory: loggerFactory, interactionService: interactionService);
- var dashboardService = new DashboardServiceImpl(dashboardServiceData, new TestHostEnvironment(), new TestHostApplicationLifetime(), new ConfigurationBuilder().Build(), loggerFactory.CreateLogger());
+ var dashboardService = new DashboardServiceImpl(dashboardServiceData, new TestAppHostEnvironment(), new TestHostApplicationLifetime(), loggerFactory.CreateLogger());
var cts = new CancellationTokenSource();
var context = TestServerCallContext.Create(cancellationToken: cts.Token);
@@ -347,7 +347,7 @@ public async Task WatchInteractions_PromptInputAsync_CompleteOnCancelResponse()
new ServiceCollection().BuildServiceProvider(),
new Microsoft.Extensions.Configuration.ConfigurationBuilder().Build());
using var dashboardServiceData = CreateDashboardServiceData(loggerFactory: loggerFactory, interactionService: interactionService);
- var dashboardService = new DashboardServiceImpl(dashboardServiceData, new TestHostEnvironment(), new TestHostApplicationLifetime(), new ConfigurationBuilder().Build(), loggerFactory.CreateLogger());
+ var dashboardService = new DashboardServiceImpl(dashboardServiceData, new TestAppHostEnvironment(), new TestHostApplicationLifetime(), loggerFactory.CreateLogger());
var cts = new CancellationTokenSource();
var context = TestServerCallContext.Create(cancellationToken: cts.Token);
@@ -406,7 +406,7 @@ public async Task WatchInteractions_ReaderError_CompleteWithError()
new ServiceCollection().BuildServiceProvider(),
new Microsoft.Extensions.Configuration.ConfigurationBuilder().Build());
using var dashboardServiceData = CreateDashboardServiceData(loggerFactory: loggerFactory, interactionService: interactionService);
- var dashboardService = new DashboardServiceImpl(dashboardServiceData, new TestHostEnvironment(), new TestHostApplicationLifetime(), new ConfigurationBuilder().Build(), loggerFactory.CreateLogger());
+ var dashboardService = new DashboardServiceImpl(dashboardServiceData, new TestAppHostEnvironment(), new TestHostApplicationLifetime(), loggerFactory.CreateLogger());
var cts = new CancellationTokenSource();
var context = TestServerCallContext.Create(cancellationToken: cts.Token);
@@ -443,7 +443,7 @@ public async Task WatchInteractions_WriterError_CompleteWithError()
new ServiceCollection().BuildServiceProvider(),
new Microsoft.Extensions.Configuration.ConfigurationBuilder().Build());
using var dashboardServiceData = CreateDashboardServiceData(loggerFactory: loggerFactory, interactionService: interactionService);
- var dashboardService = new DashboardServiceImpl(dashboardServiceData, new TestHostEnvironment(), new TestHostApplicationLifetime(), new ConfigurationBuilder().Build(), loggerFactory.CreateLogger());
+ var dashboardService = new DashboardServiceImpl(dashboardServiceData, new TestAppHostEnvironment(), new TestHostApplicationLifetime(), loggerFactory.CreateLogger());
var cts = new CancellationTokenSource();
var context = TestServerCallContext.Create(cancellationToken: cts.Token);
@@ -500,9 +500,8 @@ public async Task GetApplicationInformation_ReadsFromConfiguration()
};
var dashboardService = new DashboardServiceImpl(
dashboardServiceData,
- hostEnvironment,
+ new TestAppHostEnvironment(configuration, hostEnvironment),
new TestHostApplicationLifetime(),
- configuration,
NullLogger.Instance);
var context = TestServerCallContext.Create();
@@ -529,9 +528,8 @@ public async Task GetApplicationInformation_FallsBackToEnvironmentApplicationNam
};
var dashboardService = new DashboardServiceImpl(
dashboardServiceData,
- hostEnvironment,
+ new TestAppHostEnvironment(configuration, hostEnvironment),
new TestHostApplicationLifetime(),
- configuration,
NullLogger.Instance);
var context = TestServerCallContext.Create();
@@ -559,9 +557,8 @@ public async Task GetApplicationInformation_StripsAppHostSuffix()
var dashboardServiceData = CreateDashboardServiceData();
var dashboardService = new DashboardServiceImpl(
dashboardServiceData,
- new TestHostEnvironment(),
+ new TestAppHostEnvironment(configuration),
new TestHostApplicationLifetime(),
- configuration,
NullLogger.Instance);
var context = TestServerCallContext.Create();
diff --git a/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs b/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs
index 426057108ec..69732ef4aa0 100644
--- a/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs
+++ b/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs
@@ -2014,12 +2014,14 @@ private static DcpExecutor CreateAppExecutor(
resourceLoggerService ??= new ResourceLoggerService();
dcpOptions ??= new DcpOptions { DashboardPath = "./dashboard" };
+ var testHostEnvironment = hostEnvironment ?? new TestHostEnvironment();
+ var testAppHostEnvironment = new TestAppHostEnvironment(configuration, testHostEnvironment);
return new DcpExecutor(
NullLogger.Instance,
NullLogger.Instance,
distributedAppModel,
- hostEnvironment ?? new TestHostEnvironment(),
+ testAppHostEnvironment,
kubernetesService ?? new TestKubernetesService(),
configuration,
new Hosting.Eventing.DistributedApplicationEventing(),
@@ -2031,7 +2033,7 @@ private static DcpExecutor CreateAppExecutor(
}),
resourceLoggerService,
new TestDcpDependencyCheckService(),
- new DcpNameGenerator(configuration, Options.Create(dcpOptions)),
+ new DcpNameGenerator(testAppHostEnvironment, Options.Create(dcpOptions)),
events ?? new DcpExecutorEvents(),
new Locations(),
new DeveloperCertificateService(NullLogger.Instance));
diff --git a/tests/Aspire.Hosting.Tests/DistributedApplicationBuilderTests.cs b/tests/Aspire.Hosting.Tests/DistributedApplicationBuilderTests.cs
index 65a47bd56e5..c0b90c7f6f3 100644
--- a/tests/Aspire.Hosting.Tests/DistributedApplicationBuilderTests.cs
+++ b/tests/Aspire.Hosting.Tests/DistributedApplicationBuilderTests.cs
@@ -240,6 +240,90 @@ public void LegacyShaUsesProjectNameShaInPublishMode()
Assert.Equal(projectNameSha, legacySha);
}
+ [Fact]
+ public void AppHostEnvironmentIsAvailableFromBuilder()
+ {
+ var appBuilder = DistributedApplication.CreateBuilder();
+
+ Assert.NotNull(appBuilder.AppHostEnvironment);
+ Assert.NotNull(appBuilder.AppHostEnvironment.ProjectName);
+ Assert.NotNull(appBuilder.AppHostEnvironment.Directory);
+ Assert.NotNull(appBuilder.AppHostEnvironment.Path);
+ Assert.NotNull(appBuilder.AppHostEnvironment.DashboardApplicationName);
+ Assert.NotNull(appBuilder.AppHostEnvironment.Sha256);
+ Assert.NotNull(appBuilder.AppHostEnvironment.PathSha256);
+ Assert.NotNull(appBuilder.AppHostEnvironment.ProjectNameSha256);
+ }
+
+ [Fact]
+ public void AppHostEnvironmentIsAvailableFromDI()
+ {
+ var appBuilder = DistributedApplication.CreateBuilder();
+ using var app = appBuilder.Build();
+
+ var appHostEnvironment = app.Services.GetRequiredService();
+ Assert.NotNull(appHostEnvironment);
+ Assert.NotNull(appHostEnvironment.ProjectName);
+ Assert.NotNull(appHostEnvironment.Directory);
+ Assert.NotNull(appHostEnvironment.Path);
+ }
+
+ [Fact]
+ public void AppHostEnvironmentProjectNameMatchesConfiguration()
+ {
+ var appBuilder = DistributedApplication.CreateBuilder();
+ using var app = appBuilder.Build();
+
+ var appHostEnvironment = app.Services.GetRequiredService();
+ var config = app.Services.GetRequiredService();
+
+ var configDashboardAppName = config["AppHost:DashboardApplicationName"];
+ Assert.Equal(configDashboardAppName, appHostEnvironment.ProjectName);
+ Assert.Equal(configDashboardAppName, appHostEnvironment.DashboardApplicationName);
+ }
+
+ [Fact]
+ public void AppHostEnvironmentDirectoryMatchesConfiguration()
+ {
+ var appBuilder = DistributedApplication.CreateBuilder();
+ using var app = appBuilder.Build();
+
+ var appHostEnvironment = app.Services.GetRequiredService();
+ var config = app.Services.GetRequiredService();
+
+ Assert.Equal(config["AppHost:Directory"], appHostEnvironment.Directory);
+ Assert.Equal(config["AppHost:Path"], appHostEnvironment.Path);
+ }
+
+ [Fact]
+ public void AppHostEnvironmentSha256MatchesConfiguration()
+ {
+ var appBuilder = DistributedApplication.CreateBuilder();
+ using var app = appBuilder.Build();
+
+ var appHostEnvironment = app.Services.GetRequiredService();
+ var config = app.Services.GetRequiredService();
+
+ Assert.Equal(config["AppHost:Sha256"], appHostEnvironment.Sha256);
+ Assert.Equal(config["AppHost:PathSha256"], appHostEnvironment.PathSha256);
+ Assert.Equal(config["AppHost:ProjectNameSha256"], appHostEnvironment.ProjectNameSha256);
+ }
+
+ [Fact]
+ public void AppHostEnvironmentSecurityConfigMatchesConfiguration()
+ {
+ var appBuilder = DistributedApplication.CreateBuilder();
+ using var app = appBuilder.Build();
+
+ var appHostEnvironment = app.Services.GetRequiredService();
+ var config = app.Services.GetRequiredService();
+
+ Assert.Equal(config["AppHost:OtlpApiKey"], appHostEnvironment.OtlpApiKey);
+ Assert.Equal(config["AppHost:BrowserToken"], appHostEnvironment.BrowserToken);
+ Assert.Equal(config["AppHost:ResourceService:ApiKey"], appHostEnvironment.ResourceServiceApiKey);
+ Assert.Equal(config["AppHost:ResourceService:AuthMode"], appHostEnvironment.ResourceServiceAuthMode);
+ }
+
private sealed class TestResource : IResource
{
public string Name => nameof(TestResource);
diff --git a/tests/Aspire.Hosting.Tests/Utils/TestAppHostEnvironment.cs b/tests/Aspire.Hosting.Tests/Utils/TestAppHostEnvironment.cs
new file mode 100644
index 00000000000..a8c460be141
--- /dev/null
+++ b/tests/Aspire.Hosting.Tests/Utils/TestAppHostEnvironment.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+
+namespace Aspire.Hosting.Tests.Utils;
+
+internal sealed class TestAppHostEnvironment : IAppHostEnvironment
+{
+ private readonly IConfiguration? _configuration;
+ private readonly IHostEnvironment? _hostEnvironment;
+
+ public TestAppHostEnvironment(IConfiguration? configuration = null, IHostEnvironment? hostEnvironment = null)
+ {
+ _configuration = configuration;
+ _hostEnvironment = hostEnvironment;
+ }
+
+ public string ProjectName => _configuration?["AppHost:DashboardApplicationName"] ?? _hostEnvironment?.ApplicationName ?? "TestApp";
+ public string Directory => _configuration?["AppHost:Directory"] ?? "/test";
+ public string Path => _configuration?["AppHost:Path"] ?? "/test/TestApp";
+ public string DashboardApplicationName => _configuration?["AppHost:DashboardApplicationName"] ?? _hostEnvironment?.ApplicationName ?? "TestApp";
+ public string Sha256 => _configuration?["AppHost:Sha256"] ?? "0000000000000000000000000000000000000000000000000000000000000000";
+ public string PathSha256 => _configuration?["AppHost:PathSha256"] ?? "0000000000000000000000000000000000000000000000000000000000000000";
+ public string ProjectNameSha256 => _configuration?["AppHost:ProjectNameSha256"] ?? "0000000000000000000000000000000000000000000000000000000000000000";
+ public string? ContainerHostname => _configuration?["AppHost:ContainerHostname"];
+ public string? DefaultLaunchProfileName => _configuration?["AppHost:DefaultLaunchProfileName"];
+ public string? OtlpApiKey => _configuration?["AppHost:OtlpApiKey"];
+ public string? BrowserToken => _configuration?["AppHost:BrowserToken"];
+ public string? ResourceServiceApiKey => _configuration?["AppHost:ResourceService:ApiKey"];
+ public string? ResourceServiceAuthMode => _configuration?["AppHost:ResourceService:AuthMode"];
+}
diff --git a/tests/Aspire.Hosting.Tests/VersionChecking/VersionCheckServiceTests.cs b/tests/Aspire.Hosting.Tests/VersionChecking/VersionCheckServiceTests.cs
index fbfa5abdb7e..f4bffd27dbb 100644
--- a/tests/Aspire.Hosting.Tests/VersionChecking/VersionCheckServiceTests.cs
+++ b/tests/Aspire.Hosting.Tests/VersionChecking/VersionCheckServiceTests.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Globalization;
+using Aspire.Hosting.Tests.Utils;
using Aspire.Hosting.VersionChecking;
using Aspire.Shared;
using Microsoft.AspNetCore.InternalTesting;
@@ -251,10 +252,12 @@ private static VersionCheckService CreateVersionCheckService(
DistributedApplicationOptions? options = null,
IPackageVersionProvider? packageVersionProvider = null)
{
+ var config = configuration ?? new ConfigurationManager();
return new VersionCheckService(
interactionService ?? new TestInteractionService(),
NullLogger.Instance,
- configuration ?? new ConfigurationManager(),
+ config,
+ new TestAppHostEnvironment(config),
options ?? new DistributedApplicationOptions(),
packageFetcher ?? new TestPackageFetcher(),
new DistributedApplicationExecutionContext(new DistributedApplicationOperation()),
From 8e7e8faaff6661490f383f7ced50d4233964b794 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 18 Oct 2025 09:08:47 +0000
Subject: [PATCH 4/7] Fix ResourceLoggerForwarderServiceTests to use
IAppHostEnvironment
Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com>
---
.../ResourceLoggerForwarderServiceTests.cs | 30 +++++++++++++++++--
1 file changed, 28 insertions(+), 2 deletions(-)
diff --git a/tests/Aspire.Hosting.Testing.Tests/ResourceLoggerForwarderServiceTests.cs b/tests/Aspire.Hosting.Testing.Tests/ResourceLoggerForwarderServiceTests.cs
index 2e1084139c8..2a9664ee530 100644
--- a/tests/Aspire.Hosting.Testing.Tests/ResourceLoggerForwarderServiceTests.cs
+++ b/tests/Aspire.Hosting.Testing.Tests/ResourceLoggerForwarderServiceTests.cs
@@ -32,8 +32,9 @@ public async Task ExecuteDoesNotThrowOperationCanceledWhenAppStoppingTokenSignal
var resourceLoggerService = new ResourceLoggerService();
var resourceNotificationService = CreateResourceNotificationService(hostApplicationLifetime, resourceLoggerService);
var hostEnvironment = new HostingEnvironment();
+ var appHostEnvironment = new TestAppHostEnvironment(hostEnvironment: hostEnvironment);
var loggerFactory = new NullLoggerFactory();
- var resourceLogForwarder = new ResourceLoggerForwarderService(resourceNotificationService, resourceLoggerService, hostEnvironment, loggerFactory);
+ var resourceLogForwarder = new ResourceLoggerForwarderService(resourceNotificationService, resourceLoggerService, appHostEnvironment, loggerFactory);
await resourceLogForwarder.StartAsync(hostApplicationLifetime.ApplicationStopping);
@@ -53,9 +54,10 @@ public async Task ResourceLogsAreForwardedToHostLogging()
var resourceLoggerService = ConsoleLoggingTestHelpers.GetResourceLoggerService();
var resourceNotificationService = CreateResourceNotificationService(hostApplicationLifetime, resourceLoggerService);
var hostEnvironment = new HostingEnvironment { ApplicationName = "TestApp.AppHost" };
+ var appHostEnvironment = new TestAppHostEnvironment(hostEnvironment: hostEnvironment);
var fakeLoggerProvider = new FakeLoggerProvider();
var fakeLoggerFactory = new LoggerFactory([fakeLoggerProvider, new XunitLoggerProvider(output)]);
- var resourceLogForwarder = new ResourceLoggerForwarderService(resourceNotificationService, resourceLoggerService, hostEnvironment, fakeLoggerFactory);
+ var resourceLogForwarder = new ResourceLoggerForwarderService(resourceNotificationService, resourceLoggerService, appHostEnvironment, fakeLoggerFactory);
var subscribedTcs = new TaskCompletionSource();
var subscriberLoop = Task.Run(async () =>
@@ -172,4 +174,28 @@ public void Dispose()
_stoppedCts.Dispose();
}
}
+
+ private sealed class TestAppHostEnvironment : IAppHostEnvironment
+ {
+ private readonly IHostEnvironment? _hostEnvironment;
+
+ public TestAppHostEnvironment(IHostEnvironment? hostEnvironment = null)
+ {
+ _hostEnvironment = hostEnvironment;
+ }
+
+ public string ProjectName => _hostEnvironment?.ApplicationName ?? "TestApp";
+ public string Directory => "/test";
+ public string Path => "/test/TestApp";
+ public string DashboardApplicationName => _hostEnvironment?.ApplicationName ?? "TestApp";
+ public string Sha256 => "0000000000000000000000000000000000000000000000000000000000000000";
+ public string PathSha256 => "0000000000000000000000000000000000000000000000000000000000000000";
+ public string ProjectNameSha256 => "0000000000000000000000000000000000000000000000000000000000000000";
+ public string? ContainerHostname => null;
+ public string? DefaultLaunchProfileName => null;
+ public string? OtlpApiKey => null;
+ public string? BrowserToken => null;
+ public string? ResourceServiceApiKey => null;
+ public string? ResourceServiceAuthMode => null;
+ }
}
From d7e6dafcc856f80bc1b6059dc069949756127c91 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 18 Oct 2025 16:23:48 +0000
Subject: [PATCH 5/7] Implement PR feedback: Rename properties and remove
BrowserToken
Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com>
---
src/Aspire.Hosting/AppHostEnvironment.cs | 11 ++++------
.../Dashboard/DashboardOptions.cs | 2 +-
src/Aspire.Hosting/Dcp/DcpNameGenerator.cs | 2 +-
.../DistributedApplicationLifecycle.cs | 2 +-
src/Aspire.Hosting/IAppHostEnvironment.cs | 15 +++++--------
.../IDistributedApplicationBuilder.cs | 2 +-
.../Internal/FileDeploymentStateManager.cs | 4 ++--
.../VersionChecking/VersionCheckService.cs | 2 +-
src/Aspire.Hosting/VolumeNameGenerator.cs | 2 +-
.../ResourceLoggerForwarderServiceTests.cs | 9 ++++----
.../DistributedApplicationBuilderTests.cs | 21 +++++++++----------
.../Utils/TestAppHostEnvironment.cs | 9 ++++----
12 files changed, 35 insertions(+), 46 deletions(-)
diff --git a/src/Aspire.Hosting/AppHostEnvironment.cs b/src/Aspire.Hosting/AppHostEnvironment.cs
index 1c40c4bed11..a75372bf2c1 100644
--- a/src/Aspire.Hosting/AppHostEnvironment.cs
+++ b/src/Aspire.Hosting/AppHostEnvironment.cs
@@ -29,19 +29,19 @@ public AppHostEnvironment(IConfiguration configuration, IHostEnvironment hostEnv
public string ProjectName => _configuration["AppHost:DashboardApplicationName"] ?? _hostEnvironment.ApplicationName;
///
- public string Directory => _configuration["AppHost:Directory"]!;
+ public string ProjectDirectory => _configuration["AppHost:Directory"]!;
///
- public string Path => _configuration["AppHost:Path"]!;
+ public string FullPath => _configuration["AppHost:Path"]!;
///
public string DashboardApplicationName => _configuration["AppHost:DashboardApplicationName"] ?? _hostEnvironment.ApplicationName;
///
- public string Sha256 => _configuration["AppHost:Sha256"]!;
+ public string DefaultHash => _configuration["AppHost:Sha256"]!;
///
- public string PathSha256 => _configuration["AppHost:PathSha256"]!;
+ public string FullPathHash => _configuration["AppHost:PathSha256"]!;
///
public string ProjectNameSha256 => _configuration["AppHost:ProjectNameSha256"]!;
@@ -55,9 +55,6 @@ public AppHostEnvironment(IConfiguration configuration, IHostEnvironment hostEnv
///
public string? OtlpApiKey => _configuration["AppHost:OtlpApiKey"];
- ///
- public string? BrowserToken => _configuration["AppHost:BrowserToken"];
-
///
public string? ResourceServiceApiKey => _configuration["AppHost:ResourceService:ApiKey"];
diff --git a/src/Aspire.Hosting/Dashboard/DashboardOptions.cs b/src/Aspire.Hosting/Dashboard/DashboardOptions.cs
index 4133d14139c..6295e7e4d0a 100644
--- a/src/Aspire.Hosting/Dashboard/DashboardOptions.cs
+++ b/src/Aspire.Hosting/Dashboard/DashboardOptions.cs
@@ -25,7 +25,7 @@ public void Configure(DashboardOptions options)
{
options.DashboardPath = dcpOptions.Value.DashboardPath;
options.DashboardUrl = configuration[KnownConfigNames.AspNetCoreUrls];
- options.DashboardToken = appHostEnvironment.BrowserToken;
+ options.DashboardToken = configuration["AppHost:BrowserToken"];
options.OtlpGrpcEndpointUrl = configuration.GetString(KnownConfigNames.DashboardOtlpGrpcEndpointUrl, KnownConfigNames.Legacy.DashboardOtlpGrpcEndpointUrl);
options.OtlpHttpEndpointUrl = configuration.GetString(KnownConfigNames.DashboardOtlpHttpEndpointUrl, KnownConfigNames.Legacy.DashboardOtlpHttpEndpointUrl);
diff --git a/src/Aspire.Hosting/Dcp/DcpNameGenerator.cs b/src/Aspire.Hosting/Dcp/DcpNameGenerator.cs
index 7e6faf47c74..4ff298bd3a7 100644
--- a/src/Aspire.Hosting/Dcp/DcpNameGenerator.cs
+++ b/src/Aspire.Hosting/Dcp/DcpNameGenerator.cs
@@ -114,7 +114,7 @@ public static string GetRandomNameSuffix()
public string GetProjectHashSuffix()
{
// Compute a short hash of the content root path to differentiate between multiple AppHost projects with similar resource names
- var suffix = _appHostEnvironment.Sha256.Substring(0, RandomNameSuffixLength).ToLowerInvariant();
+ var suffix = _appHostEnvironment.DefaultHash.Substring(0, RandomNameSuffixLength).ToLowerInvariant();
return suffix;
}
diff --git a/src/Aspire.Hosting/DistributedApplicationLifecycle.cs b/src/Aspire.Hosting/DistributedApplicationLifecycle.cs
index 7e013629bbb..283b25cae34 100644
--- a/src/Aspire.Hosting/DistributedApplicationLifecycle.cs
+++ b/src/Aspire.Hosting/DistributedApplicationLifecycle.cs
@@ -40,7 +40,7 @@ public Task StartingAsync(CancellationToken cancellationToken)
if (executionContext.IsRunMode)
{
logger.LogInformation("Distributed application starting.");
- logger.LogInformation("Application host directory is: {AppHostDirectory}", appHostEnvironment.Directory);
+ logger.LogInformation("Application host directory is: {AppHostDirectory}", appHostEnvironment.ProjectDirectory);
}
if (localeOverrideContext.OverrideErrorMessage is { Length: > 0 } localOverrideError)
diff --git a/src/Aspire.Hosting/IAppHostEnvironment.cs b/src/Aspire.Hosting/IAppHostEnvironment.cs
index a49617384ab..76e150d331f 100644
--- a/src/Aspire.Hosting/IAppHostEnvironment.cs
+++ b/src/Aspire.Hosting/IAppHostEnvironment.cs
@@ -20,12 +20,12 @@ public interface IAppHostEnvironment
///
/// Gets the directory of the project where the app host is located.
///
- string Directory { get; }
+ string ProjectDirectory { get; }
///
/// Gets the full path to the app host.
///
- string Path { get; }
+ string FullPath { get; }
///
/// Gets the application name used for the dashboard.
@@ -38,9 +38,9 @@ public interface IAppHostEnvironment
///
/// For backward compatibility, this uses mode-dependent logic:
/// - Publish mode: ProjectNameSha (stable across paths)
- /// - Run mode: PathSha (disambiguates by path)
+ /// - Run mode: FullPathHash (disambiguates by path)
///
- string Sha256 { get; }
+ string DefaultHash { get; }
///
/// Gets the SHA256 hash based on the app host path.
@@ -48,7 +48,7 @@ public interface IAppHostEnvironment
///
/// Used for disambiguating projects with the same name in different locations (deployment state).
///
- string PathSha256 { get; }
+ string FullPathHash { get; }
///
/// Gets the SHA256 hash based on the project name.
@@ -73,11 +73,6 @@ public interface IAppHostEnvironment
///
string? OtlpApiKey { get; }
- ///
- /// Gets the browser token for the dashboard.
- ///
- string? BrowserToken { get; }
-
///
/// Gets the resource service API key.
///
diff --git a/src/Aspire.Hosting/IDistributedApplicationBuilder.cs b/src/Aspire.Hosting/IDistributedApplicationBuilder.cs
index 44b2f319dde..8dd9976881e 100644
--- a/src/Aspire.Hosting/IDistributedApplicationBuilder.cs
+++ b/src/Aspire.Hosting/IDistributedApplicationBuilder.cs
@@ -75,7 +75,7 @@ public interface IDistributedApplicationBuilder
///
/// Provides information about the AppHost environment.
///
- public IAppHostEnvironment AppHostEnvironment { get; }
+ public IAppHostEnvironment AppHostEnvironment => throw new NotImplementedException();
///
public IServiceCollection Services { get; }
diff --git a/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs b/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs
index e3bac034340..01c539c170e 100644
--- a/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs
+++ b/src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs
@@ -30,8 +30,8 @@ public sealed class FileDeploymentStateManager(
private string? GetDeploymentStatePath()
{
- // Use PathSha256 for deployment state to disambiguate projects with the same name in different locations
- var appHostSha = appHostEnvironment.PathSha256;
+ // Use FullPathHash for deployment state to disambiguate projects with the same name in different locations
+ var appHostSha = appHostEnvironment.FullPathHash;
if (string.IsNullOrEmpty(appHostSha))
{
return null;
diff --git a/src/Aspire.Hosting/VersionChecking/VersionCheckService.cs b/src/Aspire.Hosting/VersionChecking/VersionCheckService.cs
index 29aa86eaccc..37abfc1fc78 100644
--- a/src/Aspire.Hosting/VersionChecking/VersionCheckService.cs
+++ b/src/Aspire.Hosting/VersionChecking/VersionCheckService.cs
@@ -99,7 +99,7 @@ private async Task CheckForLatestAsync(CancellationToken cancellationToken)
SemVersion? storedKnownLatestVersion = null;
if (checkForLatestVersion)
{
- var appHostDirectory = _appHostEnvironment.Directory;
+ var appHostDirectory = _appHostEnvironment.ProjectDirectory;
SecretsStore.TrySetUserSecret(_options.Assembly, LastCheckDateKey, now.ToString("o", CultureInfo.InvariantCulture));
packages = await _packageFetcher.TryFetchPackagesAsync(appHostDirectory, cancellationToken).ConfigureAwait(false);
diff --git a/src/Aspire.Hosting/VolumeNameGenerator.cs b/src/Aspire.Hosting/VolumeNameGenerator.cs
index 3b2d5db357e..a76040a805e 100644
--- a/src/Aspire.Hosting/VolumeNameGenerator.cs
+++ b/src/Aspire.Hosting/VolumeNameGenerator.cs
@@ -29,7 +29,7 @@ public static string Generate(IResourceBuilder builder, string suffix) whe
// Compute a short hash of the content root path to differentiate between multiple AppHost projects with similar volume names
var safeApplicationName = Sanitize(builder.ApplicationBuilder.AppHostEnvironment.ProjectName).ToLowerInvariant();
- var applicationHash = builder.ApplicationBuilder.AppHostEnvironment.Sha256[..10].ToLowerInvariant();
+ var applicationHash = builder.ApplicationBuilder.AppHostEnvironment.DefaultHash[..10].ToLowerInvariant();
var resourceName = builder.Resource.Name;
return $"{safeApplicationName}-{applicationHash}-{resourceName}-{suffix}";
}
diff --git a/tests/Aspire.Hosting.Testing.Tests/ResourceLoggerForwarderServiceTests.cs b/tests/Aspire.Hosting.Testing.Tests/ResourceLoggerForwarderServiceTests.cs
index 2a9664ee530..9ffa6a22615 100644
--- a/tests/Aspire.Hosting.Testing.Tests/ResourceLoggerForwarderServiceTests.cs
+++ b/tests/Aspire.Hosting.Testing.Tests/ResourceLoggerForwarderServiceTests.cs
@@ -185,16 +185,15 @@ public TestAppHostEnvironment(IHostEnvironment? hostEnvironment = null)
}
public string ProjectName => _hostEnvironment?.ApplicationName ?? "TestApp";
- public string Directory => "/test";
- public string Path => "/test/TestApp";
+ public string ProjectDirectory => "/test";
+ public string FullPath => "/test/TestApp";
public string DashboardApplicationName => _hostEnvironment?.ApplicationName ?? "TestApp";
- public string Sha256 => "0000000000000000000000000000000000000000000000000000000000000000";
- public string PathSha256 => "0000000000000000000000000000000000000000000000000000000000000000";
+ public string DefaultHash => "0000000000000000000000000000000000000000000000000000000000000000";
+ public string FullPathHash => "0000000000000000000000000000000000000000000000000000000000000000";
public string ProjectNameSha256 => "0000000000000000000000000000000000000000000000000000000000000000";
public string? ContainerHostname => null;
public string? DefaultLaunchProfileName => null;
public string? OtlpApiKey => null;
- public string? BrowserToken => null;
public string? ResourceServiceApiKey => null;
public string? ResourceServiceAuthMode => null;
}
diff --git a/tests/Aspire.Hosting.Tests/DistributedApplicationBuilderTests.cs b/tests/Aspire.Hosting.Tests/DistributedApplicationBuilderTests.cs
index c0b90c7f6f3..ec80bae97ea 100644
--- a/tests/Aspire.Hosting.Tests/DistributedApplicationBuilderTests.cs
+++ b/tests/Aspire.Hosting.Tests/DistributedApplicationBuilderTests.cs
@@ -247,11 +247,11 @@ public void AppHostEnvironmentIsAvailableFromBuilder()
Assert.NotNull(appBuilder.AppHostEnvironment);
Assert.NotNull(appBuilder.AppHostEnvironment.ProjectName);
- Assert.NotNull(appBuilder.AppHostEnvironment.Directory);
- Assert.NotNull(appBuilder.AppHostEnvironment.Path);
+ Assert.NotNull(appBuilder.AppHostEnvironment.ProjectDirectory);
+ Assert.NotNull(appBuilder.AppHostEnvironment.FullPath);
Assert.NotNull(appBuilder.AppHostEnvironment.DashboardApplicationName);
- Assert.NotNull(appBuilder.AppHostEnvironment.Sha256);
- Assert.NotNull(appBuilder.AppHostEnvironment.PathSha256);
+ Assert.NotNull(appBuilder.AppHostEnvironment.DefaultHash);
+ Assert.NotNull(appBuilder.AppHostEnvironment.FullPathHash);
Assert.NotNull(appBuilder.AppHostEnvironment.ProjectNameSha256);
}
@@ -264,8 +264,8 @@ public void AppHostEnvironmentIsAvailableFromDI()
var appHostEnvironment = app.Services.GetRequiredService();
Assert.NotNull(appHostEnvironment);
Assert.NotNull(appHostEnvironment.ProjectName);
- Assert.NotNull(appHostEnvironment.Directory);
- Assert.NotNull(appHostEnvironment.Path);
+ Assert.NotNull(appHostEnvironment.ProjectDirectory);
+ Assert.NotNull(appHostEnvironment.FullPath);
}
[Fact]
@@ -291,8 +291,8 @@ public void AppHostEnvironmentDirectoryMatchesConfiguration()
var appHostEnvironment = app.Services.GetRequiredService();
var config = app.Services.GetRequiredService();
- Assert.Equal(config["AppHost:Directory"], appHostEnvironment.Directory);
- Assert.Equal(config["AppHost:Path"], appHostEnvironment.Path);
+ Assert.Equal(config["AppHost:Directory"], appHostEnvironment.ProjectDirectory);
+ Assert.Equal(config["AppHost:Path"], appHostEnvironment.FullPath);
}
[Fact]
@@ -304,8 +304,8 @@ public void AppHostEnvironmentSha256MatchesConfiguration()
var appHostEnvironment = app.Services.GetRequiredService();
var config = app.Services.GetRequiredService();
- Assert.Equal(config["AppHost:Sha256"], appHostEnvironment.Sha256);
- Assert.Equal(config["AppHost:PathSha256"], appHostEnvironment.PathSha256);
+ Assert.Equal(config["AppHost:Sha256"], appHostEnvironment.DefaultHash);
+ Assert.Equal(config["AppHost:PathSha256"], appHostEnvironment.FullPathHash);
Assert.Equal(config["AppHost:ProjectNameSha256"], appHostEnvironment.ProjectNameSha256);
}
@@ -319,7 +319,6 @@ public void AppHostEnvironmentSecurityConfigMatchesConfiguration()
var config = app.Services.GetRequiredService();
Assert.Equal(config["AppHost:OtlpApiKey"], appHostEnvironment.OtlpApiKey);
- Assert.Equal(config["AppHost:BrowserToken"], appHostEnvironment.BrowserToken);
Assert.Equal(config["AppHost:ResourceService:ApiKey"], appHostEnvironment.ResourceServiceApiKey);
Assert.Equal(config["AppHost:ResourceService:AuthMode"], appHostEnvironment.ResourceServiceAuthMode);
}
diff --git a/tests/Aspire.Hosting.Tests/Utils/TestAppHostEnvironment.cs b/tests/Aspire.Hosting.Tests/Utils/TestAppHostEnvironment.cs
index a8c460be141..ae364201767 100644
--- a/tests/Aspire.Hosting.Tests/Utils/TestAppHostEnvironment.cs
+++ b/tests/Aspire.Hosting.Tests/Utils/TestAppHostEnvironment.cs
@@ -18,16 +18,15 @@ public TestAppHostEnvironment(IConfiguration? configuration = null, IHostEnviron
}
public string ProjectName => _configuration?["AppHost:DashboardApplicationName"] ?? _hostEnvironment?.ApplicationName ?? "TestApp";
- public string Directory => _configuration?["AppHost:Directory"] ?? "/test";
- public string Path => _configuration?["AppHost:Path"] ?? "/test/TestApp";
+ public string ProjectDirectory => _configuration?["AppHost:Directory"] ?? "/test";
+ public string FullPath => _configuration?["AppHost:Path"] ?? "/test/TestApp";
public string DashboardApplicationName => _configuration?["AppHost:DashboardApplicationName"] ?? _hostEnvironment?.ApplicationName ?? "TestApp";
- public string Sha256 => _configuration?["AppHost:Sha256"] ?? "0000000000000000000000000000000000000000000000000000000000000000";
- public string PathSha256 => _configuration?["AppHost:PathSha256"] ?? "0000000000000000000000000000000000000000000000000000000000000000";
+ public string DefaultHash => _configuration?["AppHost:Sha256"] ?? "0000000000000000000000000000000000000000000000000000000000000000";
+ public string FullPathHash => _configuration?["AppHost:PathSha256"] ?? "0000000000000000000000000000000000000000000000000000000000000000";
public string ProjectNameSha256 => _configuration?["AppHost:ProjectNameSha256"] ?? "0000000000000000000000000000000000000000000000000000000000000000";
public string? ContainerHostname => _configuration?["AppHost:ContainerHostname"];
public string? DefaultLaunchProfileName => _configuration?["AppHost:DefaultLaunchProfileName"];
public string? OtlpApiKey => _configuration?["AppHost:OtlpApiKey"];
- public string? BrowserToken => _configuration?["AppHost:BrowserToken"];
public string? ResourceServiceApiKey => _configuration?["AppHost:ResourceService:ApiKey"];
public string? ResourceServiceAuthMode => _configuration?["AppHost:ResourceService:AuthMode"];
}
From 308eecb5bd19cb31a69857b6d3daaf8585f999b6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 18 Oct 2025 17:18:03 +0000
Subject: [PATCH 6/7] Rename ProjectNameSha256 to ProjectNameHash and use same
instance in DI
Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com>
---
.../AzureFunctionsProjectResourceExtensions.cs | 4 ++--
.../AzureEnvironmentResourceExtensions.cs | 4 ++--
src/Aspire.Hosting/AppHostEnvironment.cs | 2 +-
src/Aspire.Hosting/DistributedApplicationBuilder.cs | 4 ++--
src/Aspire.Hosting/IAppHostEnvironment.cs | 2 +-
.../ResourceLoggerForwarderServiceTests.cs | 2 +-
.../DistributedApplicationBuilderTests.cs | 4 ++--
tests/Aspire.Hosting.Tests/Utils/TestAppHostEnvironment.cs | 2 +-
8 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/src/Aspire.Hosting.Azure.Functions/AzureFunctionsProjectResourceExtensions.cs b/src/Aspire.Hosting.Azure.Functions/AzureFunctionsProjectResourceExtensions.cs
index 90020bd61d3..4590753cad2 100644
--- a/src/Aspire.Hosting.Azure.Functions/AzureFunctionsProjectResourceExtensions.cs
+++ b/src/Aspire.Hosting.Azure.Functions/AzureFunctionsProjectResourceExtensions.cs
@@ -250,8 +250,8 @@ public static IResourceBuilder WithReference WithResourceGroup(
private static string CreateDefaultAzureEnvironmentName(this IDistributedApplicationBuilder builder)
{
- // Use ProjectNameSha256 for stable naming across deployments
- var applicationHash = builder.Configuration["AppHost:ProjectNameSha256"]?[..5].ToLowerInvariant();
+ // Use ProjectNameHash for stable naming across deployments
+ var applicationHash = builder.AppHostEnvironment.ProjectNameHash[..5].ToLowerInvariant();
return $"azure{applicationHash}";
}
}
diff --git a/src/Aspire.Hosting/AppHostEnvironment.cs b/src/Aspire.Hosting/AppHostEnvironment.cs
index a75372bf2c1..0e07eafdb17 100644
--- a/src/Aspire.Hosting/AppHostEnvironment.cs
+++ b/src/Aspire.Hosting/AppHostEnvironment.cs
@@ -44,7 +44,7 @@ public AppHostEnvironment(IConfiguration configuration, IHostEnvironment hostEnv
public string FullPathHash => _configuration["AppHost:PathSha256"]!;
///
- public string ProjectNameSha256 => _configuration["AppHost:ProjectNameSha256"]!;
+ public string ProjectNameHash => _configuration["AppHost:ProjectNameSha256"]!;
///
public string? ContainerHostname => _configuration["AppHost:ContainerHostname"];
diff --git a/src/Aspire.Hosting/DistributedApplicationBuilder.cs b/src/Aspire.Hosting/DistributedApplicationBuilder.cs
index a2adfbc9d45..a90509e19bd 100644
--- a/src/Aspire.Hosting/DistributedApplicationBuilder.cs
+++ b/src/Aspire.Hosting/DistributedApplicationBuilder.cs
@@ -470,10 +470,10 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options)
}
_innerBuilder.Services.AddSingleton(ExecutionContext);
- _innerBuilder.Services.AddSingleton();
- // Initialize the AppHostEnvironment for use within the builder
+ // Initialize the AppHostEnvironment for use within the builder and register in DI
_appHostEnvironment = new AppHostEnvironment(_innerBuilder.Configuration, _innerBuilder.Environment);
+ _innerBuilder.Services.AddSingleton(_appHostEnvironment);
LogBuilderConstructed(this);
}
diff --git a/src/Aspire.Hosting/IAppHostEnvironment.cs b/src/Aspire.Hosting/IAppHostEnvironment.cs
index 76e150d331f..0ddc677b57c 100644
--- a/src/Aspire.Hosting/IAppHostEnvironment.cs
+++ b/src/Aspire.Hosting/IAppHostEnvironment.cs
@@ -56,7 +56,7 @@ public interface IAppHostEnvironment
///
/// Used for stable naming across deployments regardless of path (Azure Functions, Azure environments).
///
- string ProjectNameSha256 { get; }
+ string ProjectNameHash { get; }
///
/// Gets the container hostname.
diff --git a/tests/Aspire.Hosting.Testing.Tests/ResourceLoggerForwarderServiceTests.cs b/tests/Aspire.Hosting.Testing.Tests/ResourceLoggerForwarderServiceTests.cs
index 9ffa6a22615..be4f79d3e80 100644
--- a/tests/Aspire.Hosting.Testing.Tests/ResourceLoggerForwarderServiceTests.cs
+++ b/tests/Aspire.Hosting.Testing.Tests/ResourceLoggerForwarderServiceTests.cs
@@ -190,7 +190,7 @@ public TestAppHostEnvironment(IHostEnvironment? hostEnvironment = null)
public string DashboardApplicationName => _hostEnvironment?.ApplicationName ?? "TestApp";
public string DefaultHash => "0000000000000000000000000000000000000000000000000000000000000000";
public string FullPathHash => "0000000000000000000000000000000000000000000000000000000000000000";
- public string ProjectNameSha256 => "0000000000000000000000000000000000000000000000000000000000000000";
+ public string ProjectNameHash => "0000000000000000000000000000000000000000000000000000000000000000";
public string? ContainerHostname => null;
public string? DefaultLaunchProfileName => null;
public string? OtlpApiKey => null;
diff --git a/tests/Aspire.Hosting.Tests/DistributedApplicationBuilderTests.cs b/tests/Aspire.Hosting.Tests/DistributedApplicationBuilderTests.cs
index ec80bae97ea..44d34ebfbbe 100644
--- a/tests/Aspire.Hosting.Tests/DistributedApplicationBuilderTests.cs
+++ b/tests/Aspire.Hosting.Tests/DistributedApplicationBuilderTests.cs
@@ -252,7 +252,7 @@ public void AppHostEnvironmentIsAvailableFromBuilder()
Assert.NotNull(appBuilder.AppHostEnvironment.DashboardApplicationName);
Assert.NotNull(appBuilder.AppHostEnvironment.DefaultHash);
Assert.NotNull(appBuilder.AppHostEnvironment.FullPathHash);
- Assert.NotNull(appBuilder.AppHostEnvironment.ProjectNameSha256);
+ Assert.NotNull(appBuilder.AppHostEnvironment.ProjectNameHash);
}
[Fact]
@@ -306,7 +306,7 @@ public void AppHostEnvironmentSha256MatchesConfiguration()
Assert.Equal(config["AppHost:Sha256"], appHostEnvironment.DefaultHash);
Assert.Equal(config["AppHost:PathSha256"], appHostEnvironment.FullPathHash);
- Assert.Equal(config["AppHost:ProjectNameSha256"], appHostEnvironment.ProjectNameSha256);
+ Assert.Equal(config["AppHost:ProjectNameSha256"], appHostEnvironment.ProjectNameHash);
}
[Fact]
diff --git a/tests/Aspire.Hosting.Tests/Utils/TestAppHostEnvironment.cs b/tests/Aspire.Hosting.Tests/Utils/TestAppHostEnvironment.cs
index ae364201767..173b2ca2f3d 100644
--- a/tests/Aspire.Hosting.Tests/Utils/TestAppHostEnvironment.cs
+++ b/tests/Aspire.Hosting.Tests/Utils/TestAppHostEnvironment.cs
@@ -23,7 +23,7 @@ public TestAppHostEnvironment(IConfiguration? configuration = null, IHostEnviron
public string DashboardApplicationName => _configuration?["AppHost:DashboardApplicationName"] ?? _hostEnvironment?.ApplicationName ?? "TestApp";
public string DefaultHash => _configuration?["AppHost:Sha256"] ?? "0000000000000000000000000000000000000000000000000000000000000000";
public string FullPathHash => _configuration?["AppHost:PathSha256"] ?? "0000000000000000000000000000000000000000000000000000000000000000";
- public string ProjectNameSha256 => _configuration?["AppHost:ProjectNameSha256"] ?? "0000000000000000000000000000000000000000000000000000000000000000";
+ public string ProjectNameHash => _configuration?["AppHost:ProjectNameSha256"] ?? "0000000000000000000000000000000000000000000000000000000000000000";
public string? ContainerHostname => _configuration?["AppHost:ContainerHostname"];
public string? DefaultLaunchProfileName => _configuration?["AppHost:DefaultLaunchProfileName"];
public string? OtlpApiKey => _configuration?["AppHost:OtlpApiKey"];
From cc8a18654fa6971d17ab958c93ba520a6a11326b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 18 Oct 2025 17:36:31 +0000
Subject: [PATCH 7/7] Replace IHostEnvironment with IAppHostEnvironment in
Azure provisioning providers
Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com>
---
.../BaseProvisioningContextProvider.cs | 5 ++--
.../PublishModeProvisioningContextProvider.cs | 7 +++--
.../RunModeProvisioningContextProvider.cs | 7 +++--
.../AzureDeployerTests.cs | 2 +-
.../ProvisioningContextProviderTests.cs | 20 +++++++-------
.../ProvisioningTestHelpers.cs | 27 ++++++++++++++++---
6 files changed, 42 insertions(+), 26 deletions(-)
diff --git a/src/Aspire.Hosting.Azure/Provisioning/Internal/BaseProvisioningContextProvider.cs b/src/Aspire.Hosting.Azure/Provisioning/Internal/BaseProvisioningContextProvider.cs
index 5cc8bc07368..8a33eb9a94f 100644
--- a/src/Aspire.Hosting.Azure/Provisioning/Internal/BaseProvisioningContextProvider.cs
+++ b/src/Aspire.Hosting.Azure/Provisioning/Internal/BaseProvisioningContextProvider.cs
@@ -8,7 +8,6 @@
using Azure;
using Azure.Core;
using Azure.ResourceManager.Resources;
-using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -20,7 +19,7 @@ namespace Aspire.Hosting.Azure.Provisioning.Internal;
internal abstract partial class BaseProvisioningContextProvider(
IInteractionService interactionService,
IOptions options,
- IHostEnvironment environment,
+ IAppHostEnvironment appHostEnvironment,
ILogger logger,
IArmClientProvider armClientProvider,
IUserPrincipalProvider userPrincipalProvider,
@@ -33,7 +32,7 @@ internal abstract partial class BaseProvisioningContextProvider(
protected readonly IInteractionService _interactionService = interactionService;
protected readonly AzureProvisionerOptions _options = options.Value;
- protected readonly IHostEnvironment _environment = environment;
+ protected readonly IAppHostEnvironment _appHostEnvironment = appHostEnvironment;
protected readonly ILogger _logger = logger;
protected readonly IArmClientProvider _armClientProvider = armClientProvider;
protected readonly IUserPrincipalProvider _userPrincipalProvider = userPrincipalProvider;
diff --git a/src/Aspire.Hosting.Azure/Provisioning/Internal/PublishModeProvisioningContextProvider.cs b/src/Aspire.Hosting.Azure/Provisioning/Internal/PublishModeProvisioningContextProvider.cs
index de2a5bdff9d..8a52e5cbe7e 100644
--- a/src/Aspire.Hosting.Azure/Provisioning/Internal/PublishModeProvisioningContextProvider.cs
+++ b/src/Aspire.Hosting.Azure/Provisioning/Internal/PublishModeProvisioningContextProvider.cs
@@ -8,7 +8,6 @@
using Aspire.Hosting.Azure.Resources;
using Aspire.Hosting.Azure.Utils;
using Aspire.Hosting.Publishing;
-using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -21,7 +20,7 @@ namespace Aspire.Hosting.Azure.Provisioning.Internal;
internal sealed class PublishModeProvisioningContextProvider(
IInteractionService interactionService,
IOptions options,
- IHostEnvironment environment,
+ IAppHostEnvironment appHostEnvironment,
ILogger logger,
IArmClientProvider armClientProvider,
IUserPrincipalProvider userPrincipalProvider,
@@ -30,7 +29,7 @@ internal sealed class PublishModeProvisioningContextProvider(
IPublishingActivityReporter activityReporter) : BaseProvisioningContextProvider(
interactionService,
options,
- environment,
+ appHostEnvironment,
logger,
armClientProvider,
userPrincipalProvider,
@@ -48,7 +47,7 @@ protected override string GetDefaultResourceGroupName()
var maxApplicationNameSize = ResourceGroupNameHelpers.MaxResourceGroupNameLength - prefix.Length - 1; // extra '-'
- var normalizedApplicationName = ResourceGroupNameHelpers.NormalizeResourceGroupName(_environment.ApplicationName.ToLowerInvariant());
+ var normalizedApplicationName = ResourceGroupNameHelpers.NormalizeResourceGroupName(_appHostEnvironment.ProjectName.ToLowerInvariant());
if (normalizedApplicationName.Length > maxApplicationNameSize)
{
normalizedApplicationName = normalizedApplicationName[..maxApplicationNameSize];
diff --git a/src/Aspire.Hosting.Azure/Provisioning/Internal/RunModeProvisioningContextProvider.cs b/src/Aspire.Hosting.Azure/Provisioning/Internal/RunModeProvisioningContextProvider.cs
index a1556d01051..4c581764a60 100644
--- a/src/Aspire.Hosting.Azure/Provisioning/Internal/RunModeProvisioningContextProvider.cs
+++ b/src/Aspire.Hosting.Azure/Provisioning/Internal/RunModeProvisioningContextProvider.cs
@@ -7,7 +7,6 @@
using System.Text.Json.Nodes;
using Aspire.Hosting.Azure.Resources;
using Aspire.Hosting.Azure.Utils;
-using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -19,7 +18,7 @@ namespace Aspire.Hosting.Azure.Provisioning.Internal;
internal sealed class RunModeProvisioningContextProvider(
IInteractionService interactionService,
IOptions options,
- IHostEnvironment environment,
+ IAppHostEnvironment appHostEnvironment,
ILogger logger,
IArmClientProvider armClientProvider,
IUserPrincipalProvider userPrincipalProvider,
@@ -27,7 +26,7 @@ internal sealed class RunModeProvisioningContextProvider(
DistributedApplicationExecutionContext distributedApplicationExecutionContext) : BaseProvisioningContextProvider(
interactionService,
options,
- environment,
+ appHostEnvironment,
logger,
armClientProvider,
userPrincipalProvider,
@@ -49,7 +48,7 @@ protected override string GetDefaultResourceGroupName()
var maxApplicationNameSize = ResourceGroupNameHelpers.MaxResourceGroupNameLength - prefix.Length - suffix.Length - 2; // extra '-'s
- var normalizedApplicationName = ResourceGroupNameHelpers.NormalizeResourceGroupName(_environment.ApplicationName.ToLowerInvariant());
+ var normalizedApplicationName = ResourceGroupNameHelpers.NormalizeResourceGroupName(_appHostEnvironment.ProjectName.ToLowerInvariant());
if (normalizedApplicationName.Length > maxApplicationNameSize)
{
normalizedApplicationName = normalizedApplicationName[..maxApplicationNameSize];
diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureDeployerTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureDeployerTests.cs
index 4e56ca76ab8..702d7114129 100644
--- a/tests/Aspire.Hosting.Azure.Tests/AzureDeployerTests.cs
+++ b/tests/Aspire.Hosting.Azure.Tests/AzureDeployerTests.cs
@@ -854,7 +854,7 @@ private static void ConfigureTestServices(IDistributedApplicationTestingBuilder
bool setDefaultProvisioningOptions = true)
{
var options = setDefaultProvisioningOptions ? ProvisioningTestHelpers.CreateOptions() : ProvisioningTestHelpers.CreateOptions(null, null, null);
- var environment = ProvisioningTestHelpers.CreateEnvironment();
+ var environment = ProvisioningTestHelpers.CreateAppHostEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
armClientProvider ??= ProvisioningTestHelpers.CreateArmClientProvider();
var userPrincipalProvider = ProvisioningTestHelpers.CreateUserPrincipalProvider();
diff --git a/tests/Aspire.Hosting.Azure.Tests/ProvisioningContextProviderTests.cs b/tests/Aspire.Hosting.Azure.Tests/ProvisioningContextProviderTests.cs
index 18b2d942d1f..27853700d0f 100644
--- a/tests/Aspire.Hosting.Azure.Tests/ProvisioningContextProviderTests.cs
+++ b/tests/Aspire.Hosting.Azure.Tests/ProvisioningContextProviderTests.cs
@@ -22,7 +22,7 @@ public async Task CreateProvisioningContextAsync_ReturnsValidContext()
{
// Arrange
var options = ProvisioningTestHelpers.CreateOptions();
- var environment = ProvisioningTestHelpers.CreateEnvironment();
+ var environment = ProvisioningTestHelpers.CreateAppHostEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var userPrincipalProvider = ProvisioningTestHelpers.CreateUserPrincipalProvider();
@@ -60,7 +60,7 @@ public async Task CreateProvisioningContextAsync_ThrowsWhenSubscriptionIdMissing
{
// Arrange
var options = ProvisioningTestHelpers.CreateOptions(subscriptionId: null);
- var environment = ProvisioningTestHelpers.CreateEnvironment();
+ var environment = ProvisioningTestHelpers.CreateAppHostEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var userPrincipalProvider = ProvisioningTestHelpers.CreateUserPrincipalProvider();
@@ -88,7 +88,7 @@ public async Task CreateProvisioningContextAsync_ThrowsWhenLocationMissing()
{
// Arrange
var options = ProvisioningTestHelpers.CreateOptions(location: null);
- var environment = ProvisioningTestHelpers.CreateEnvironment();
+ var environment = ProvisioningTestHelpers.CreateAppHostEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var userPrincipalProvider = ProvisioningTestHelpers.CreateUserPrincipalProvider();
@@ -116,7 +116,7 @@ public async Task CreateProvisioningContextAsync_GeneratesResourceGroupNameWhenN
{
// Arrange
var options = ProvisioningTestHelpers.CreateOptions(resourceGroup: null);
- var environment = ProvisioningTestHelpers.CreateEnvironment();
+ var environment = ProvisioningTestHelpers.CreateAppHostEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var userPrincipalProvider = ProvisioningTestHelpers.CreateUserPrincipalProvider();
@@ -152,7 +152,7 @@ public async Task CreateProvisioningContextAsync_UsesProvidedResourceGroupName()
// Arrange
var resourceGroupName = "my-custom-rg";
var options = ProvisioningTestHelpers.CreateOptions(resourceGroup: resourceGroupName);
- var environment = ProvisioningTestHelpers.CreateEnvironment();
+ var environment = ProvisioningTestHelpers.CreateAppHostEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var userPrincipalProvider = ProvisioningTestHelpers.CreateUserPrincipalProvider();
@@ -182,7 +182,7 @@ public async Task CreateProvisioningContextAsync_RetrievesUserPrincipal()
{
// Arrange
var options = ProvisioningTestHelpers.CreateOptions();
- var environment = ProvisioningTestHelpers.CreateEnvironment();
+ var environment = ProvisioningTestHelpers.CreateAppHostEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var userPrincipalProvider = ProvisioningTestHelpers.CreateUserPrincipalProvider();
@@ -213,7 +213,7 @@ public async Task CreateProvisioningContextAsync_SetsCorrectTenant()
{
// Arrange
var options = ProvisioningTestHelpers.CreateOptions();
- var environment = ProvisioningTestHelpers.CreateEnvironment();
+ var environment = ProvisioningTestHelpers.CreateAppHostEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var userPrincipalProvider = ProvisioningTestHelpers.CreateUserPrincipalProvider();
@@ -245,7 +245,7 @@ public async Task CreateProvisioningContextAsync_PromptsIfNoOptions()
// Arrange
var testInteractionService = new TestInteractionService();
var options = ProvisioningTestHelpers.CreateOptions(null, null, null);
- var environment = ProvisioningTestHelpers.CreateEnvironment();
+ var environment = ProvisioningTestHelpers.CreateAppHostEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var userPrincipalProvider = ProvisioningTestHelpers.CreateUserPrincipalProvider();
@@ -332,7 +332,7 @@ public async Task CreateProvisioningContextAsync_Prompt_ValidatesSubAndResourceG
{
var testInteractionService = new TestInteractionService();
var options = ProvisioningTestHelpers.CreateOptions(null, null, null);
- var environment = ProvisioningTestHelpers.CreateEnvironment();
+ var environment = ProvisioningTestHelpers.CreateAppHostEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var userPrincipalProvider = ProvisioningTestHelpers.CreateUserPrincipalProvider();
@@ -391,7 +391,7 @@ public async Task PublishMode_CreateProvisioningContextAsync_ReturnsValidContext
{
// Arrange
var options = ProvisioningTestHelpers.CreateOptions();
- var environment = ProvisioningTestHelpers.CreateEnvironment();
+ var environment = ProvisioningTestHelpers.CreateAppHostEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var userPrincipalProvider = ProvisioningTestHelpers.CreateUserPrincipalProvider();
diff --git a/tests/Aspire.Hosting.Azure.Tests/ProvisioningTestHelpers.cs b/tests/Aspire.Hosting.Azure.Tests/ProvisioningTestHelpers.cs
index 2c562b70566..bd15bb85ffd 100644
--- a/tests/Aspire.Hosting.Azure.Tests/ProvisioningTestHelpers.cs
+++ b/tests/Aspire.Hosting.Azure.Tests/ProvisioningTestHelpers.cs
@@ -95,13 +95,13 @@ public static IOptions CreatePublishingOptions(
}
///
- /// Creates a test host environment.
+ /// Creates a test AppHost environment.
///
- public static IHostEnvironment CreateEnvironment()
+ public static IAppHostEnvironment CreateAppHostEnvironment()
{
- var environment = new TestHostEnvironment
+ var environment = new TestAppHostEnvironment
{
- ApplicationName = "TestApp"
+ ProjectName = "TestApp"
};
return environment;
}
@@ -558,6 +558,25 @@ internal sealed class TestHostEnvironment : IHostEnvironment
public IFileProvider ContentRootFileProvider { get; set; } = new NullFileProvider();
}
+///
+/// Test implementation of .
+///
+internal sealed class TestAppHostEnvironment : IAppHostEnvironment
+{
+ public string ProjectName { get; set; } = "TestApp";
+ public string ProjectDirectory { get; set; } = "/test";
+ public string FullPath { get; set; } = "/test/TestApp";
+ public string DashboardApplicationName { get; set; } = "TestApp";
+ public string DefaultHash { get; set; } = "0000000000000000000000000000000000000000000000000000000000000000";
+ public string FullPathHash { get; set; } = "0000000000000000000000000000000000000000000000000000000000000000";
+ public string ProjectNameHash { get; set; } = "0000000000000000000000000000000000000000000000000000000000000000";
+ public string? ContainerHostname { get; set; }
+ public string? DefaultLaunchProfileName { get; set; }
+ public string? OtlpApiKey { get; set; }
+ public string? ResourceServiceApiKey { get; set; }
+ public string? ResourceServiceAuthMode { get; set; }
+}
+
internal sealed class TestBicepCompiler : IBicepCompiler
{
public Task CompileBicepToArmAsync(string bicepFilePath, CancellationToken cancellationToken = default)