DEV Community

Cover image for Simplest Server Sent Events In .NET (no libraries)
yrezehi
yrezehi

Posted on

Simplest Server Sent Events In .NET (no libraries)

Implementing SSE into your application can go in two ways, in a simple way or the hard way but I prefer the simple dumb way..

SSE

In nutshell SSE is a long running http connection, with a special mime type (text/event-stream) and can be used for connection between a server and a client, where messages can be sent from server to client in continuous manner. If you plan to send a data in real time without the need to submitting requests from the client to the server asking for the data you can use SSE, perfect examples are real-time stock market data to be sent from an exchange's backend to it frontend.

Setting up the project

To keep the the solution as simple as possible, we gonna implement the solution in three files.

Object to hold the connection

We need a class to hold Http instance to write a data to it, and you can put the rest of the properties as you need.

public class SSEClient
{
    public string Id { get; set; }
        // to keep http instance for data transfer
    public HttpResponse Response { get; set; }
    public DateTime JoinTime { get; set; }

    private SSEClient(HttpResponse response) =>
        (Id, Response, JoinTime) = (Guid.NewGuid().ToString(), response, DateTime.Now);

    public static SSEClient New(HttpResponse response) =>
        new SSEClient(response);
}
Enter fullscreen mode Exit fullscreen mode

Setting helper class

Helper class does three things, setting up necessary http headers for SSE, keeps the http connection active and send data to a client

public static async Task SetSSEHeaders(this HttpResponse response)
    {
                # don't cache the response
        response.Headers.Add("Cache-Control", "no-cache");
                # content type to let the browser knows it's SSE
        response.Headers.Add("Content-Type", "text/event-stream");
                # 
        response.Headers.Add("X-Accel-Buffering", "no");
                # keep the connection a live for data flow
        response.Headers.Add("Connection", "keep-alive");

        await response.Body.FlushAsync();
    }

    public static async Task KeepSSEAlive(this HttpResponse _, CancellationToken cancellationToken) {
        while (!cancellationToken.IsCancellationRequested)
            await Task.Delay(3000, cancellationToken);
    }

    public static async Task SendSEEEvent(this HttpResponse response, string @event)
    {
        await response.WriteAsync($"data: {@event}\r\r");
        await response.Body.FlushAsync();
    }
Enter fullscreen mode Exit fullscreen mode

Holding Active Connections

A simple class to hold the connections for brodcasting data and registering connections

public class SSEProvider
{
    public readonly ConcurrentBag<SSEClient> Clients;

    public SSEProvider() =>
        Clients = new ConcurrentBag<SSEClient>();

    private ParallelOptions ParallelOptions => 
        new () { MaxDegreeOfParallelism = 5 };

    public void Brodcast(string @event) =>
        Parallel.ForEach(Clients, ParallelOptions, client =>
        {
            client.Response.SendSEEEvent(@event).Wait();
        });

    public void Register(HttpContext context) =>
        Clients.Add(SSEClient.New(context.Response));
}
Enter fullscreen mode Exit fullscreen mode

How to use the code

Now register the SSEProvider as singleton service

builder.Services.AddSingleton(typeof(SSEProvider), typeof(SSEProvider));
Enter fullscreen mode Exit fullscreen mode

And create a controller or a controller you already have and put the following code in it

.....
public class LiveController : Controller
{
    private readonly SSEProvider SSEProvider;

    public LiveController(SSEProvider sseProvider) => 
        SSEProvider = sseProvider;

    [HttpGet("[action]")]
    public async Task Listen(CancellationToken cancellationToken)
    {
        await HttpContext.Response.SetSSEHeaders();
        SSEProvider.Register(HttpContext);
        await HttpContext.Response.KeepSSEAlive(cancellationToken);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now when you call SSEProvider.Brodcast("the data here"); you can send the data to all connected clients

At last

The code provided was only tested on low load applications, you might face issues on higher load applications but it's good for simple use cases, if you have any questions feel free to drop a comment.

If you like the my coding style for learning consider checking out my open source projects

https://github.com/yrezehi

Top comments (0)