DEV Community

Cover image for Server Sent Events in ASP.NET Core
Andreas Nägeli
Andreas Nägeli

Posted on

3

Server Sent Events in ASP.NET Core

Server Sent Events (SSE) is a HTML standard with good support by all major browsers that allows your server to push events to connected clients as they happen.

In contrast to websockets, the connection is unidirectional (so only the server can push events), making it easier to be implemented, soley relying on default HTTP mechanisms. On the client side, there is a simple, comprehensible API ready to be used.

ASP.NET Core does not offer an official implementation for Server Sent Events, so there are a lot of tutorials which try to add this functionality by directly writing to the response stream in the format the client expects. This approach works, but adds a lot of clutter to your application code and typically lacks some handy features provided by the standard, such as event types or event stream resumption.

There are other web server frameworks that have a first-class support for server sent events built in, but you probably do not want to change your server framework just for implementing an event source.

Gladly, one of those frameworks - namely GenHTTP - provides an adapter that allows us to plug in their implementation into an ASP.NET Core application. The following example will demonstrate this approach with a simple app that pushes randomly generated stock symbols to interested clients.

First, we create a new ASP.NET Core minimal API project from the terminal (or in Visual Studio):

dotnet new web -o StockEvents

In the generated project, we then add the nuget packages for the provider and the adapter.

dotnet add package GenHTTP.Modules.ServerSentEvents
dotnet add package GenHTTP.Adapters.AspNetCore
Enter fullscreen mode Exit fullscreen mode

In our Program.cs we can then create an Event Source that will randomly generate our stock symbol updates and push them to connected clients. This source can be mapped to any path using the adapter functionality.

using GenHTTP.Adapters.AspNetCore;
using GenHTTP.Modules.ServerSentEvents;

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

var source = EventSource.Create()
                        .Generator(GenerateStock)
                        .Defaults();

app.Map("/stock", source);

app.UseDefaultFiles();
app.UseStaticFiles();

app.Run();

static async ValueTask GenerateStock(IEventConnection connection)
{
    var rand = new Random();

    var stockSymbols = new List<string> { "AAPL", "GOOGL", "MSFT" };

    await connection.CommentAsync("Sending stock data");

    while (connection.Connected)
    {
        var symbol = stockSymbols[rand.Next(0, 3)];

        await connection.DataAsync(rand.Next(100, 1000), symbol);

        await Task.Delay(1000);
    }

}
Enter fullscreen mode Exit fullscreen mode

To test our implementation, we create a wwwroot sub directory in our project and create an index.html file there with the following content (adjust the port of the endpoint as needed):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Stock Tracker</title>
    <style>
        body {
            font-family: Arial, sans-serif;
        }
        #stocks {
            display: flex;
            flex-direction: column;
            gap: 10px;
            margin-top: 20px;
        }
        .stock {
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
            background-color: #f9f9f9;
        }
    </style>
</head>
<body>
<h1>Stock Tracker</h1>
<div id="stocks"></div>

<script>
    // Establish a connection to the server using Server-Sent Events
    const eventSource = new EventSource('http://localhost:5011/stock/');

    // Function to display stock updates
    function updateStock(symbol, value) {
        let stockElement = document.getElementById(symbol);

        if (!stockElement) {
            // Create a new element for the stock symbol if it doesn't exist
            stockElement = document.createElement('div');
            stockElement.id = symbol;
            stockElement.className = 'stock';
            document.getElementById('stocks').appendChild(stockElement);
        }

        // Update stock value
        stockElement.innerHTML = `<strong>${symbol}:</strong> ${value}`;
    }

    // Event listener for general updates
    eventSource.onmessage = function(event) {
        updateStock(event.type, event.data);
    };

    // Event listeners for specific stock symbols
    const symbols = ['AAPL', 'GOOGL', 'MSFT']; // Example stocks
    symbols.forEach(symbol => {
        eventSource.addEventListener(symbol, event => {
            updateStock(symbol, event.data);
        });
    });

    // Error handling
    eventSource.onerror = function() {
        console.error('Connection to the server lost.');
    };
</script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

After the server has been started (dotnet run), you can open the endpoint announced by the launcher (e.g. http://localhost:5011) and will see the stock updates ticking in.

Please note that the endpoint you specified in the HTML file needs to match the URL you open in your browser, or otherwise CORS will prevent us from fetching events (so for example you cannot mix HTTP and HTTPS).

Stock updates processed via Server Sent Events

This was a brief tutorial to quickly spawn and consume an event source - you can check the documentation of the GenHTTP module to learn more about event types, event IDs, or error handling.

Cover by: Devon Janse van Rensburg

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay