doc(Online): update sample code#8042
Conversation
Reviewer's GuideRefactors the Online page from a DataTable/ DynamicObject-based sample to a strongly-typed ConnectionItem table with async periodic refresh using PeriodicTimer, simplifying the data model and rendering logic. Sequence diagram for Online page periodic refresh with PeriodicTimersequenceDiagram
actor User
participant Browser
participant Online
participant ConnectionService
participant PeriodicTimer
User->>Browser: Navigate /online
Browser->>Online: OnAfterRenderAsync(firstRender=true)
Online->>Online: _cancellationTokenSource ??= new CancellationTokenSource()
Online->>Online: _refreshTask = RefreshAsync(token)
activate Online
Online->>PeriodicTimer: new PeriodicTimer(10s)
loop every 10s
PeriodicTimer->>Online: WaitForNextTickAsync(token)
Online->>Online: BuildContext()
Online->>ConnectionService: read Connections
Online->>Online: InvokeAsync(StateHasChanged)
end
deactivate Online
User->>Browser: Leave page / dispose
Browser->>Online: Dispose()
Online->>Online: _cancellationTokenSource.Cancel()
Online->>Online: _refreshTask = null
File-Level Changes
|
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- In
RefreshAsync,BuildContext()is called directly on the background timer thread beforeInvokeAsync(StateHasChanged), which means component state (_items) is being mutated off the renderer thread; consider wrapping bothBuildContext()andStateHasChanged()inside a singleawait InvokeAsync(() => { BuildContext(); StateHasChanged(); });to keep state changes on the UI thread. - The current refresh logic waits for the first 10-second timer tick before populating
_items, so the table will render empty after initial navigation; consider invokingBuildContext()once during initialization/first render (or before starting the timer) so the initial data is shown immediately.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `RefreshAsync`, `BuildContext()` is called directly on the background timer thread before `InvokeAsync(StateHasChanged)`, which means component state (`_items`) is being mutated off the renderer thread; consider wrapping both `BuildContext()` and `StateHasChanged()` inside a single `await InvokeAsync(() => { BuildContext(); StateHasChanged(); });` to keep state changes on the UI thread.
- The current refresh logic waits for the first 10-second timer tick before populating `_items`, so the table will render empty after initial navigation; consider invoking `BuildContext()` once during initialization/first render (or before starting the timer) so the initial data is shown immediately.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8042 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 766 766
Lines 34154 34154
Branches 4696 4696
=========================================
Hits 34154 34154
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Updates the /online page sample to use a strongly-typed Table and simplifies the refresh logic for displaying current connections (Issue #8041).
Changes:
- Replaces the
DataTable/DynamicObject-based table with aTablebound toList<ConnectionItem>and explicitTableTemplateColumns. - Refactors periodic UI refresh from a
Task.Runloop to an asyncPeriodicTimer-driven loop. - Removes client-id row-highlighting logic and associated
WebClientServiceusage.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/BootstrapBlazor.Server/Components/Pages/Online.razor.cs | Switches to a typed in-memory list (List<ConnectionItem>) and introduces a PeriodicTimer refresh loop. |
| src/BootstrapBlazor.Server/Components/Pages/Online.razor | Rewrites the table markup to use typed items and template columns for each displayed field. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private async Task RefreshAsync(CancellationToken cancellationToken) | ||
| { | ||
| _table.Rows.Clear(); | ||
| var rows = ConnectionService.Connections.Sort(["ConnectionTime"]); | ||
| foreach (var item in rows) | ||
| { | ||
| _table.Rows.Add( | ||
| item.Id, | ||
| item.ConnectionTime, | ||
| item.LastBeatTime, | ||
| item.LastBeatTime - item.ConnectionTime, | ||
| item.ClientInfo?.Ip ?? "", | ||
| item.ClientInfo?.City ?? "", | ||
| item.ClientInfo?.OS ?? "", | ||
| item.ClientInfo?.Device.ToString() ?? "", | ||
| item.ClientInfo?.Browser ?? "", | ||
| item.ClientInfo?.Language ?? "", | ||
| item.ClientInfo?.Engine ?? "", | ||
| item.ClientInfo?.RequestUrl ?? "" | ||
| ); | ||
| } | ||
| _table.AcceptChanges(); | ||
| using var timer = new PeriodicTimer(TimeSpan.FromSeconds(10)); | ||
|
|
||
| //table | ||
| DataTableDynamicContext = new DataTableDynamicContext(_table, (context, col) => | ||
| try | ||
| { | ||
| col.Text = Localizer[col.GetFieldName()]; | ||
| if (col.GetFieldName() == "Id") | ||
| while (await timer.WaitForNextTickAsync(cancellationToken)) | ||
| { | ||
| col.Ignore = true; | ||
| BuildContext(); | ||
| await InvokeAsync(StateHasChanged); | ||
| } | ||
| else if (col.GetFieldName() == "ConnectionTime") | ||
| { | ||
| col.FormatString = "yyyy/MM/dd HH:mm:ss"; | ||
| col.Width = 118; | ||
| } | ||
| else if (col.GetFieldName() == "LastBeatTime") | ||
| { | ||
| col.FormatString = "yyyy/MM/dd HH:mm:ss"; | ||
| col.Width = 118; | ||
| } | ||
| else if (col.GetFieldName() == "Dur") | ||
| { | ||
| col.FormatString = @"dd\.hh\:mm\:ss"; | ||
| col.Width = 54; | ||
| } | ||
| else if (col.GetFieldName() == "Ip") | ||
| { | ||
| col.Template = v => builder => builder.AddContent(0, FormatIp(v)); | ||
| } | ||
| else if (col.GetFieldName() == "RequestUrl") | ||
| { | ||
| col.Template = v => builder => | ||
| { | ||
| if (v is IDynamicObject val) | ||
| { | ||
| var url = val.GetValue("RequestUrl")?.ToString(); | ||
| if (!string.IsNullOrEmpty(url)) | ||
| { | ||
| builder.AddContent(0, new MarkupString($"<a href=\"{url}\" target=\"_blank\">{url}</a>")); | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| }); | ||
| } | ||
| catch (OperationCanceledException) { } | ||
| } |
Link issues
fixes #8041
Summary By Copilot
Regression?
Risk
Verification
Packaging changes reviewed?
☑️ Self Check before Merge
Summary by Sourcery
Refactor the Online connections page to use a strongly-typed table and an async periodic refresh loop for displaying connection data.
Enhancements: