Why isn't your website "talking" to the user in real time yet?
\n\nImagine: you open a stock exchange terminal, and the dollar exchange rate updates by itself. Or you log into an admin panel and see the number of registered users growing right before your eyes. Or a chat where messages arrive without constant page "refreshes."
\n\nPreviously, this required either setting up WebSocket (complex, expensive, not always necessary) or using long polling (a workaround that kills your phone's battery). But there is a third way — Server-Sent Events in C#.
\n\nToday we will break down a technology that allows the server to independently "reach out" to the client and transmit data. Without extra code, without tons of JavaScript, without the headache of proxy servers. Just C#, just hardcore.
\n\nWhat are Server-Sent Events (SSE) and why it's not a "poor man's protocol"
\n\nServer-Sent Events (or SSE) is an HTML5 standard that allows the server to send streaming data to the client over a regular HTTP connection. The client subscribes to events, and the server simply writes to the Response.Body. That's it.
\n\nThe main difference from WebSocket: SSE is a one-way channel (server → client). Don't need to send data back? Great, SSE is your choice. Moreover:
\n\n- \n
- Works over regular HTTP/1.1 and HTTP/2 \n
- Automatically reconnects on disconnect (built into the browser's
EventSource) \n - Does not require additional libraries on the client (besides standard JavaScript) \n
- Passes through any proxies and load balancers (unlike WebSocket) \n
In practice, SSE C# is used for:
\n- \n
- Notification feeds (push notifications directly to the browser) \n
- Server monitoring (dashboards with live metrics) \n
- Log streaming (e.g., tail -f in the browser) \n
- Streaming AI generations (gradual output from ChatGPT-like models) \n
How SSE works: a one-line protocol
\n\nEverything ingenious is simple. The server sends the client a text stream with a special format:
\n\ndata: Hello, world!\\n\\n\ndata: {"message": "JSON works too"}\\n\\n\nevent: customEvent\\n\ndata: Named event\\n\\n\n\nEach message starts with data:, ends with two line breaks. You can specify event: for a custom event type, id: for an identifier, and retry: for a reconnection timeout.
The client subscribes in JavaScript like this:
\n\nconst source = new EventSource("/api/stream");\nsource.onmessage = (event) => {\n console.log("Received:", event.data);\n};\nsource.addEventListener("customEvent", (event) => {\n console.log("Custom event:", event.data);\n});\n\nAnd this works in all modern browsers. No npm packages, no WebSocket polyfills.
\n\nServer-Sent Events in C#: Implementation on ASP.NET Core
\n\nLet's finally write some code. The simplest way to organize SSE in C# is an endpoint in ASP.NET Core that does not complete the response but writes to it in chunks.
\n\nBasic SSE Endpoint
\n\nusing Microsoft.AspNetCore.Mvc;\n\n[ApiController]\n[Route("api")]\npublic class StreamController : ControllerBase\n{\n [HttpGet("stream")]\n public async Task Get(CancellationToken cancellationToken)\n {\n Response.Headers.Append("Content-Type", "text/event-stream");\n Response.Headers.Append("Cache-Control", "no-cache");\n Response.Headers.Append("Connection", "keep-alive");\n\n for (int i = 0; i < 10; i++)\n {\n if (cancellationToken.IsCancellationRequested)\n break;\n\n // SSE format: data: ... \\n\\n\n await Response.WriteAsync($"data: Message #{i}\\n\\n",