Beyond WebSockets: High-Performance Real-Time Streaming with .NET 10 Server-Sent Events (SSE)
How to build a resilient, Node-free, and reactive Order Tracking system using Minimal APIs and Blazor.

📡 The Real-Time Dilemma
In order tracking systems, data freshness is everything. However, not every real-time scenario requires the full-duplex complexity of WebSockets or the heavy abstraction layer of SignalR.
For this scenario, the goal was to create a high-performance event pipeline that was:
Unidirectional by design: The server pushes, the client reacts.
Lightweight: Low memory and CPU footprint.
Resilient: Native HTTP/2 support for automatic reconnection.
The solution? Server-Sent Events (SSE) built on top of the .NET 10 infrastructure.
⚡ The Engine: System.Threading.Channels
To prevent the API from "choking" while thousands of events occur, I implemented the Producer-Consumer pattern using System.Threading.Channels. This allows the simulation endpoint (Producer) to write messages while the SSE endpoint (Consumer) reads them asynchronously without blocking the main thread.
// OrderStreamService.cs - Thread-safe event distribution
public sealed class OrderStreamService
{
private readonly ConcurrentDictionary<Guid, Channel<OrderStatusUpdate>> _streams = new();
public ChannelReader<OrderStatusUpdate> Subscribe(Guid orderId)
{
var channel = Channel.CreateUnbounded<OrderStatusUpdate>();
_streams[orderId] = channel;
return channel.Reader;
}
public async Task PublishAsync(OrderStatusUpdate update)
{
if (_streams.TryGetValue(update.OrderId, out var channel))
{
await channel.Writer.WriteAsync(update); // Non-blocking write
}
}
}
🔗 Clean Architecture: Minimal API Extensions
Instead of cluttering Program.cs, I used Extension Methods to map endpoints. This keeps the project modular. The new Results.ServerSentEvents in .NET 10 makes exposing an IAsyncEnumerable incredibly clean.
// OrderEndpoints.cs - Mapping the Stream
public static void MapOrderEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/orders");
group.MapGet("/{orderId:guid}/stream", (Guid orderId, OrderStreamService service, CancellationToken ct) =>
{
// ReadAllAsync handles the stream consumption reactively
var updateSource = service.Subscribe(orderId).ReadAllAsync(ct);
return Results.ServerSentEvents(updateSource, eventType: "order-update");
});
}
🌊 Deep Dive: The SSE Pipeline in .NET 10
Server-Sent Events are often overlooked. In .NET 10, we can expose an IAsyncEnumerable directly as an HTTP stream, leveraging native HTTP/2 multiplexing.
Unlike WebSockets, SSE features native browser reconnection and is much more friendly to firewalls and proxies. In this implementation, we ensure resource safety by passing the CancellationToken through the entire chain. If a user closes the browser tab, the server-side stream is terminated immediately, preventing memory leaks.
🛡️ Frontend Resilience: Native SSE JavaScript
One of the biggest advantages of SSE is the native support via EventSource. To bridge the gap between the browser and Blazor WebAssembly, I implemented a specialized JS wrapper. It handles the connection lifecycle and uses a signalize pattern to close the stream when a null status (final state) is received.
// sse.js - Bridging Browser and Blazor
window.setupSSE = (dotNetHelper, fullUrl) => {
const eventSource = new EventSource(fullUrl);
eventSource.addEventListener("order-update", (event) => {
const data = JSON.parse(event.data);
// Handle stream finalization signal
if (data.status === null) {
eventSource.close();
dotNetHelper.invokeMethodAsync('OnStreamStateChanged', false);
return;
}
dotNetHelper.invokeMethodAsync('OnOrderUpdateReceived', data);
});
eventSource.onerror = () => {
eventSource.close();
dotNetHelper.invokeMethodAsync('OnStreamStateChanged', false);
};
};
🎨 UI Precision with Blazor & Tailwind v4
The interface was designed for clarity and speed. By using Tailwind v4 native binaries, we eliminated the need for external frontend build tools. The CSS is compiled by specialized binaries (Windows/Linux), perfectly integrated into the project's PowerShell orchestration scripts.
In Blazor WASM, the SSE integration allows for granular UI updates. When a new event arrives through the stream, the component re-renders only the necessary fragments, providing a seamless "Live" experience:
🚀 The Tech Stack: Zero-Node Native Stack
Shifting away from heavy node_modules dependencies, this project is built for speed and native execution:
.NET 10 Minimal APIs: The event hub, optimized for ultra-low latency.
System.Threading.Channels: The heart of reactivity, serving as a thread-safe in-memory queue between producers and consumers.
Blazor WebAssembly: A precision-crafted frontend that consumes the stream natively.
Tailwind CSS v4 (Native Binaries): Next-gen visual workflow with zero Node.js dependency, utilizing native binaries for high-speed compilation.
🛠️ Verification: Playwright & xUnit (The Collision Test)
A Software Architect doesn't rely on luck. The solution features a robust testing suite where Microsoft Playwright performs the "Collision Test":
It automates the browser to the Tracking Page.
Triggers an order update simulation via the REST API.
Validates that the Blazor UI reacted instantly through the SSE stream without a single page refresh.
🏁 Conclusion: Performance Beyond JSON
In my previous article, we explored how Span<T> saves memory. Here, we explored how the right architecture saves network resources and code complexity.
.NET 10 continues to prove itself as the definitive platform for those seeking raw performance without sacrificing developer productivity.
📦 Technical Blueprint & Repository
Stack: .NET 10, Blazor WASM, SSE, Tailwind v4, Playwright.
GitHub Repository: https://github.com/andreecirillo/dotnet10-realtime-sse





