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.
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.
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.
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.
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:
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.