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-Typetotext/event-streamand ensureCache-Controlis set tono-cache. - Each SSE message must end with two newline characters
\n\nto indicate the end of the message. - When using
EventSourcefor cross-origin requests, if authentication information needs to be included, setwithCredentials: trueand 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
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.
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.
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.
[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.