Simplest Server Sent Events In .NET (no libraries)
Tags: dotnet, backend, beginners
Published: 2024-02-10 11:40:58

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.

```csharp
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);
}
```

### 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

```csharp
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();
	}
```

### Holding Active Connections

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

```csharp
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));
}
```

## How to use the code

Now register the `SSEProvider` as singleton service

```csharp
builder.Services.AddSingleton(typeof(SSEProvider), typeof(SSEProvider));
```

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

```csharp
.....
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);
	}
}
```

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