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-Typetotext/event-streamand disable caching. - If cross-origin requests are required, set
withCredentials: trueinEventSourceand ensure the server has CORS correctly configured. - In ASP.NET Core, use
Response.WriteAsynccombined withResponse.Body.FlushAsyncto 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.
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.
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.
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.
[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.
