Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -21,6 +21,7 @@ public void Render_DashboardClientDisabled_Success()
Services.AddSingleton<IConfiguration>(new ConfigurationManager());
Services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
Services.AddSingleton<IDashboardClient, DashboardClient>();
Services.AddSingleton<IDashboardClientStatus, DashboardClientStatus>();
Services.AddSingleton<BrowserTimeProvider>();
Services.AddSingleton<IKnownPropertyLookup>(new MockKnownPropertyLookup());

Expand Down
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
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public async Task InitializeAsync()
preConfigureBuilder: builder =>
{
builder.Configuration.AddConfiguration(config);
builder.Services.AddSingleton<IDashboardClientStatus, MockDashboardClientStatus>();
builder.Services.AddScoped<IDashboardClient, MockDashboardClient>();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,20 @@

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

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

public sealed class MockDashboardClient : IDashboardClient
{
private static readonly BrowserTimeProvider s_timeProvider = new(NullLoggerFactory.Instance);

public MockDashboardClient(IDashboardClientStatus dashboardClientStatus)
{
_dashboardClientStatus = dashboardClientStatus;
}

public static readonly ResourceViewModel TestResource1 = ModelTestHelpers.CreateResource(
appName: "TestResource",
resourceType: KnownResourceTypes.Project,
Expand All @@ -32,7 +42,9 @@ public sealed class MockDashboardClient : IDashboardClient
}.ToDictionary(),
state: KnownResourceState.Running);

public bool IsEnabled => true;
private readonly IDashboardClientStatus _dashboardClientStatus;

public bool IsEnabled => _dashboardClientStatus.IsEnabled;
public Task WhenConnected => Task.CompletedTask;
public string ApplicationName => "IntegrationTestApplication";
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
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;
}
}