Skip to content

Commit 45898c6

Browse files
authored
Fix creating temporary dashboard client on startup (#6316)
1 parent 5deda3d commit 45898c6

File tree

10 files changed

+64
-17
lines changed

10 files changed

+64
-17
lines changed

src/Aspire.Dashboard/DashboardWebApplication.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ public DashboardWebApplication(
232232

233233
// Data from the server.
234234
builder.Services.TryAddScoped<IDashboardClient, DashboardClient>();
235+
builder.Services.TryAddSingleton<IDashboardClientStatus, DashboardClientStatus>();
235236

236237
// OTLP services.
237238
builder.Services.AddGrpc();
@@ -340,7 +341,7 @@ public DashboardWebApplication(
340341
{
341342
if (context.Request.Path.Equals(TargetLocationInterceptor.ResourcesPath, StringComparisons.UrlPath))
342343
{
343-
var client = context.RequestServices.GetRequiredService<IDashboardClient>();
344+
var client = context.RequestServices.GetRequiredService<IDashboardClientStatus>();
344345
if (!client.IsEnabled)
345346
{
346347
context.Response.Redirect(TargetLocationInterceptor.StructuredLogsPath);

src/Aspire.Dashboard/ResourceService/DashboardClient.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ namespace Aspire.Dashboard.Model;
3131
/// If the <c>DOTNET_RESOURCE_SERVICE_ENDPOINT_URL</c> environment variable is not specified, then there's
3232
/// no known endpoint to connect to, and this dashboard client will be disabled. Calls to
3333
/// <see cref="IDashboardClient.SubscribeResourcesAsync"/> and <see cref="IDashboardClient.SubscribeConsoleLogs"/>
34-
/// will throw if <see cref="IDashboardClient.IsEnabled"/> is <see langword="false"/>. Callers should
34+
/// will throw if <see cref="IDashboardClientStatus.IsEnabled"/> is <see langword="false"/>. Callers should
3535
/// check this property first, before calling these methods.
3636
/// </para>
3737
/// </remarks>
@@ -47,6 +47,7 @@ internal sealed class DashboardClient : IDashboardClient
4747
private readonly object _lock = new();
4848

4949
private readonly ILoggerFactory _loggerFactory;
50+
private readonly IDashboardClientStatus _dashboardClientStatus;
5051
private readonly BrowserTimeProvider _timeProvider;
5152
private readonly IKnownPropertyLookup _knownPropertyLookup;
5253
private readonly DashboardOptions _dashboardOptions;
@@ -71,11 +72,13 @@ public DashboardClient(
7172
ILoggerFactory loggerFactory,
7273
IConfiguration configuration,
7374
IOptions<DashboardOptions> dashboardOptions,
75+
IDashboardClientStatus dashboardClientStatus,
7476
BrowserTimeProvider timeProvider,
7577
IKnownPropertyLookup knownPropertyLookup,
7678
Action<SocketsHttpHandler>? configureHttpHandler = null)
7779
{
7880
_loggerFactory = loggerFactory;
81+
_dashboardClientStatus = dashboardClientStatus;
7982
_timeProvider = timeProvider;
8083
_knownPropertyLookup = knownPropertyLookup;
8184
_dashboardOptions = dashboardOptions.Value;
@@ -85,9 +88,7 @@ public DashboardClient(
8588

8689
_logger = loggerFactory.CreateLogger<DashboardClient>();
8790

88-
var address = _dashboardOptions.ResourceServiceClient.GetUri();
89-
90-
if (address is null)
91+
if (!_dashboardClientStatus.IsEnabled)
9192
{
9293
_state = StateDisabled;
9394
_logger.LogDebug($"{DashboardConfigNames.ResourceServiceUrlName.ConfigKey} is not specified. Dashboard client services are unavailable.");
@@ -96,6 +97,7 @@ public DashboardClient(
9697
return;
9798
}
9899

100+
var address = _dashboardOptions.ResourceServiceClient.GetUri()!;
99101
_logger.LogDebug("Dashboard configured to connect to: {Address}", address);
100102

101103
// Create the gRPC channel. This channel performs automatic reconnects.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Aspire.Dashboard.Configuration;
5+
using Microsoft.Extensions.Options;
6+
7+
namespace Aspire.Dashboard.Model;
8+
9+
internal sealed class DashboardClientStatus(IOptions<DashboardOptions> dashboardOptions) : IDashboardClientStatus
10+
{
11+
public bool IsEnabled => dashboardOptions.Value.ResourceServiceClient.GetUri() is not null;
12+
}

src/Aspire.Dashboard/ResourceService/IDashboardClient.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,8 @@ namespace Aspire.Dashboard.Model;
88
/// <summary>
99
/// Provides data about active resources to external components, such as the dashboard.
1010
/// </summary>
11-
public interface IDashboardClient : IAsyncDisposable
11+
public interface IDashboardClient : IDashboardClientStatus, IAsyncDisposable
1212
{
13-
/// <summary>
14-
/// Gets whether this client object is enabled for use.
15-
/// </summary>
16-
/// <remarks>
17-
/// Users of this client should check <see cref="IsEnabled"/> before calling
18-
/// any other members of this interface, to avoid exceptions.
19-
/// </remarks>
20-
bool IsEnabled { get; }
21-
2213
Task WhenConnected { get; }
2314

2415
/// <summary>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Aspire.Dashboard.Model;
5+
6+
public interface IDashboardClientStatus
7+
{
8+
/// <summary>
9+
/// Gets whether the client object is enabled for use.
10+
/// </summary>
11+
/// <remarks>
12+
/// Users of <see cref="IDashboardClient"/> client should check <see cref="IsEnabled"/> before calling
13+
/// any other members of this interface, to avoid exceptions.
14+
/// </remarks>
15+
bool IsEnabled { get; }
16+
}

tests/Aspire.Dashboard.Components.Tests/Controls/ApplicationNameTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public void Render_DashboardClientDisabled_Success()
2121
Services.AddSingleton<IConfiguration>(new ConfigurationManager());
2222
Services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
2323
Services.AddSingleton<IDashboardClient, DashboardClient>();
24+
Services.AddSingleton<IDashboardClientStatus, DashboardClientStatus>();
2425
Services.AddSingleton<BrowserTimeProvider>();
2526
Services.AddSingleton<IKnownPropertyLookup>(new MockKnownPropertyLookup());
2627

tests/Aspire.Dashboard.Tests/Integration/DashboardClientAuthTests.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ private static async Task<DashboardClient> CreateDashboardClientAsync(
130130
loggerFactory: loggerFactory,
131131
configuration: new ConfigurationManager(),
132132
dashboardOptions: Options.Create(options),
133+
dashboardClientStatus: new TestDashboardClientStatus(),
133134
timeProvider: new BrowserTimeProvider(NullLoggerFactory.Instance),
134135
knownPropertyLookup: new MockKnownPropertyLookup(),
135136
configureHttpHandler: handler => handler.SslOptions.RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true);
@@ -159,6 +160,11 @@ private sealed class TestCalls
159160
public Channel<ReceivedCallInfo<ApplicationInformationRequest>> ApplicationInformationCallsChannel { get; } = Channel.CreateUnbounded<ReceivedCallInfo<ApplicationInformationRequest>>();
160161
}
161162

163+
private sealed class TestDashboardClientStatus : IDashboardClientStatus
164+
{
165+
public bool IsEnabled => true;
166+
}
167+
162168
private sealed class MockDashboardService(TestCalls testCalls) : DashboardServiceBase
163169
{
164170
public override Task<ApplicationInformationResponse> GetApplicationInformation(

tests/Aspire.Dashboard.Tests/Integration/Playwright/Infrastructure/DashboardServerFixture.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public async Task InitializeAsync()
5757
preConfigureBuilder: builder =>
5858
{
5959
builder.Configuration.AddConfiguration(config);
60+
builder.Services.AddSingleton<IDashboardClientStatus, MockDashboardClientStatus>();
6061
builder.Services.AddScoped<IDashboardClient, MockDashboardClient>();
6162
});
6263

tests/Aspire.Dashboard.Tests/Integration/Playwright/Infrastructure/MockDashboardClient.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,20 @@
88

99
namespace Aspire.Dashboard.Tests.Integration.Playwright.Infrastructure;
1010

11+
public sealed class MockDashboardClientStatus : IDashboardClientStatus
12+
{
13+
public bool IsEnabled => true;
14+
}
15+
1116
public sealed class MockDashboardClient : IDashboardClient
1217
{
1318
private static readonly BrowserTimeProvider s_timeProvider = new(NullLoggerFactory.Instance);
1419

20+
public MockDashboardClient(IDashboardClientStatus dashboardClientStatus)
21+
{
22+
_dashboardClientStatus = dashboardClientStatus;
23+
}
24+
1525
public static readonly ResourceViewModel TestResource1 = ModelTestHelpers.CreateResource(
1626
appName: "TestResource",
1727
resourceType: KnownResourceTypes.Project,
@@ -32,7 +42,9 @@ public sealed class MockDashboardClient : IDashboardClient
3242
}.ToDictionary(),
3343
state: KnownResourceState.Running);
3444

35-
public bool IsEnabled => true;
45+
private readonly IDashboardClientStatus _dashboardClientStatus;
46+
47+
public bool IsEnabled => _dashboardClientStatus.IsEnabled;
3648
public Task WhenConnected => Task.CompletedTask;
3749
public string ApplicationName => "IntegrationTestApplication";
3850
public ValueTask DisposeAsync() => ValueTask.CompletedTask;

tests/Aspire.Dashboard.Tests/Model/DashboardClientTests.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ public async Task SubscribeResources_HasInitialData_InitialDataReturned()
151151

152152
private DashboardClient CreateResourceServiceClient()
153153
{
154-
return new DashboardClient(NullLoggerFactory.Instance, _configuration, _dashboardOptions, s_timeProvider, new MockKnownPropertyLookup());
154+
return new DashboardClient(NullLoggerFactory.Instance, _configuration, _dashboardOptions, new TestDashboardClientStatus(), s_timeProvider, new MockKnownPropertyLookup());
155+
}
156+
157+
private sealed class TestDashboardClientStatus : IDashboardClientStatus
158+
{
159+
public bool IsEnabled => true;
155160
}
156161
}

0 commit comments

Comments
 (0)