In this article, we will talk about Server-Sent Events in ASP.NET Core. We will see what they are, their features, advantages, and implementation.

To download the source code for this article, you can visit our GitHub repository.

So, let’s start.

What Are Server-Sent Events

The Server-Sent Events (SSE) protocol allows servers to send real-time, event-driven updates to clients over an HTTP connection without the client requesting these updates. It uses HTTP as the communication protocol between servers and clients. SSE eliminates the need to manually query the server or establish multiple connections to deliver changes to the client.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Key Features of Server-Sent Events

In an established connection between the server and the client, the server can provide event-driven updates to clients in real-time. The client interface listens for server events, which can include specific identities.

With SSE, only the server can send data to a connected client. That’s why we call it a unidirectional communication channel. The client cannot send data back to the server.

The SSE client-server connection is a persistent HTTP connection maintained throughout the entire client lifecycle.

SSE messages are sent in plain text over HTTP. Because SSE is a text-based protocol, its fields are sent as strings of text as event fields.

The client interface will attempt to keep the connection open. Once disconnected, it will try to reconnect to the server to maintain updates. SSE provides a resilient connection by automatically re-establishing the connection. This allows events to continue.

With SSE, it’s possible to receive updates from a different origin or domain than the client. We can configure our server’s cross-origin resource sharing (CORS) rules to allow specific origins and domains.

How Do Server-Sent Events Work?

SSE eliminates the need for manual server queries and multiple connections by enabling real-time, unidirectional communication between the server and client. Once established, the server sends event-driven changes to the client over a single HTTP connection. The server can submit new events individually while keeping HTTP responses open. The EventSource API is a standard client-side interface that opens a persistent connection to the HTTP server, and the connection remains open until it is closed by calling EventSource.close().

WebSockets vs Server-Sent Events

The main difference between WebSockets and Server-Sent Events is the direction of communication. WebSockets are bi-directional, allowing communication between the client and the server, while SSEs are mono-directional, only allowing the client to receive data from the server. Another difference is that while SSEs have automatic reconnection, event IDs, and arbitrary sending of events as features, WebSockets can detect when clients lose connections. Additionally, WebSockets can send binary and UTF-8 data, while SSE is limited to UTF-8 data. One disadvantage of SSE is its limitation to a maximum of six open connections per browser.

Building an Application Using Server-Sent Events

We can use Server-sent events in various scenarios and applications, such as news feeds, notifications, or real-time monitoring dashboards. Let’s use ASP.NET Core Web API to implement a simple SSE application. Our application will be a countdown timer counting down from 30 to 0.

The server sends data with a start number and decrements it every second until it reaches 0. This loop continues while the connection server client is alive.

Creating an ASP.NET Core Project

First, let’s create our project using dotnet CLI:
dotnet new webapi -n ServerSentEventsForRealtimeUpdates.Server

We use the new command to create a new project, the webapi specifies the template, and the -n option we use to set a name for our project.

If you would like to learn more about how to create an ASP.NET Core project, we highly recommend that you visit the complete navigation of the ASP.NET Core Series.

Implement Timer Service

Let’s create a CounterService class that inherits from ICounterService, and implement a couple of methods:

public class CounterService : ICounterService
{
    private const int StartValueCounter = 30;
    private const int MillisecondsDelay = 1000;

    public async Task CountdownDelay(CancellationToken cancellationToken)
    {
        await Task.Delay(MillisecondsDelay, cancellationToken);
    }

    public int StartValue
    {
        get => StartValueCounter;
    }
}

First, we declare two constants: StartValueCounter and MillisecondsDelay.

Now, we implement the CountdownDelay() method, which is just a call to Task.Delay() method passing the MillisecondsDelay constant that we declared.

Then, we declare the StartValue as a getter for the StartValue constant.

Implement the Server-Sent Events Endpoint

Now, let’s modify our Program class so we can have the SSE endpoint that we will use to establish the server-client connection.

Enable Cross-Origin Requests (CORS)

First, let’s enable Cross-Origin Requests (CORS) so the client can’t connect to our server:

const string myCors = "client";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(myCors,
        policy =>
        {
            policy.AllowAnyOrigin()
                .AllowAnyHeader()
                .AllowAnyMethod();
        });
});

Here, we declare the myCors constant with a name for the CORS policy and add the policy with the AddPolicy() method to the CORS options.

If you want to know more about CORS, check out our article Enabling CORS in ASP.NET Core.

Now, we can set the policy to the WebApplication:

app.UseCors(myCors);

We call the IApplicationBuilder.UseCors() method, passing the same constant we created before.

To be able to use the service methods, let’s register the CounterService:

builder.Services.AddScoped<ICounterService, CounterService>();

We add the CounterService service as ICounterService implementation to the service collection using the AddScoped() method.

Creating a Route Handler

Now,  let’s add a route endpoint using the app.MapGet():

app.MapGet("/sse", async Task (HttpContext ctx, ICounterService service, CancellationToken token) =>
{
    ctx.Response.Headers.Append(HeaderNames.ContentType, "text/event-stream");

    var count = service.StartValue;

    while (count >= 0)
    {
        token.ThrowIfCancellationRequested();
        
        await service.CountdownDelay(token);

        await ctx.Response.WriteAsync($"data: {count}\n\n", cancellationToken: token);
        await ctx.Response.Body.FlushAsync(cancellationToken: token);

        count--;
    }

    await ctx.Response.CompleteAsync();
});

We set the endpoint to /sse and inject HttpContext, ICounterService, and the CancellationToken. As we said, Server-Sent Events is a text-based protocol, so we set ContentType to text/event-stream in the headers.

We create a while loop to check when the counter is finished and add the logic that updates the client.

Then, we wait for one second before continuing by using the service.CountdownDelay().

We must define the response in the format $"data: {<DataToSend>}\n\n" or else it won’t work. In our example, we used the count variable as the data.

Finally, we send it to the client using HttpResponse.FlushAsync() method.

Implementing the Client

Now that we have Server-Sent Events working, let’s implement our client:

<div id="counter" />

We create a simple div container in an HTML file with the ID counter, which will be the placeholder for the counter value.

Now, let’s implement the script we’ll use to connect to the server:

<script>
    async function ServerConnection() {
        const eventSource = new EventSource("https://localhost:7095/sse");

        eventSource.onmessage = (event) => {
            document.getElementById("counter").innerText = event.data.replace(/(\r\n|\n|\r)/gm, "");
        }
    }

    ServerConnection();
</script>

We add a new <script> tag before the </body> tag, and create a new JavaScript async function ServerConnection(). 

We can now create the Server-Sent Events connection. So, we create a new instance of the EventSource, passing the server URL. Then, we call the EventSource.onmessage method to receive the server data. We use the document.getElementById() method to get the element with the id attribute set to counter and set the inner text of this element with the value event.data. Finally, we replace the carriage return \r and new line \n characters at the end of the data.

Let’s have a look at the counter:

Server-Sent Events: Shows the counter solution

Voila, our web page is updated by Server-Sent Events, counting from 30 down to 0.

Conclusion

In this article, we have learned how Server-Sent Events work for real-time updates. We saw some applications of it and how it differs from WebSocket. Then, we implemented it in a simple ASP.NET Core Web API. Using Server-Sent Events has some advantages, but there are also some disadvantages. We must evaluate the whole context to choose the best technology for our application.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!