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: 3 additions & 0 deletions src/TickerQ.Dashboard/DashboardOptionsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public class DashboardOptionsBuilder
/// Separate from request serialization options to prevent user configuration from breaking dashboard APIs.
/// </summary>
internal JsonSerializerOptions DashboardJsonOptions { get; set; }

/// <summary>Tracks whether dashboard middleware has been applied to prevent double registration.</summary>
internal bool MiddlewareApplied { get; set; }

public void SetCorsPolicy(Action<CorsPolicyBuilder> corsPolicyBuilder)
=> CorsPolicyBuilder = corsPolicyBuilder;
Expand Down
25 changes: 15 additions & 10 deletions src/TickerQ.Dashboard/DependencyInjection/ServiceExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
using Microsoft.Extensions.DependencyInjection;
using TickerQ.Dashboard.Endpoints;
using TickerQ.Dashboard.Hubs;
using TickerQ.Dashboard.Infrastructure;
using TickerQ.Dashboard.Infrastructure.Dashboard;
using TickerQ.Dashboard.Authentication;
using TickerQ.Utilities;
using TickerQ.Utilities.Interfaces;
using System;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection.Extensions;
using TickerQ.Utilities.Entities;

Expand All @@ -15,7 +17,7 @@
{
public static class ServiceExtensions
{
public static TickerOptionsBuilder<TTimeTicker, TCronTicker> AddDashboard<TTimeTicker, TCronTicker>(this TickerOptionsBuilder<TTimeTicker, TCronTicker> tickerConfiguration, Action<DashboardOptionsBuilder> configureDashboard = null)

Check warning on line 20 in src/TickerQ.Dashboard/DependencyInjection/ServiceExtensions.cs

View workflow job for this annotation

GitHub Actions / PR Build and Test

Cannot convert null literal to non-nullable reference type.
where TTimeTicker : TimeTickerEntity<TTimeTicker>, new()
where TCronTicker : CronTickerEntity, new()
{
Expand Down Expand Up @@ -59,6 +61,10 @@

services.AddDashboardService<TTimeTicker, TCronTicker>(dashboardConfig);
services.AddSingleton<DashboardOptionsBuilder>(_ => dashboardConfig);

// Register IStartupFilter for old Startup.cs pattern where IHost != IApplicationBuilder.
// In the new WebApplication pattern, UseDashboardDelegate handles it directly.
services.AddSingleton<IStartupFilter>(new DashboardStartupFilter<TTimeTicker, TCronTicker>(dashboardConfig));
};

UseDashboardDelegate(tickerConfiguration, dashboardConfig);
Expand All @@ -72,16 +78,15 @@
{
tickerConfiguration.UseDashboardApplication((appObj) =>
{
if (appObj is not IApplicationBuilder app)
throw new InvalidOperationException(
"TickerQ Dashboard can only be used in ASP.NET Core applications. " +
"The current host does not provide an HTTP application pipeline " +
"(IApplicationBuilder is not available). " +
"If you are running a Worker Service, Console app, or background node, " +
"remove the dashboard configuration or move it to a WebApplication."
);
// Configure static files and middleware with endpoints
app.UseDashboardWithEndpoints<TTimeTicker, TCronTicker>(dashboardConfig);
if (appObj is IApplicationBuilder app)
{
// New WebApplication pattern: WebApplication implements both IHost and IApplicationBuilder.
// Mark as applied so DashboardStartupFilter skips duplicate registration.
dashboardConfig.MiddlewareApplied = true;
app.UseDashboardWithEndpoints<TTimeTicker, TCronTicker>(dashboardConfig);
}
// Old Startup.cs pattern: IHost is not IApplicationBuilder.
// Dashboard middleware is injected via IStartupFilter registered in AddDashboard.
});
}
}
Expand Down
34 changes: 34 additions & 0 deletions src/TickerQ.Dashboard/Infrastructure/DashboardStartupFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using TickerQ.Dashboard.DependencyInjection;
using TickerQ.Utilities.Entities;

namespace TickerQ.Dashboard.Infrastructure;

internal class DashboardStartupFilter<TTimeTicker, TCronTicker> : IStartupFilter
where TTimeTicker : TimeTickerEntity<TTimeTicker>, new()
where TCronTicker : CronTickerEntity, new()
{
private readonly DashboardOptionsBuilder _config;

public DashboardStartupFilter(DashboardOptionsBuilder config)
{
_config = config;
}

public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return app =>
{
// Only apply if not already applied by UseDashboardDelegate (new WebApplication pattern)
if (!_config.MiddlewareApplied)
{
_config.MiddlewareApplied = true;
app.UseDashboardWithEndpoints<TTimeTicker, TCronTicker>(_config);
}

next(app);
};
}
}
Loading