diff --git a/src/Aspire.Hosting/Backchannel/AppHostRpcTarget.cs b/src/Aspire.Hosting/Backchannel/AppHostRpcTarget.cs index b481e2861f0..7d243fb3d08 100644 --- a/src/Aspire.Hosting/Backchannel/AppHostRpcTarget.cs +++ b/src/Aspire.Hosting/Backchannel/AppHostRpcTarget.cs @@ -9,7 +9,6 @@ using Aspire.Hosting.Publishing; using Aspire.Hosting.Utils; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -114,16 +113,11 @@ public Task PingAsync(long timestamp, CancellationToken cancellationToken) throw new InvalidOperationException("Dashboard URL requested but dashboard is disabled."); } - // Wait for the dashboard to be healthy before returning the URL. This next statement has several - // layers of hacks. Some to work around devcontainer/codespaces port forwarding behavior, and one to - // temporarily work around the fact that resource events abuse the state to mark the resource as - // hidden instead of having another field. There is a corresponding modification in the ResourceHealthService - // which allows the dashboard resource to trigger health reports even though it never enters - // the Running state. This is a hack. The reason we can't just check HealthStatus is because - // the current implementation of HealthStatus depends on the state of the resource as well. - await resourceNotificationService.WaitForResourceAsync( + // Wait for the dashboard to be healthy before returning the URL. This is to ensure that the + // endpoint for the resource is available and the dashboard is ready to be used. This helps + // avoid some issues with port forwarding in devcontainer/codespaces scenarios. + await resourceNotificationService.WaitForResourceHealthyAsync( KnownResourceNames.AspireDashboard, - re => re.Snapshot.HealthReports.All(h => h.Status == HealthStatus.Healthy), cancellationToken).ConfigureAwait(false); var dashboardOptions = serviceProvider.GetService>(); diff --git a/src/Aspire.Hosting/Dashboard/DashboardLifecycleHook.cs b/src/Aspire.Hosting/Dashboard/DashboardLifecycleHook.cs index 78a73a064a4..8e605832a2e 100644 --- a/src/Aspire.Hosting/Dashboard/DashboardLifecycleHook.cs +++ b/src/Aspire.Hosting/Dashboard/DashboardLifecycleHook.cs @@ -160,6 +160,9 @@ private void ConfigureAspireDashboardResource(IResource dashboardResource) } } + var showDashboardResources = configuration.GetBool(KnownConfigNames.ShowDashboardResources, KnownConfigNames.Legacy.ShowDashboardResources); + var hideDashboard = !(showDashboardResources ?? false); + var snapshot = new CustomResourceSnapshot { Properties = [], @@ -170,11 +173,7 @@ private void ConfigureAspireDashboardResource(IResource dashboardResource) ContainerResource => KnownResourceTypes.Container, _ => dashboardResource.GetType().Name }, - State = configuration.GetBool(KnownConfigNames.ShowDashboardResources, KnownConfigNames.Legacy.ShowDashboardResources) is true - ? null -#pragma warning disable CS0618 // Type or member is obsolete - : KnownResourceStates.Hidden -#pragma warning restore CS0618 // Type or member is obsolete + IsHidden = hideDashboard }; dashboardResource.Annotations.Add(new ResourceSnapshotAnnotation(snapshot)); diff --git a/src/Aspire.Hosting/DistributedApplicationBuilder.cs b/src/Aspire.Hosting/DistributedApplicationBuilder.cs index fcab487bad2..6a13942c6a0 100644 --- a/src/Aspire.Hosting/DistributedApplicationBuilder.cs +++ b/src/Aspire.Hosting/DistributedApplicationBuilder.cs @@ -333,19 +333,7 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options) _innerBuilder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, ConfigureDefaultDashboardOptions>()); _innerBuilder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, ValidateDashboardOptions>()); - // Dashboard health check. - _innerBuilder.Services.AddHealthChecks().AddUrlGroup(sp => { - - var dashboardOptions = sp.GetRequiredService>().Value; - if (StringUtils.TryGetUriFromDelimitedString(dashboardOptions.DashboardUrl, ";", out var firstDashboardUrl)) - { - return firstDashboardUrl; - } - else - { - throw new DistributedApplicationException($"The dashboard resource '{KnownResourceNames.AspireDashboard}' does not have endpoints."); - } - }, KnownHealthCheckNames.DasboardHealthCheck); + ConfigureDashboardHealthCheck(); } if (options.EnableResourceLogging) @@ -401,6 +389,24 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options) LogBuilderConstructed(this); } + private void ConfigureDashboardHealthCheck() + { + _innerBuilder.Services.AddHealthChecks().AddUrlGroup(sp => { + + var dashboardOptions = sp.GetRequiredService>().Value; + if (StringUtils.TryGetUriFromDelimitedString(dashboardOptions.DashboardUrl, ";", out var firstDashboardUrl)) + { + return firstDashboardUrl; + } + else + { + throw new DistributedApplicationException($"The dashboard resource '{KnownResourceNames.AspireDashboard}' does not have endpoints."); + } + }, KnownHealthCheckNames.DasboardHealthCheck); + + _innerBuilder.Services.SuppressHealthCheckHttpClientLogging(KnownHealthCheckNames.DasboardHealthCheck); + } + private void ConfigureHealthChecks() { _innerBuilder.Services.AddSingleton>(sp => diff --git a/src/Aspire.Hosting/Health/ResourceHealthCheckService.cs b/src/Aspire.Hosting/Health/ResourceHealthCheckService.cs index cefe7f32102..bf831314053 100644 --- a/src/Aspire.Hosting/Health/ResourceHealthCheckService.cs +++ b/src/Aspire.Hosting/Health/ResourceHealthCheckService.cs @@ -39,10 +39,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) } } - // HACK: We are special casing the Aspire dashboard here until we address the issue of the Hidden state - // making it impossible to determine whether a hidden resource is running or not. When that change - // is made we can remove the special case logic here for the dashboard. - if (resourceEvent.Snapshot.State?.Text == KnownResourceStates.Running || resourceEvent.Resource.Name == KnownResourceNames.AspireDashboard) + if (resourceEvent.Snapshot.State?.Text == KnownResourceStates.Running) { if (state == null) { diff --git a/src/Aspire.Hosting/ResourceBuilderExtensions.cs b/src/Aspire.Hosting/ResourceBuilderExtensions.cs index a0bc79316df..625d1b39f13 100644 --- a/src/Aspire.Hosting/ResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting/ResourceBuilderExtensions.cs @@ -6,6 +6,7 @@ using Aspire.Dashboard.Model; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Publishing; +using Aspire.Hosting.Utils; using HealthChecks.Uris; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; @@ -1281,12 +1282,8 @@ internal static IResourceBuilder WithHttpHealthCheckInternal(this IResourc }); var healthCheckKey = $"{builder.Resource.Name}_{endpointName}_{path}_{statusCode}_check"; - builder.ApplicationBuilder.Services.AddLogging(configure => - { - // The AddUrlGroup health check makes use of http client factory. - configure.AddFilter($"System.Net.Http.HttpClient.{healthCheckKey}.LogicalHandler", LogLevel.None); - configure.AddFilter($"System.Net.Http.HttpClient.{healthCheckKey}.ClientHandler", LogLevel.None); - }); + + builder.ApplicationBuilder.Services.SuppressHealthCheckHttpClientLogging(healthCheckKey); builder.ApplicationBuilder.Services.AddHealthChecks().AddUrlGroup((UriHealthCheckOptions options) => { diff --git a/src/Aspire.Hosting/Utils/LoggingUtils.cs b/src/Aspire.Hosting/Utils/LoggingUtils.cs new file mode 100644 index 00000000000..e6579297083 --- /dev/null +++ b/src/Aspire.Hosting/Utils/LoggingUtils.cs @@ -0,0 +1,20 @@ +// 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.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Aspire.Hosting.Utils; + +internal static class LoggingUtils +{ + public static void SuppressHealthCheckHttpClientLogging(this IServiceCollection services, string healthCheckName) + { + services.AddLogging(configure => + { + // The AddUrlGroup health check makes use of http client factory. + configure.AddFilter($"System.Net.Http.HttpClient.{healthCheckName}.LogicalHandler", LogLevel.None); + configure.AddFilter($"System.Net.Http.HttpClient.{healthCheckName}.ClientHandler", LogLevel.None); + }); + } +} \ No newline at end of file diff --git a/tests/Aspire.Hosting.Tests/Dashboard/DashboardResourceTests.cs b/tests/Aspire.Hosting.Tests/Dashboard/DashboardResourceTests.cs index e6e72cdc46b..2fa7df11206 100644 --- a/tests/Aspire.Hosting.Tests/Dashboard/DashboardResourceTests.cs +++ b/tests/Aspire.Hosting.Tests/Dashboard/DashboardResourceTests.cs @@ -52,7 +52,7 @@ public async Task DashboardIsAutomaticallyAddedAsHiddenResource(string showDashb Assert.NotNull(dashboard); Assert.Equal("aspire-dashboard", dashboard.Name); Assert.Equal(dashboardPath, dashboard.Command); - Assert.Equal("Hidden", initialSnapshot.InitialSnapshot.State); + Assert.True(initialSnapshot.InitialSnapshot.IsHidden); } [Fact]