Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Aspire.Dashboard/DashboardWebApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ public DashboardWebApplication(

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

// OTLP services.
builder.Services.AddGrpc();
Expand Down Expand Up @@ -340,7 +341,7 @@ public DashboardWebApplication(
{
if (context.Request.Path.Equals(TargetLocationInterceptor.ResourcesPath, StringComparisons.UrlPath))
{
var client = context.RequestServices.GetRequiredService<IDashboardClient>();
var client = context.RequestServices.GetRequiredService<IDashboardClientStatus>();
if (!client.IsEnabled)
{
context.Response.Redirect(TargetLocationInterceptor.StructuredLogsPath);
Expand Down
10 changes: 6 additions & 4 deletions src/Aspire.Dashboard/ResourceService/DashboardClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ namespace Aspire.Dashboard.Model;
/// If the <c>DOTNET_RESOURCE_SERVICE_ENDPOINT_URL</c> environment variable is not specified, then there's
/// no known endpoint to connect to, and this dashboard client will be disabled. Calls to
/// <see cref="IDashboardClient.SubscribeResourcesAsync"/> and <see cref="IDashboardClient.SubscribeConsoleLogs"/>
/// will throw if <see cref="IDashboardClient.IsEnabled"/> is <see langword="false"/>. Callers should
/// will throw if <see cref="IDashboardClientStatus.IsEnabled"/> is <see langword="false"/>. Callers should
/// check this property first, before calling these methods.
/// </para>
/// </remarks>
Expand All @@ -47,6 +47,7 @@ internal sealed class DashboardClient : IDashboardClient
private readonly object _lock = new();

private readonly ILoggerFactory _loggerFactory;
private readonly IDashboardClientStatus _dashboardClientStatus;
private readonly BrowserTimeProvider _timeProvider;
private readonly IKnownPropertyLookup _knownPropertyLookup;
private readonly DashboardOptions _dashboardOptions;
Expand All @@ -71,11 +72,13 @@ public DashboardClient(
ILoggerFactory loggerFactory,
IConfiguration configuration,
IOptions<DashboardOptions> dashboardOptions,
IDashboardClientStatus dashboardClientStatus,
BrowserTimeProvider timeProvider,
IKnownPropertyLookup knownPropertyLookup,
Action<SocketsHttpHandler>? configureHttpHandler = null)
{
_loggerFactory = loggerFactory;
_dashboardClientStatus = dashboardClientStatus;
_timeProvider = timeProvider;
_knownPropertyLookup = knownPropertyLookup;
_dashboardOptions = dashboardOptions.Value;
Expand All @@ -85,9 +88,7 @@ public DashboardClient(

_logger = loggerFactory.CreateLogger<DashboardClient>();

var address = _dashboardOptions.ResourceServiceClient.GetUri();

if (address is null)
if (!_dashboardClientStatus.IsEnabled)
{
_state = StateDisabled;
_logger.LogDebug($"{DashboardConfigNames.ResourceServiceUrlName.ConfigKey} is not specified. Dashboard client services are unavailable.");
Expand All @@ -96,6 +97,7 @@ public DashboardClient(
return;
}

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

// Create the gRPC channel. This channel performs automatic reconnects.
Expand Down
12 changes: 12 additions & 0 deletions src/Aspire.Dashboard/ResourceService/DashboardClientStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Dashboard.Configuration;
using Microsoft.Extensions.Options;

namespace Aspire.Dashboard.Model;

internal sealed class DashboardClientStatus(IOptions<DashboardOptions> dashboardOptions) : IDashboardClientStatus
{
public bool IsEnabled => dashboardOptions.Value.ResourceServiceClient.GetUri() is not null;
}
11 changes: 1 addition & 10 deletions src/Aspire.Dashboard/ResourceService/IDashboardClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,8 @@ namespace Aspire.Dashboard.Model;
/// <summary>
/// Provides data about active resources to external components, such as the dashboard.
/// </summary>
public interface IDashboardClient : IAsyncDisposable
public interface IDashboardClient : IDashboardClientStatus, IAsyncDisposable
{
/// <summary>
/// Gets whether this client object is enabled for use.
/// </summary>
/// <remarks>
/// Users of this client should check <see cref="IsEnabled"/> before calling
/// any other members of this interface, to avoid exceptions.
/// </remarks>
bool IsEnabled { get; }

Task WhenConnected { get; }

/// <summary>
Expand Down
16 changes: 16 additions & 0 deletions src/Aspire.Dashboard/ResourceService/IDashboardClientStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Aspire.Dashboard.Model;

public interface IDashboardClientStatus
{
/// <summary>
/// Gets whether the client object is enabled for use.
/// </summary>
/// <remarks>
/// Users of <see cref="IDashboardClient"/> client should check <see cref="IsEnabled"/> before calling
/// any other members of this interface, to avoid exceptions.
/// </remarks>
bool IsEnabled { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ private static async Task<DashboardClient> CreateDashboardClientAsync(
loggerFactory: loggerFactory,
configuration: new ConfigurationManager(),
dashboardOptions: Options.Create(options),
dashboardClientStatus: new TestDashboardClientStatus(),
timeProvider: new BrowserTimeProvider(NullLoggerFactory.Instance),
knownPropertyLookup: new MockKnownPropertyLookup(),
configureHttpHandler: handler => handler.SslOptions.RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true);
Expand Down Expand Up @@ -159,6 +160,11 @@ private sealed class TestCalls
public Channel<ReceivedCallInfo<ApplicationInformationRequest>> ApplicationInformationCallsChannel { get; } = Channel.CreateUnbounded<ReceivedCallInfo<ApplicationInformationRequest>>();
}

private sealed class TestDashboardClientStatus : IDashboardClientStatus
{
public bool IsEnabled => true;
}

private sealed class MockDashboardService(TestCalls testCalls) : DashboardServiceBase
{
public override Task<ApplicationInformationResponse> GetApplicationInformation(
Expand Down
7 changes: 6 additions & 1 deletion tests/Aspire.Dashboard.Tests/Model/DashboardClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ public async Task SubscribeResources_HasInitialData_InitialDataReturned()

private DashboardClient CreateResourceServiceClient()
{
return new DashboardClient(NullLoggerFactory.Instance, _configuration, _dashboardOptions, s_timeProvider, new MockKnownPropertyLookup());
return new DashboardClient(NullLoggerFactory.Instance, _configuration, _dashboardOptions, new TestDashboardClientStatus(), s_timeProvider, new MockKnownPropertyLookup());
}

private sealed class TestDashboardClientStatus : IDashboardClientStatus
{
public bool IsEnabled => true;
}
}