Skip to content

Commit d02f8be

Browse files
committed
diagnostics
1 parent 9f84b00 commit d02f8be

22 files changed

Lines changed: 2552 additions & 4 deletions

samples/EasyAppDev.Blazor.Store.Sample/Pages/Counter.razor

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@
2626
// Only re-render when Count changes (not when LastAction changes)
2727
protected override object SelectState(CounterState state)
2828
=> state.Count;
29+
30+
2931
}
3032

33+
3134
<div class="alert alert-info mt-3">
3235
<h4>Redux DevTools Integration</h4>
3336
<p>
@@ -47,3 +50,7 @@
4750
</small>
4851
</p>
4952
</div>
53+
54+
@* Diagnostic Panel - Only available in DEBUG builds *@
55+
<DiagnosticPanel DisplayMode="DiagnosticDisplayMode.Floating" />
56+

samples/EasyAppDev.Blazor.Store.Sample/Program.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,38 @@
44
using EasyAppDev.Blazor.Store.Sample.State;
55
using EasyAppDev.Blazor.Store.Core;
66
using Microsoft.JSInterop;
7+
#if DEBUG
8+
using EasyAppDev.Blazor.Store.Diagnostics;
9+
#endif
710

811
var builder = WebAssemblyHostBuilder.CreateDefault(args);
912
builder.RootComponents.Add<App>("#app");
1013
builder.RootComponents.Add<HeadOutlet>("head::after");
1114

1215
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
1316

14-
// Register store with DevTools
17+
#if DEBUG
18+
// Register diagnostics service (DEBUG only)
19+
builder.Services.AddStoreDiagnostics();
20+
#endif
21+
22+
// Register store with DevTools and Diagnostics
1523
builder.Services.AddSingleton<IStore<CounterState>>(sp =>
1624
{
1725
var jsRuntime = sp.GetRequiredService<IJSRuntime>();
18-
return StoreBuilder<CounterState>
26+
var storeBuilder = StoreBuilder<CounterState>
1927
.Create(new CounterState(0))
2028
.WithJSRuntime(jsRuntime)
2129
.WithDevTools("Counter Store")
22-
.WithLogging()
23-
.Build();
30+
.WithLogging();
31+
32+
#if DEBUG
33+
// Add diagnostics middleware in DEBUG builds
34+
var diagnosticsService = sp.GetRequiredService<IDiagnosticsService>();
35+
storeBuilder = storeBuilder.WithDiagnostics(diagnosticsService);
36+
#endif
37+
38+
return storeBuilder.Build();
2439
});
2540

2641
await builder.Build().RunAsync();

samples/EasyAppDev.Blazor.Store.Sample/_Imports.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@
1111
@using EasyAppDev.Blazor.Store.Core
1212
@using EasyAppDev.Blazor.Store.Blazor
1313
@using EasyAppDev.Blazor.Store.Sample.State
14+
@using EasyAppDev.Blazor.Store.Diagnostics.Components

samples/EasyAppDev.Blazor.Store.Sample/wwwroot/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<link rel="stylesheet" href="css/app.css" />
1111
<link rel="icon" type="image/png" href="favicon.png" />
1212
<link href="EasyAppDev.Blazor.Store.Sample.styles.css" rel="stylesheet" />
13+
<link href="_content/EasyAppDev.Blazor.Store/diagnostics.css" rel="stylesheet" />
1314
</head>
1415

1516
<body>

src/EasyAppDev.Blazor.Store/Blazor/SelectorStoreComponent.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
using Microsoft.AspNetCore.Components;
22
using EasyAppDev.Blazor.Store.Core;
3+
#if DEBUG
4+
using EasyAppDev.Blazor.Store.Diagnostics;
5+
using EasyAppDev.Blazor.Store.Diagnostics.Models;
6+
using Microsoft.Extensions.DependencyInjection;
7+
#endif
38

49
namespace EasyAppDev.Blazor.Store.Blazor;
510

@@ -32,13 +37,29 @@ public abstract class SelectorStoreComponent<TState> : ComponentBase, IDisposabl
3237
private IDisposable? _subscription;
3338
private bool _disposed;
3439
private object? _selectedValue;
40+
#if DEBUG
41+
private Guid _subscriptionId;
42+
#endif
3543

3644
/// <summary>
3745
/// Gets the injected store instance.
3846
/// </summary>
3947
[Inject]
4048
protected IStore<TState> Store { get; set; } = default!;
4149

50+
#if DEBUG
51+
/// <summary>
52+
/// Gets the injected service provider (DEBUG only).
53+
/// </summary>
54+
[Inject]
55+
private IServiceProvider ServiceProvider { get; set; } = default!;
56+
57+
/// <summary>
58+
/// Gets the diagnostics service if available (DEBUG only).
59+
/// </summary>
60+
protected IDiagnosticsService? DiagnosticsService { get; private set; }
61+
#endif
62+
4263
/// <summary>
4364
/// Gets the current state from the store.
4465
/// </summary>
@@ -147,9 +168,35 @@ protected Task UpdateAsync(Func<TState, Task<TState>> asyncUpdater, string? acti
147168
protected override void OnInitialized()
148169
{
149170
base.OnInitialized();
171+
172+
#if DEBUG
173+
// Try to resolve diagnostics service if available
174+
DiagnosticsService = ServiceProvider.GetService(typeof(IDiagnosticsService)) as IDiagnosticsService;
175+
#endif
176+
150177
SubscribeToStore();
151178
}
152179

180+
#if DEBUG
181+
/// <inheritdoc />
182+
protected override void OnAfterRender(bool firstRender)
183+
{
184+
base.OnAfterRender(firstRender);
185+
186+
if (DiagnosticsService is not null)
187+
{
188+
DiagnosticsService.RecordRender(new RenderEvent
189+
{
190+
ComponentName = GetType().Name,
191+
Timestamp = DateTime.UtcNow,
192+
IsFirstRender = firstRender,
193+
StateType = typeof(TState),
194+
Reason = firstRender ? "Initial Render" : "Selector Value Changed"
195+
});
196+
}
197+
}
198+
#endif
199+
153200
/// <summary>
154201
/// Subscribes to store changes using the selector pattern.
155202
/// Can be overridden for custom subscription logic.
@@ -159,11 +206,31 @@ protected virtual void SubscribeToStore()
159206
// Get initial selected value
160207
_selectedValue = SelectState(Store.GetState());
161208

209+
#if DEBUG
210+
_subscriptionId = Guid.NewGuid();
211+
212+
if (DiagnosticsService is not null)
213+
{
214+
DiagnosticsService.RecordSubscription(new SubscriptionInfo
215+
{
216+
SubscriptionId = _subscriptionId,
217+
StateType = typeof(TState),
218+
SubscriberName = GetType().Name,
219+
SubscriptionType = "Selector",
220+
CreatedAt = DateTime.UtcNow,
221+
NotificationCount = 0
222+
});
223+
}
224+
#endif
225+
162226
// Subscribe using the selector
163227
_subscription = Store.Subscribe(
164228
selector: SelectState,
165229
callback: value =>
166230
{
231+
#if DEBUG
232+
DiagnosticsService?.RecordSubscriptionNotification(_subscriptionId);
233+
#endif
167234
_selectedValue = value;
168235
InvokeAsync(StateHasChanged);
169236
});
@@ -187,6 +254,9 @@ protected virtual void Dispose(bool disposing)
187254

188255
if (disposing)
189256
{
257+
#if DEBUG
258+
DiagnosticsService?.RecordSubscriptionDisposed(_subscriptionId);
259+
#endif
190260
_subscription?.Dispose();
191261
_subscription = null;
192262
}

src/EasyAppDev.Blazor.Store/Blazor/StoreComponent.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
using Microsoft.AspNetCore.Components;
22
using EasyAppDev.Blazor.Store.Core;
3+
#if DEBUG
4+
using EasyAppDev.Blazor.Store.Diagnostics;
5+
using EasyAppDev.Blazor.Store.Diagnostics.Models;
6+
using Microsoft.Extensions.DependencyInjection;
7+
#endif
38

49
namespace EasyAppDev.Blazor.Store.Blazor;
510

@@ -12,13 +17,29 @@ public abstract class StoreComponent<TState> : ComponentBase, IDisposable
1217
{
1318
private IDisposable? _subscription;
1419
private bool _disposed;
20+
#if DEBUG
21+
private Guid _subscriptionId;
22+
#endif
1523

1624
/// <summary>
1725
/// Gets the injected store instance.
1826
/// </summary>
1927
[Inject]
2028
protected IStore<TState> Store { get; set; } = default!;
2129

30+
#if DEBUG
31+
/// <summary>
32+
/// Gets the injected service provider (DEBUG only).
33+
/// </summary>
34+
[Inject]
35+
private IServiceProvider ServiceProvider { get; set; } = default!;
36+
37+
/// <summary>
38+
/// Gets the diagnostics service if available (DEBUG only).
39+
/// </summary>
40+
protected IDiagnosticsService? DiagnosticsService { get; private set; }
41+
#endif
42+
2243
/// <summary>
2344
/// Gets the current state from the store.
2445
/// </summary>
@@ -80,16 +101,62 @@ protected Task UpdateAsync(Func<TState, Task<TState>> asyncUpdater, string? acti
80101
protected override void OnInitialized()
81102
{
82103
base.OnInitialized();
104+
105+
#if DEBUG
106+
// Try to resolve diagnostics service if available
107+
DiagnosticsService = ServiceProvider.GetService(typeof(IDiagnosticsService)) as IDiagnosticsService;
108+
#endif
109+
83110
SubscribeToStore();
84111
}
85112

113+
#if DEBUG
114+
/// <inheritdoc />
115+
protected override void OnAfterRender(bool firstRender)
116+
{
117+
base.OnAfterRender(firstRender);
118+
119+
if (DiagnosticsService is not null)
120+
{
121+
DiagnosticsService.RecordRender(new RenderEvent
122+
{
123+
ComponentName = GetType().Name,
124+
Timestamp = DateTime.UtcNow,
125+
IsFirstRender = firstRender,
126+
StateType = typeof(TState),
127+
Reason = firstRender ? "Initial Render" : "State Change"
128+
});
129+
}
130+
}
131+
#endif
132+
86133
/// <summary>
87134
/// Subscribes to store changes. Can be overridden for custom subscription logic.
88135
/// </summary>
89136
protected virtual void SubscribeToStore()
90137
{
138+
#if DEBUG
139+
_subscriptionId = Guid.NewGuid();
140+
141+
if (DiagnosticsService is not null)
142+
{
143+
DiagnosticsService.RecordSubscription(new SubscriptionInfo
144+
{
145+
SubscriptionId = _subscriptionId,
146+
StateType = typeof(TState),
147+
SubscriberName = GetType().Name,
148+
SubscriptionType = "Full",
149+
CreatedAt = DateTime.UtcNow,
150+
NotificationCount = 0
151+
});
152+
}
153+
#endif
154+
91155
_subscription = Store.Subscribe(_ =>
92156
{
157+
#if DEBUG
158+
DiagnosticsService?.RecordSubscriptionNotification(_subscriptionId);
159+
#endif
93160
InvokeAsync(StateHasChanged);
94161
});
95162
}
@@ -132,6 +199,9 @@ protected virtual void Dispose(bool disposing)
132199

133200
if (disposing)
134201
{
202+
#if DEBUG
203+
DiagnosticsService?.RecordSubscriptionDisposed(_subscriptionId);
204+
#endif
135205
_subscription?.Dispose();
136206
_subscription = null;
137207
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#if DEBUG
2+
namespace EasyAppDev.Blazor.Store.Diagnostics.Components;
3+
4+
/// <summary>
5+
/// Display mode for the diagnostic panel.
6+
/// </summary>
7+
public enum DiagnosticDisplayMode
8+
{
9+
/// <summary>
10+
/// Floating panel in bottom-right corner (draggable).
11+
/// </summary>
12+
Floating,
13+
14+
/// <summary>
15+
/// Inline panel embedded in page layout.
16+
/// </summary>
17+
Inline
18+
}
19+
#endif

0 commit comments

Comments
 (0)