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
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; }
}
}
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)
}
}
}
}
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 theRandom
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 newDictionary<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>© 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>
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
- Open the HTML file in your browser. The stock price will update every second, simulating a live feed.
Best Practices for SSE in C# .NET
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.
Manage Connection Lifespan: Always monitor the connection and handle client disconnections gracefully. The server should break the loop if
RequestAborted
is triggered.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.Buffering Control: Ensure that the
Response.Body.FlushAsync()
is used frequently to flush the output stream and send updates immediately.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.
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.
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.
Top comments (0)