Repository navigation
feat(SignalR): add Dispatch/Connection services for wasm mode - #1257
Merged
Merged
Conversation
Reviewer's GuideAdds a WebAssembly SignalR client package with a shared reconnecting Hub connection, typed dispatch subscriptions, heartbeat reporting, and connection-snapshot polling, while updating middleware to expose the shared snapshot contract and client-facing Hub behavior. Sequence diagram for SignalR dispatch subscription and deliverysequenceDiagram
participant App as WASM application
participant Dispatch as DefaultDispatchService
participant Connection as SignalRConnection
participant Hub as BootstrapBlazorHub
participant Subscriber as Local callback
App->>Dispatch: SubscribeAsync(callback)
Dispatch->>Connection: SubscribeAsync(entryType, ReceiveAsync)
Connection->>Hub: Subscribe(entryType)
Hub-->>Connection: true
App->>Dispatch: DispatchAsync(entry)
Dispatch->>Connection: DispatchAsync(entryType, entry)
Connection->>Hub: Dispatch(entryType, entry)
Hub-->>Connection: Receive(entryType, JSON)
Connection->>Dispatch: ReceiveAsync(JSON)
Dispatch->>Subscriber: callback(entry)
Sequence diagram for connection heartbeat and snapshot pollingsequenceDiagram
participant Client as WASM client
participant Connection as DefaultConnectionService
participant SignalR as SignalRConnection
participant Hub as BootstrapBlazorHub
participant Store as ConnectionHandler
Client->>Connection: AddOrUpdateAsync(client)
Connection->>SignalR: InvokeAsync(Heartbeat, client)
SignalR->>Hub: Heartbeat(client)
Hub->>Store: ReportHeartbeatAsync(client)
loop Every 3 seconds
Connection->>SignalR: InvokeAsync(GetConnectionSnapshot)
SignalR->>Hub: GetConnectionSnapshot()
Hub->>Store: GetSnapshotAsync()
Store-->>Hub: ConnectionSnapshot
Hub-->>SignalR: ConnectionSnapshot
SignalR-->>Connection: Update local snapshot
end
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 6 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="src/middleware/BootstrapBlazor.Middleware/ConnectionHandler.cs" line_range="27-30" />
<code_context>
return false;
}
- if (client == null || string.IsNullOrWhiteSpace(client.Id) || client.Id.Length > 128 ||
- !Enum.IsDefined(typeof(WebClientDeviceType), client.Device))
- {
- throw new HubException("Invalid connection heartbeat.");
- }
-
</code_context>
<issue_to_address>
**Malformed heartbeats enter storage**
When a caller sends a null `ClientInfo`, a blank or oversized ID, or an undefined device value, `ReportHeartbeatAsync` dereferences a null `client` and forwards invalid IDs or device values to `IConnectionService`, so null calls fail and malformed records enter connection storage.
Restore validation of the client, ID, and device value before assigning fields or calling the connection service.
</issue_to_address>
### Comment 2
<location path="src/extensions/BootstrapBlazor.SignalR.Client/SignalRConnection.cs" line_range="204" />
<code_context>
+ try
+ {
+ await EnsureConnectedAsync(token);
+ await _connection.InvokeAsync("Dispatch", entryType, entry, token);
+ }
+ finally
</code_context>
<issue_to_address>
**Slow dispatch blocks shared connection**
When the server's `Dispatch` method or an awaited publish handler stalls, `DispatchAsync` invokes the Hub directly without a timeout, so a server dispatch that never completes holds `_gate` indefinitely. All later operations using the shared connection then wait on that gate, and dispatch callers with the default cancellation token remain stuck.
Apply a bounded timeout to the dispatch invocation, or otherwise ensure a stalled dispatch cannot hold the shared-operation gate indefinitely.
</issue_to_address>
### Comment 3
<location path="src/extensions/BootstrapBlazor.SignalR.Client/SignalRConnection.cs" line_range="64" />
<code_context>
+ {
+ _logger = logger;
+ _connection = new HubConnectionBuilder()
+ .WithUrl(navigation.ToAbsoluteUri(hubPath))
+ .WithAutomaticReconnect(new ConnectionRetryPolicy())
+ .Build();
</code_context>
<issue_to_address>
**Bearer-only Hub calls fail**
When the Hub endpoint requires a bearer token and does not accept browser cookies, `SignalRConnection` builds the Hub URL with `WithUrl` but supplies no access-token provider, and `AddBootstrapBlazorHub` exposes no authentication configuration, so bearer-only WebAssembly clients cannot authorize Hub operations.
Provide an access-token provider and expose the required authentication configuration to Hub clients.
Also at `src/extensions/BootstrapBlazor.SignalR.Client/ServiceCollectionExtensions.cs:22`.
</issue_to_address>
### Comment 4
<location path="src/extensions/BootstrapBlazor.SignalR.Client/SignalRConnection.cs" line_range="175-179" />
<code_context>
+ using var cancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _lifetime.Token);
+ cancellation.CancelAfter(TimeSpan.FromSeconds(30));
+ var token = cancellation.Token;
+ await _gate.WaitAsync(token);
+ try
+ {
+ await EnsureConnectedAsync(token);
+ return await _connection.InvokeCoreAsync<TResult>(method, argument == null ? [] : [argument], token);
+ }
+ finally
</code_context>
<issue_to_address>
**Slow snapshots block dispatches**
When a connection snapshot request takes several seconds or reaches its 30-second timeout, `InvokeAsync` holds `_gate` for the entire Hub request, so a slow snapshot poll blocks dispatch, heartbeat, and subscription operations waiting for that gate; a timed-out poll can delay them for up to 30 seconds.
Avoid holding the shared-operation gate while awaiting Hub responses, while retaining synchronization around connection-state changes.
</issue_to_address>
### Comment 5
<location path="src/extensions/BootstrapBlazor.SignalR.Client/ServiceCollectionExtensions.cs" line_range="30" />
<code_context>
+ /// <para lang="zh">服务端需注册并映射对应 Hub,Subscribe 方法需返回 bool,以区分订阅拒绝与可重试的执行失败。连接快照查询需在服务端单独启用并授权客户端访问</para>
+ /// <para lang="en">The server must register and map a corresponding Hub whose Subscribe method returns bool to distinguish subscription rejection from retryable execution failures. Connection snapshot queries require explicit server-side opt-in and client authorization</para>
+ /// </remarks>
+ public static IServiceCollection AddBootstrapBlazorHub(this IServiceCollection services, string hubPath = "bb.hub")
+ {
+#if NET7_0_OR_GREATER
</code_context>
<issue_to_address>
**Hub URL breaks under app base path**
When the WebAssembly app is hosted beneath a non-root base path and the caller uses the default `hubPath`, `NavigationManager.ToAbsoluteUri` resolves the default relative `bb.hub` beneath the WebAssembly app’s base path, while the server maps the default endpoint at `/bb.hub`; the client therefore connects to a nonexistent endpoint and its dispatch, heartbeat, and snapshot operations fail.
Make the client default an absolute-root path (`/bb.hub`) or resolve the configured path consistently with the server endpoint mapping.
Also at `src/extensions/BootstrapBlazor.SignalR.Client/SignalRConnection.cs:64`.
</issue_to_address>
### Comment 6
<location path="src/extensions/BootstrapBlazor.SignalR.Client/DefaultDispatchService.cs" line_range="40" />
<code_context>
+ public async Task DispatchAsync(DispatchEntry<TEntry> entry, CancellationToken cancellationToken = default)
+ {
+ await connection.DispatchAsync(_entryType, entry, cancellationToken);
+ await DispatchCoreAsync(entry, cancellationToken);
+ }
+
</code_context>
<issue_to_address>
**Dispatch callbacks observe messages out of order**
When multiple entries are dispatched concurrently, or receive callbacks overlap, and subscribers rely on entry order, `DispatchAsync` sends each entry through the shared connection, then invokes `DispatchCoreAsync` without serializing that local delivery with other sends; concurrent callers can therefore complete their local callbacks in the opposite order from their server invocations. Concurrent receive callbacks also enter `DispatchCoreAsync` independently, so subscribers can apply later entries before earlier ones and end up with incorrect state.
Serialize entry delivery per service, including the send-to-local-delivery transition, or explicitly provide ordering metadata and have consumers reject stale entries.
Also at `src/extensions/BootstrapBlazor.SignalR.Client/DefaultDispatchService.cs:172`.
</issue_to_address># Conflicts: # src/middleware/BootstrapBlazor.Middleware/BootstrapBlazor.Middleware.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Link issues
fixes #1254
Summary By Copilot
Regression?
Risk
Verification
Packaging changes reviewed?
☑️ Self Check before Merge
Summary by Sourcery
Add shared SignalR-backed dispatch and connection services for WebAssembly clients.
New Features:
Enhancements:
Build: