On this page

Skip to content

Using Server-Sent-Events (SSE) in .NET

TLDR

  • SSE is a unidirectional connection technology where the server pushes data to the client, suitable for real-time update scenarios.
  • Compared to Polling, SSE is more resource-efficient; compared to WebSocket, it is more lightweight and only requires unidirectional transmission.
  • The server must set the Content-Type to text/event-stream and ensure Cache-Control is set to no-cache.
  • Each SSE message must end with two newline characters \n\n to indicate the end of the message.
  • When using EventSource for cross-origin requests, if authentication information needs to be included, set withCredentials: true and adjust the CORS settings on the server side accordingly.

Example Project

Executable example for this article: CloudyWing/SseProgressSample.

Comparison of SSE and Other Real-Time Communication Methods

When developing real-time communication features, common technical choices include:

  • Polling: The client sends requests periodically. The disadvantage is that it generates too many invalid requests, consuming significant server resources.
  • Server-Sent-Events (SSE): A unidirectional connection where the server pushes updates to the client. The advantage is that it only requires one TCP connection, effectively reducing server load and bandwidth waste.
  • WebSocket: A bidirectional connection where both parties can send data at any time. Suitable for scenarios requiring frequent two-way interaction.

SSE JavaScript Implementation

SSE provides open, error, and message events by default for developers to listen to.

SSE Code Example

javascript
const sse = new EventSource('Your API Url');

// Listen for the open event, triggered when the connection is successful
sse.addEventListener('open', function (e) {
  console.log('SSE connection opened');
});

// Listen for the error event, triggered when a connection error occurs
sse.addEventListener('error', function (e) {
  console.log('SSE connection error');
});

// Listen for the message event, triggered when a message is received
sse.addEventListener('message', function (e) {
  console.log('SSE message received', e);

  const data = JSON.parse(e.data);
  const messageElement = document.createElement('div');
  messageElement.textContent = data.message;
  document.body.appendChild(messageElement);
});

// Listen for a custom end event
sse.addEventListener('end', function (e) {
  console.log('SSE custom end', e);
  sse.close();
});

Handling Cross-Origin Requests (CORS)

When you need to send authentication information (such as Cookies) across origins, you must use the withCredentials property when creating the EventSource.

When this issue occurs: When the frontend and backend are on different domains, and the SSE connection needs to carry user authentication information.

javascript
const sse = new EventSource('Your API Url', { withCredentials: true } );

WARNING

When using SSE with cross-origin requests, the server-side headers must also be configured with corresponding CORS settings.

Implementing an SSE Server in .NET

The SSE server must return data in text/event-stream format and use empty lines (\n\n) as message delimiters.

Implementation via ASHX (Generic Handler)

When this issue occurs: When you need to implement lightweight real-time pushing in a traditional ASP.NET Web Forms project.

csharp
public class SseHandler : IHttpHandler {
    public void ProcessRequest (HttpContext context) {
        context.Response.ContentType = "text/event-stream";
        context.Response.CacheControl = "no-cache";

        int count = 0;
        while (count < 10) {
            count++;
            context.Response.Write("data: " + "{\"message\": \"Hello SSE " + count + "\"}\n\n");
            context.Response.Flush();
            System.Threading.Thread.Sleep(1000);
        }

        context.Response.Write("event: end\ndata: {}\n\n");
        context.Response.Flush();
        context.Response.End();
    }

    public bool IsReusable => false;
}

Implementation via ASP.NET Core Web API

When this issue occurs: In a modern ASP.NET Core architecture, when you need to handle long-running progress reporting or data pushing asynchronously.

csharp
[ApiController]
[Route("[controller]")]
public class SseController : ControllerBase {
    [HttpGet]
    public async Task GetAsync() {
        Response.Headers.Add("Content-Type", "text/event-stream;");
        Response.Headers.Add("Cache-Control", "no-cache");

        int count = 0;
        while (count < 10) {
            count++;
            await Response.WriteAsync($"data: " + "{\"message\": \"Hello SSE " + count + "\"}\n\n");
            await Response.Body.FlushAsync();
            await Task.Delay(TimeSpan.FromSeconds(1));
        }

        await Response.WriteAsync("event: end\ndata: {}\n\n");
        await Response.Body.FlushAsync();
    }
}

Changelog

  • 2023-03-15 Initial documentation created.
  • 2024-02-17 Updated content related to withCredentials.
  • 2026-05-17 Added link to the GitHub example project.