From 26a19f2b854d4d5e3620ae7c84b18316561c709a Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 23 May 2026 00:06:36 +0800 Subject: [PATCH 1/2] Use KnownConfigNames for resource service endpoint URL with legacy fallback Remove hardcoded DOTNET_RESOURCE_SERVICE_ENDPOINT_URL constant from DashboardServiceHost and use KnownConfigNames.ResourceServiceEndpointUrl (ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL) with fallback to KnownConfigNames.Legacy.ResourceServiceEndpointUrl. --- .../ServiceClient/DashboardClient.cs | 2 +- .../Dashboard/DashboardServiceHost.cs | 18 +++++------------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/Aspire.Dashboard/ServiceClient/DashboardClient.cs b/src/Aspire.Dashboard/ServiceClient/DashboardClient.cs index d21f37f9d11..eb06704c436 100644 --- a/src/Aspire.Dashboard/ServiceClient/DashboardClient.cs +++ b/src/Aspire.Dashboard/ServiceClient/DashboardClient.cs @@ -31,7 +31,7 @@ namespace Aspire.Dashboard.ServiceClient; /// lives until the stream is closed. /// /// -/// If the DOTNET_RESOURCE_SERVICE_ENDPOINT_URL environment variable is not specified, then there's +/// If the ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL environment variable is not specified, then there's /// no known endpoint to connect to, and this dashboard client will be disabled. Calls to /// and /// will throw if is . Callers should diff --git a/src/Aspire.Hosting/Dashboard/DashboardServiceHost.cs b/src/Aspire.Hosting/Dashboard/DashboardServiceHost.cs index b69a48f98fe..3d66f7e2b7c 100644 --- a/src/Aspire.Hosting/Dashboard/DashboardServiceHost.cs +++ b/src/Aspire.Hosting/Dashboard/DashboardServiceHost.cs @@ -24,16 +24,6 @@ namespace Aspire.Hosting.Dashboard; /// internal sealed class DashboardServiceHost : IHostedService { - /// - /// Name of the environment variable that optionally specifies the resource service URL, - /// which the dashboard will connect to over gRPC. - /// - /// - /// This is primarily intended for cases outside of the local developer environment. - /// If no value exists for this variable, a port is assigned dynamically. - /// - private const string ResourceServiceUrlVariableName = "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL"; - /// /// Provides access to the URI at which the resource service endpoint is hosted. /// @@ -135,7 +125,9 @@ public DashboardServiceHost( void ConfigureKestrel(KestrelServerOptions kestrelOptions) { // Inspect environment for the address to listen on. - var uri = configuration.GetUri(ResourceServiceUrlVariableName); + // Prefer the new config name, falling back to the legacy name. + var uri = configuration.GetUri(KnownConfigNames.ResourceServiceEndpointUrl) + ?? configuration.GetUri(KnownConfigNames.Legacy.ResourceServiceEndpointUrl); var allowUnsecuredTransport = configuration.GetBool(KnownConfigNames.AllowUnsecuredTransport) ?? false; var scheme = ResolveScheme(uri, allowUnsecuredTransport); @@ -154,7 +146,7 @@ void ConfigureKestrel(KestrelServerOptions kestrelOptions) } else { - throw new ArgumentException($"{ResourceServiceUrlVariableName} must contain a local loopback address."); + throw new ArgumentException($"{KnownConfigNames.ResourceServiceEndpointUrl} must contain a local loopback address."); } void ConfigureListen(ListenOptions options) @@ -191,7 +183,7 @@ internal static string ResolveScheme(Uri? configuredUri, bool allowUnsecuredTran /// /// /// Intended to be used by the app model when launching the dashboard process, populating its - /// DOTNET_RESOURCE_SERVICE_ENDPOINT_URL environment variable with a single URI. + /// ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL environment variable with a single URI. /// public async Task GetResourceServiceUriAsync(CancellationToken cancellationToken = default) { From 9ff622a9f1c0863c486ea58cfe036be4c3155b9b Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 28 May 2026 08:17:29 +0800 Subject: [PATCH 2/2] Add test for configured resource service endpoint URL --- .../DistributedApplicationTests.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs b/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs index 459674e7649..bb08e172538 100644 --- a/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs +++ b/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs @@ -11,6 +11,7 @@ using System.Threading.Channels; using Aspire.Dashboard.Model; using Aspire.Hosting.Diagnostics; +using Aspire.Hosting.Dashboard; using Aspire.TestUtilities; using Aspire.Hosting.Dcp; using Aspire.Hosting.Dcp.Model; @@ -1433,6 +1434,33 @@ public async Task StartAsync_UnsecuredAllowAnonymous_PassedToDashboardProcess() } } + [Fact] + public async Task StartAsync_ResourceServiceEndpointUrl_PassedToDashboardServiceHost() + { + const string testName = "dashboard-resource-service-endpoint-url"; + var resourceServicePort = await Network.GetAvailablePortAsync(); + var configuredResourceServiceUrl = $"http://localhost:{resourceServicePort}"; + var args = new string[] { + $"{KnownConfigNames.ResourceServiceEndpointUrl}={configuredResourceServiceUrl}" + }; + using var testProgram = CreateTestProgram(testName, args: args, disableDashboard: false); + + await using var app = testProgram.Build(); + + var dashboardServiceHost = app.Services.GetRequiredService(); + await ((IHostedService)dashboardServiceHost).StartAsync(CancellationToken.None); + + try + { + var resourceServiceUri = await dashboardServiceHost.GetResourceServiceUriAsync(); + Assert.Equal(configuredResourceServiceUrl, resourceServiceUri.TrimEnd('/')); + } + finally + { + await ((IHostedService)dashboardServiceHost).StopAsync(CancellationToken.None); + } + } + [Fact] [RequiresFeature(TestFeature.Docker)] public async Task VerifyDockerWithEntrypointWorks()