On this page

Skip to content

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

TLDR

  • SSE is a unidirectional connection technology where the server actively pushes data to the client, suitable for reducing bandwidth waste and server load.
  • SSE message format must end with data: <content>\n\n, where the two newline characters signify the end of a message.
  • The server must set the Content-Type to text/event-stream and disable caching.
  • If cross-origin requests are required, set withCredentials: true in EventSource and ensure the server has CORS correctly configured.
  • In ASP.NET Core, use Response.WriteAsync combined with Response.Body.FlushAsync to continuously push data.

Comparison of SSE and Other Real-time Communication Methods

When developing real-time communication features, common technical choices include Polling, SSE, and WebSocket. Their characteristic differences are as follows:

  • Polling: The client sends requests periodically, which often leads to frequent requests and responses, consuming excessive server resources.
  • Server-Sent-Events (SSE): Establishes a unidirectional connection where the server pushes updates to the client. The advantage is that it only requires a single TCP connection, effectively reducing server load and bandwidth waste.
  • WebSocket: Establishes a bidirectional connection where both parties can send data at any time, suitable for scenarios requiring frequent bidirectional interaction.

SSE JavaScript Implementation

SSE connects via the EventSource object and has built-in open, error, and message events.

SSE Code Example

When to encounter this issue: When you need to listen to a real-time data stream from the server on the frontend.

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

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

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

// Listen to 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 to the custom end event
sse.addEventListener('end', function (e) {
  console.log('SSE custom end', e);

  sse.close();
});

withCredentials Property

When to encounter this issue: When an SSE request needs to be cross-origin and carry authentication information (such as Cookies).

When creating an EventSource object, you can use the withCredentials property. For cross-origin requests, the server must simultaneously configure CORS headers to allow authentication information.

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

WARNING

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

Implementing an SSE Server in .NET

An SSE server must return the text/event-stream format, and each message must end with an empty line (\n\n) as a terminator.

Implementation via ASHX (Generic Handler)

When to encounter this issue: 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) {
        // Set the response Content-Type to text/event-stream
        context.Response.ContentType = "text/event-stream";
        context.Response.CacheControl = "no-cache";

        // Simulate an SSE event stream, sending one message per second
        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);
        }

        // Return a custom end event; the JavaScript example above will Close SSE here
        context.Response.Write("event: end\ndata: {}\n\n");
        context.Response.Flush();
        context.Response.End();
    }

    public bool IsReusable {
        get {
            return false;
        }
    }
}

TIP

Each message ends with an empty line, so the first \n indicates a line break in the message, and the second \n indicates the end of the message.

Implementation via ASP.NET Core Web API

When to encounter this issue: In a modern ASP.NET Core architecture, when you need to handle real-time data pushing via a Controller.

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

        // Simulate an SSE event stream, sending one message per second
        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));
        }

        // Return a custom end event; the JavaScript example above will Close SSE here
        await Response.WriteAsync("event: end\ndata: {}\n\n");
        await Response.Body.FlushAsync();
    }
}

Change Log

  • 2023-03-15 Initial documentation created.
  • 2024-02-17 Updated content related to withCredentials.