Implementing Real-Time Updates with Server-Sent Events (SSE) in C# .NET: A Complete Guide

Mayank Agarwal - Sep 8 - - Dev Community

Introduction

Server-Sent Events (SSE) is a web technology where the server pushes real-time updates to the client over HTTP, making it ideal for scenarios that require continuous updates, such as live stock prices, notifications, and activity feeds. Unlike WebSockets, SSE is unidirectional (server-to-client) and uses a persistent HTTP connection to send data as events. In .NET Core (C#), SSE can be implemented easily using asynchronous streams, providing a lightweight and scalable solution for real-time applications. This article covers how to implement SSE in C# .NET, detailing the steps involved, best practices to follow, and a conclusion on when to use SSE effectively.

Step 1: Create a New ASP.NET Core API Project

Start by creating a new ASP.NET Core project for the API:

dotnet new webapi -n SSEExample
cd SSEExample
Enter fullscreen mode Exit fullscreen mode

This creates a basic ASP.NET Core web API project.

Step 2: Implement SSE in Your Controller

SSE works by holding an HTTP connection open and sending a stream of text-based events to the client. In your controller, add an endpoint to send these events:

StockInfo.cs - Add Model for clean code

namespace StockPriceAPI.Model
{
    public class StockInfo
    {
        public double newPrice { get; set; }
        public bool isUp { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

StockPriceController.cs

using Microsoft.AspNetCore.Mvc;
using StockPriceAPI.Model; 
using System.Text.Json;

namespace StockPriceAPI.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class StockPriceController : ControllerBase
    {

        private static readonly Dictionary<string, double> stocks = new Dictionary<string, double>()
    {
        { "Tata Power", 150.00 },
        { "Reliance", 2800.00 },
        { "Adani Power", 3400.00 },
        { "Suzlon", 299.00 }
    };

        private static readonly Random random = new Random();
        [HttpGet("stream")]
        public async Task Get()
        {
            Response.ContentType = "text/event-stream";

            while (true)
            {
                // Declare the dictionary to store stock updates
                var updatedStocks = new Dictionary<string, StockInfo>();

                // Simulate stock price updates
                foreach (var stock in stocks.Keys.ToList())
                {
                    var oldPrice = stocks[stock];

                    // Simulate price fluctuation
                    var newPrice = oldPrice + random.NextDouble() * 10 - 5;
                    stocks[stock] = Math.Round(newPrice, 2);
                    var isUp = newPrice > oldPrice;

                    // Store updated stock data using the StockInfo class
                    updatedStocks[stock] = new StockInfo { newPrice = stocks[stock], isUp = isUp };
                }

                var json = JsonSerializer.Serialize(updatedStocks);
                await Response.WriteAsync($"data: {json}\n\n");
                await Response.Body.FlushAsync();

                await Task.Delay(2000); // Delay to simulate real-time updates (every 2 seconds)
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Here’s what’s happening:

  • stocks Dictionary: Holds initial stock names and their prices. This is used to simulate stock prices.
    • random: An instance of the Random class to generate price fluctuations.
    • Infinite Loop: Continuously sends updates to the client.
    • Stock Update Simulation: Iterates over the stocks dictionary, updates prices with random fluctuations, and stores the updated information in a new Dictionary<string, StockInfo>.
    • JSON Serialization: Converts the updatedStocks dictionary to a JSON string.
    • Sending Data: Sends the JSON data to the client with the data: prefix, followed by \n\n to adhere to SSE format.
    • Delay: Pauses for 2 seconds between updates to simulate real-time data streaming.

Step 3: Client-Side Implementation

The client will listen to the SSE stream and handle updates as they arrive. In a typical HTML client, use the EventSource API:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Stock Market Dashboard</title>
    <style>
body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #f0f2f5;
    margin: 0;
    padding: 0;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
}

.container {
    width: 90%;
    max-width: 1200px;
    margin: 20px;
    padding: 20px;
    background-color: #ffffff;
    border-radius: 10px;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    overflow: hidden;
}

header {
    text-align: center;
    margin-bottom: 20px;
}

h1 {
    color: #333;
    font-size: 2rem;
    margin: 0;
}

main {
    padding: 0 20px;
}

.stocks {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.stock {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 15px;
    border-radius: 8px;
    background-color: #f9f9f9;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    font-size: 1.2rem;
    transition: background-color 0.3s;
}

.stock.up {
    border-left: 5px solid #28a745;
    color: #28a745;
}

.stock.down {
    border-left: 5px solid #dc3545;
    color: #dc3545;
}

.stock:hover {
    background-color: #e9ecef;
}

footer {
    text-align: center;
    padding: 10px;
    margin-top: 20px;
    font-size: 0.9rem;
    color: #666;
}

    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>Stock Market Dashboard</h1>
        </header>
        <main>
            <div id="stocksDiv" class="stocks"></div>
        </main>
        <footer>
            <p>&copy; 2024 Stock Market Inc. All rights reserved.</p>
        </footer>
    </div>
    <script>
    // Connect to the SSE endpoint
        const eventSource = new EventSource('http://localhost:5249/StockPrice/stream');
        const stocksDiv = document.getElementById('stocksDiv');

        // Keep track of previous stock prices to detect changes
        const previousPrices = {};

        eventSource.onmessage = function(event) {
        const updatedStocks = JSON.parse(event.data);

    stocksDiv.innerHTML = ''; // Clear previous stock data

    for (const [stock, stockData] of Object.entries(updatedStocks)) {
        const { newPrice, isUp } = stockData || {}; // Destructure and handle undefined

        if (typeof newPrice === 'number') {
            // Determine the class based on isUp
            const priceClass = isUp ? 'up' : 'down';
            console.log(stock, stockData);

            // Display stock with the appropriate color
            stocksDiv.innerHTML += `
                <div class="stock ${priceClass}">
                    <div class="name">${stock}</div>
                    <div class="price">${newPrice.toFixed(2)}</div>
                </div>
            `;
        } else {
            console.error(`Invalid price for stock ${stock}`);
        }
    }
};

    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

This JavaScript code connects to the server via EventSource, which automatically handles the connection and incoming events.

Step 4: Run the Application

  • Start the ASP.NET Core app by running:
  dotnet run
Enter fullscreen mode Exit fullscreen mode
  • Open the HTML file in your browser. The stock price will update every second, simulating a live feed.

Dashboard

Best Practices for SSE in C# .NET

  1. Use for Server-to-Client Updates: SSE is ideal for cases where the server frequently sends updates (e.g., real-time notifications or monitoring). However, if bidirectional communication is required, consider WebSockets.

  2. Manage Connection Lifespan: Always monitor the connection and handle client disconnections gracefully. The server should break the loop if RequestAborted is triggered.

  3. Handle Reconnects: The EventSource API automatically reconnects if the connection drops. You can send an event ID so the client can resume from where it left off.

  4. Buffering Control: Ensure that the Response.Body.FlushAsync() is used frequently to flush the output stream and send updates immediately.

  5. Use Lightweight Data: SSE is text-based and operates over HTTP/1.1, so avoid sending heavy binary data. Keep payloads lightweight to maintain performance.

  6. Error Handling: Implement error handling on both server and client sides. On the server, check for network errors or cancellation tokens; on the client, handle connection errors and timeouts gracefully.

  7. Scalability: While SSE is easier to implement than WebSockets, it opens one HTTP connection per client. For a large number of clients, consider scaling horizontally or using load balancers.

Conclusion

Server-Sent Events (SSE) provide a lightweight, efficient way to push real-time updates from a server to clients in scenarios where only server-to-client communication is required. SSE in C# .NET is straightforward to implement using asynchronous streams, offering a scalable solution for real-time apps. By following the best practices outlined above, developers can ensure their SSE-based systems are performant and reliable. Although SSE is simpler than WebSockets, it is an excellent choice for applications like stock tickers, live notifications, or news feeds where real-time, one-way updates are necessary.

. .
Terabox Video Player