SignalR End-to-End — Verifying Real-Time Flow and Scaling Considerations

End-to-end SignalR verification confirms that real-time events flow correctly across multiple browser clients. The scaling discussion is critical for production: in-memory SignalR (the default) works only for a single-server deployment. As soon as the application runs on multiple instances (Azure App Service scale-out, Kubernetes pods), clients connected to different instances cannot communicate. Azure SignalR Service solves this with a managed backplane — a single configuration change.

Multi-Tab Verification

-- ── End-to-end verification checklist ────────────────────────────────────

-- 1. OPEN TWO TABS viewing the same post (e.g., /posts/getting-started-dotnet)
--    Tab 1: Browser A (Chrome)
--    Tab 2: Browser B (Firefox, to ensure different sessions)
--    Both should show viewer count = 2 within 1-2 seconds of opening Tab 2

-- 2. INSPECT WebSocket connection (DevTools → Network → filter "WS")
--    Click the /hubs/blog entry
--    Tab "Messages" shows WebSocket frames:
--    ↑ (outgoing): JoinPostRoom invocation: {"type":1,"target":"JoinPostRoom","arguments":[42]}
--    ↓ (incoming): UpdateViewerCount: {"type":1,"target":"UpdateViewerCount","arguments":[42,2]}

-- 3. SUBMIT A COMMENT in Tab 1:
--    Tab 1: comment appears immediately (optimistic update)
--    Tab 2: comment appears within milliseconds (SignalR broadcast)
--    DevTools Tab 2: incoming ReceiveComment frame visible in WS messages

-- 4. VERIFY AUTH: open an Incognito tab (unauthenticated), navigate to the post
--    The CommentsComponent shows "Sign in to leave a comment" (no form)
--    In browser console: hub connection starts but JoinPostRoom succeeds ([AllowAnonymous])
--    Viewer count updates to 3 (incognito tab joined the room)

-- 5. SIMULATE DISCONNECT: Tab 2, DevTools → Network → "Offline"
--    Tab 2 connection state → Reconnecting (visible in debug overlay if implemented)
--    DevTools → Network → back "Online"
--    Tab 2 automatically reconnects within ~2 seconds
--    New comments submitted while Tab 2 was offline are NOT automatically replayed
--    (implement REST API refresh on reconnect to resync missed messages)

-- 6. VERIFY NOTIFICATION: log in as User A in Tab 1, as User B in Tab 2
--    User B views a post authored by User A
--    User B submits a comment
--    User A's notification bell badge increments without page reload
--    Click the bell: the new notification appears in the dropdown

Azure SignalR Service — Production Scaling

// ── Install: dotnet add package Microsoft.Azure.SignalR ────────────────────

// ── Program.cs — swap AddSignalR() with Azure SignalR ─────────────────────
if (builder.Environment.IsProduction())
{
    // Azure SignalR Service replaces in-process WebSocket management
    builder.Services.AddSignalR()
        .AddAzureSignalR(builder.Configuration["Azure:SignalRConnectionString"]);
}
else
{
    // Local development — in-memory (single process only)
    builder.Services.AddSignalR(opts =>
        opts.EnableDetailedErrors = true);
}

// ── That's it — no changes to hub code or Angular client ──────────────────
// Azure SignalR Service:
// - Routes all WebSocket connections through Azure's managed service
// - Each app instance communicates with the service, not directly with clients
// - Supports 100,000+ concurrent connections per unit (vs ~500 per server)
// - Scales horizontally without sticky sessions
// - Built-in retry and reconnection handling
// - Connection string from Azure Portal → SignalR Service → Keys
Note: When using in-memory SignalR (default) with multiple app instances and a load balancer, clients connected to Instance A cannot receive messages sent to groups on Instance B. This is the sticky sessions problem — a client must always connect to the same server instance. Azure SignalR Service eliminates this entirely — all instances connect to the service, which routes messages correctly regardless of which instance each client connected to. This is why the single configuration change of .AddAzureSignalR() is so valuable for production scale.
Tip: For missed events during reconnection, implement a “resync on reconnect” pattern: when SignalR fires onreconnected(), the Angular service fetches the latest data from the REST API. For comments, call commentsApi.getByPost(postId) and merge any new comments since the last known comment ID. For notifications, call notifApi.getUnread() to pick up any notifications delivered while disconnected. This ensures no data is lost between a disconnection and reconnection even if SignalR missed events cannot be replayed.
Warning: The Azure SignalR Service connection string contains a shared access key. Store it in environment variables or Azure Key Vault — never commit it. Azure SignalR Service also supports Managed Identity authentication: .AddAzureSignalR(opts => opts.ConnectionString = null, opts.Endpoints = [new ServiceEndpoint(new Uri(endpoint), new DefaultAzureCredential())]). This eliminates the shared key entirely, using the App Service’s Managed Identity for authentication to the SignalR Service.

SignalR Transport Fallback Order

Transport Full-duplex Performance Fallback When
WebSockets ✅ Yes Excellent Preferred — used by default
Server-Sent Events ❌ Server→Client only Good WS blocked by proxy/firewall
Long Polling ❌ Simulated Poor SSE not supported

Common Mistakes

Mistake 1 — Deploying in-memory SignalR with multiple instances (messages lost)

❌ Wrong — 3 app instances; client on Instance A comments; only clients on Instance A receive the broadcast.

✅ Correct — Azure SignalR Service or Redis backplane for multi-instance; all clients receive all messages.

Mistake 2 — No resync on reconnect (missed messages during disconnect lost forever)

❌ Wrong — reconnection restores WebSocket but missed comments during disconnect never appear.

✅ Correct — on onreconnected(), fetch latest data from REST API to fill the gap.

🧠 Test Yourself

The BlogApp runs on 3 Azure App Service instances with Azure SignalR Service. User A (on Instance 1) and User B (on Instance 3) are both viewing Post 42. User A submits a comment. Does User B receive it?