Skip to content

GabrieleTronchin/ServerSentEvents

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Server-Sent Events (SSE) Demo with .NET

.NET 10 Project Status

Introduction

Server-Sent Events (SSE) is a standard protocol that enables a server to push real-time updates to clients over a single, long-lived HTTP connection. Unlike WebSockets, SSE is unidirectional — data flows only from the server to the client — making it a lightweight and straightforward choice for scenarios where the client simply needs to receive a continuous stream of updates without sending data back.

This project is a demo API built with ASP.NET Core Minimal APIs on .NET 10. It exposes a single /SSE endpoint that streams randomly generated notification messages (powered by the Bogus library) using the text/event-stream content type. The goal is to provide a clear, minimal example of how to implement server-sent events in a .NET application, useful for learning, prototyping, and testing SSE-based integrations.

Real-Time Solutions Comparison

When building applications that need to push data from the server to the client, there are three main approaches to consider:

Approach How It Works Direction Trade-offs
HTTP Polling The client sends repeated requests at a fixed interval to check for new data. Client → Server Simple to implement, but wasteful — most requests return empty responses, adding unnecessary network overhead and latency.
WebSocket / SignalR A full-duplex connection is established over a single TCP socket, allowing both the client and server to send messages at any time. In .NET, SignalR abstracts WebSocket management with fallback transports. Bidirectional Powerful and flexible, but adds complexity in connection management, protocol negotiation, and infrastructure (load balancers, proxies). Overkill when the client never needs to send data back.
Server-Sent Events (SSE) The client opens a standard HTTP GET request and the server keeps the connection alive, writing data: frames as new events become available. Built on the text/event-stream content type. Server → Client Lightweight, uses plain HTTP, and includes automatic reconnection via the browser's EventSource API. Limited to text-based, unidirectional streaming.

Why SSE?

For scenarios where the server is the sole producer of data and the client simply consumes updates, SSE is the most practical choice:

  • Lower overhead than polling — SSE maintains a single persistent connection instead of opening and closing a new HTTP request on every interval. This eliminates redundant request/response cycles, reduces bandwidth consumption, and delivers updates with near-zero latency.
  • Simpler than WebSocket — SSE works over standard HTTP, so it passes through firewalls, proxies, and load balancers without special configuration. There is no protocol upgrade handshake, no frame masking, and no need to manage a bidirectional channel when only the server needs to send data.
  • Native browser support — The EventSource API provides built-in connection management with automatic reconnection and last-event-ID tracking, requiring minimal client-side code.
  • Sufficient for unidirectional use cases — Dashboards, live feeds, notification streams, and progress indicators all follow the same pattern: the server pushes, the client listens. SSE fits this pattern exactly, without the added complexity of a bidirectional protocol.

This project uses SSE because the demo scenario — streaming server-generated notifications to a client — is purely unidirectional. WebSocket and SignalR would introduce unnecessary complexity, while HTTP polling would waste resources on empty responses.

Features

  • Real-time streaming — The /SSE endpoint pushes server-generated events to clients over a persistent HTTP connection using the text/event-stream content type.
  • Mock data generation — Notification messages with random titles and content are generated on the fly using the Bogus library, simulating a live data feed without requiring a database or external service.
  • Swagger UI documentation — Interactive API documentation is available via Swashbuckle.AspNetCore, accessible at /swagger in Development mode for easy endpoint exploration and testing.

Architecture

The project follows a single-project Minimal API architecture. All routing is defined in Program.cs, which coordinates data generation and SSE streaming.

graph LR
    Client["Client"]
    SSE["/SSE Endpoint<br/>(Program.cs)"]
    Service["MoqDataRetrieverService"]
    Model["NotificationMessage"]
    Swagger["Swagger UI<br/>(Swashbuckle)"]

    Client -->|GET /SSE| SSE
    SSE --> Service
    Service -->|generates| Model
    Model -->|serialized to JSON| SSE
    SSE -->|text/event-stream| Client
    SSE -.->|/swagger| Swagger
Loading

Data flow: The client opens a persistent connection to the /SSE endpoint. Program.cs calls MoqDataRetrieverService, which uses the Bogus library to generate random NotificationMessage objects. Each message is serialized to JSON via System.Text.Json and streamed back to the client as a data: frame over the text/event-stream connection. Swagger UI is available at /swagger in Development mode for interactive API exploration.

SSE Request Flow

The following sequence diagram illustrates the lifecycle of a single SSE connection, from the initial client request through the streaming loop to disconnection.

sequenceDiagram
    participant Client
    participant SSE as /SSE Endpoint
    participant Service as MoqDataRetrieverService
    participant Json as System.Text.Json

    Client->>SSE: GET /SSE
    SSE->>SSE: Set Content-Type: text/event-stream

    loop While CancellationToken is not cancelled
        SSE->>Service: GetNewMessages()
        Service-->>SSE: List of NotificationMessage

        loop For each message
            SSE->>Json: Serialize message to JSON
            Json-->>SSE: JSON string
            SSE->>Client: data: {json}\n\n
            SSE->>SSE: Flush response body
        end
    end

    Client->>SSE: Disconnect (CancellationToken cancelled)
    SSE-->>Client: Connection closed
Loading

How it works: When a client sends a GET /SSE request, the server sets the Content-Type header to text/event-stream and enters a continuous loop. On each iteration, MoqDataRetrieverService.GetNewMessages() generates a batch of random NotificationMessage objects. Each message is serialized to JSON via System.Text.Json, written to the response as a data: frame followed by two newlines (\n\n), and flushed to the client immediately. The loop continues until the client disconnects, which triggers the CancellationToken and ends the stream gracefully.

Prerequisites

Before running the project, make sure you have the following installed:

Quick Start

  1. Clone the repository
git clone https://github.com/<your-username>/ServerSideEvents.git
cd ServerSideEvents
  1. Restore dependencies
dotnet restore
  1. Build the solution
dotnet build
  1. Run the application

Using the default http profile (http://localhost:5059):

dotnet run --project ServerSideEvents

Or using the https profile (https://localhost:7095):

dotnet run --project ServerSideEvents --launch-profile https
  1. Access Swagger UI

Once the application is running in Development mode, open your browser and navigate to:

  • http profile: http://localhost:5059/swagger
  • https profile: https://localhost:7095/swagger
  1. Connect to the SSE endpoint

To start receiving real-time events, open the SSE stream in your browser or with a tool like curl:

  • http profile: http://localhost:5059/SSE
  • https profile: https://localhost:7095/SSE
curl http://localhost:5059/SSE

Project Structure

ServerSideEvents.sln              # Solution file (single project)
ServerSideEvents/                 # Main web API project
├── Program.cs                    # Application entry point, DI setup, and all route definitions
├── NotificationMessage.cs        # Data model for SSE notification payloads
├── MoqDataRetrieverService.cs    # Singleton service that generates fake notification data via Bogus
├── ServerSideEvents.csproj       # Project file (.NET 10 Web SDK)
├── ServerSideEvents.http         # HTTP request file for quick endpoint testing
├── Properties/
│   └── launchSettings.json       # Dev server profiles (http, https, IIS Express)
├── appsettings.json              # Base application configuration
└── appsettings.Development.json  # Development-specific overrides
assets/                           # Static assets (screenshots for README)
└── postmanTest.png               # Postman testing screenshot
README.md                         # Project documentation

Testing

You can verify the SSE endpoint using a terminal with curl or a GUI tool like Postman.

Testing with curl

Open a terminal and run the following command to connect to the SSE stream:

curl -N http://localhost:5059/SSE

The -N flag disables output buffering, so events are displayed as soon as the server sends them. You should see a continuous stream of data: frames printed to the terminal. Press Ctrl+C to disconnect.

Testing with Postman

  1. Open Postman
  2. Create a new GET request
  3. Enter the SSE endpoint URL: http://localhost:5059/SSE
  4. Click Send
  5. Observe real-time updates appearing in the response body as the server pushes new events

Postman will keep the connection open and display each incoming event as it arrives.

Postman SSE test showing real-time event stream Postman receiving real-time notification events from the /SSE endpoint.

JSON Message Format

Each event in the SSE stream is delivered as a data: frame containing a JSON-serialized NotificationMessage object:

data: {"Title":"Some random title","Message":"Some random message"}

The NotificationMessage model has two string properties:

Field Type Description
Title string A randomly generated notification title
Message string A randomly generated notification message

Messages are generated by the MoqDataRetrieverService using the Bogus library and serialized with System.Text.Json.

Technology Stack

Technology Version Purpose
.NET 10.0 Runtime and SDK for building and running the application
ASP.NET Core Minimal APIs 10.0 Lightweight web framework for defining HTTP endpoints without controllers
Bogus 35.6.5 Fake data generation library used to create random notification messages
Microsoft.AspNetCore.OpenApi 10.0.5 OpenAPI metadata support for Minimal API endpoints
Swashbuckle.AspNetCore 10.1.7 Swagger UI generation and interactive API documentation at /swagger
System.Text.Json (built-in) JSON serialization for SSE message payloads

Limitations and Best Practices

While SSE is a lightweight and effective protocol for unidirectional streaming, there are important constraints to be aware of when deploying it in real-world applications.

Browser Concurrent Connection Limit

Under HTTP/1.1, browsers enforce a limit of approximately 6 simultaneous connections per domain. Each open SSE stream occupies one of these connections for as long as it remains active. This means that a single browser can maintain at most around 6 concurrent SSE connections to the same origin before new requests — including regular API calls, asset downloads, and additional SSE streams — are queued and blocked.

Impact on Multi-Tab Usage

Because the connection limit is per domain, not per tab, multiple tabs open to the same application share the same pool of available connections. For example, if three tabs each open two SSE streams, all 6 connections are consumed and a fourth tab would be unable to establish any new HTTP connections to that domain until an existing stream is closed. This can lead to unresponsive pages and degraded user experience in applications that rely on SSE across multiple views.

HTTP/2 Multiplexing (Primary Solution)

The most effective way to overcome the HTTP/1.1 connection limit is to serve your application over HTTP/2. HTTP/2 multiplexes many streams over a single TCP connection, effectively removing the 6-connection bottleneck. With HTTP/2 enabled, browsers can maintain a much higher number of concurrent SSE streams to the same domain without blocking other requests.

To take advantage of HTTP/2 in ASP.NET Core, ensure your server is configured with HTTPS (HTTP/2 requires TLS in all major browsers) and that your hosting environment supports the protocol. Kestrel, the default ASP.NET Core web server, supports HTTP/2 out of the box.

WebSocket as an Alternative

For scenarios that require high concurrency (many simultaneous real-time connections) or bidirectional communication (both client and server need to send messages), WebSocket is the recommended alternative. WebSocket establishes a full-duplex channel over a single TCP connection, making it more efficient when the client also needs to send data back to the server.

In the .NET ecosystem, SignalR provides a high-level abstraction over WebSocket with automatic fallback transports, connection management, and group broadcasting. Consider WebSocket or SignalR when:

  • Your application needs to support a large number of concurrent real-time connections per domain
  • Clients need to send messages or commands back to the server
  • You require features like connection grouping, presence tracking, or pub/sub patterns

Useful Resources

About

This project provides an API to test Server-Sent Events (SSE) using .NET. SSE is a standard allowing servers to push real-time updates to clients over a single HTTP connection.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages