Server-Sent Events in C#: Reactive Web Without WebSocket

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why isn't your website "talking" to the user in real time yet?

\n\n

Imagine: 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\n

Previously, 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\n

Today 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\n

What are Server-Sent Events (SSE) and why it's not a "poor man's protocol"

\n\n

Server-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\n

The 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
\n\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
\n\n

How SSE works: a one-line protocol

\n\n

Everything ingenious is simple. The server sends the client a text stream with a special format:

\n\n
data: Hello, world!\\n\\n\ndata: {"message": "JSON works too"}\\n\\n\nevent: customEvent\\n\ndata: Named event\\n\\n
\n\n

Each 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.

\n\n

The client subscribes in JavaScript like this:

\n\n
const 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\n

And this works in all modern browsers. No npm packages, no WebSocket polyfills.

\n\n

Server-Sent Events in C#: Implementation on ASP.NET Core

\n\n

Let'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\n

Basic SSE Endpoint

\n\n
using 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",                

Blogs

Book Recommendations