This is not a demo – this is the same pattern used in real chat/notification systems (WhatsApp-like browser notifications, dashboards, etc.).
1. When to Use SSE (Production Decision)
Use SSE when:
- One-way server → browser notifications
- Chat message alerts (not full chat transport)
- Live counters, alerts, status updates
- Browser-based apps (no mobile socket requirement)
Do NOT use SSE for:
- Binary streaming
- High-frequency bidirectional chat typing events
- Mobile background connections (WebSocket better)
2. Architecture (Production)
Browser (EventSource)
↓
SSE Controller (long-lived HTTP)
↓
ConcurrentDictionary<UserId, SseClient>
↓
Notification Publisher (Chat / Job / API)
Key rules:
- One SSE connection per user
- No DB polling
- No blocking threads
- Heartbeat required
- Auto-reconnect handled by browser
3. Core SSE Infrastructure (IMPORTANT)
3.1 SSE Client Model
public class SseClient
{
public int UserId { get; set; }
public HttpResponse Response { get; set; }
public DateTime ConnectedAt { get; set; }
}
3.2 SSE Connection Manager (Thread-Safe)
public static class SseConnectionManager
{
private static readonly ConcurrentDictionary<int, SseClient> _clients
= new ConcurrentDictionary<int, SseClient>();
public static bool AddClient(int userId, HttpResponse response)
{
RemoveClient(userId); // ensure single tab policy (optional)
return _clients.TryAdd(userId, new SseClient
{
UserId = userId,
Response = response,
ConnectedAt = DateTime.UtcNow
});
}
public static void RemoveClient(int userId)
{
_clients.TryRemove(userId, out _);
}
public static bool SendToUser(int userId, string eventName, string data)
{
if (!_clients.TryGetValue(userId, out var client))
return false;
try
{
client.Response.Write($"event: {eventName}\n");
client.Response.Write($"data: {data}\n\n");
client.Response.Flush();
return true;
}
catch
{
RemoveClient(userId);
return false;
}
}
public static void Broadcast(string eventName, string data)
{
foreach (var userId in _clients.Keys)
{
SendToUser(userId, eventName, data);
}
}
}
- Thread-safe
- Memory-safe
- Handles broken connections
4. SSE Controller (Production-Safe)
public class SseController : Controller
{
[HttpGet]
public void Subscribe()
{
int userId = GetLoggedInUserId();
Response.ContentType = "text/event-stream";
Response.CacheControl = "no-cache";
Response.BufferOutput = false;
SseConnectionManager.AddClient(userId, Response);
try
{
while (Response.IsClientConnected)
{
// Heartbeat every 15 sec
Response.Write("event: ping\n");
Response.Write("data: {}\n\n");
Response.Flush();
Thread.Sleep(15000);
}
}
finally
{
SseConnectionManager.RemoveClient(userId);
}
}
private int GetLoggedInUserId()
{
return int.Parse(User.Identity.Name);
}
}
Why heartbeat?
- Prevents proxy/load-balancer timeout
- Detects dead connections
5. Sending Notifications (Chat / API / Jobs)
Example: Chat Message Notification
public class NotificationService
{
public static void NotifyUser(int userId, object payload)
{
string json = Newtonsoft.Json.JsonConvert.SerializeObject(payload);
SseConnectionManager.SendToUser(userId, "message", json);
}
}
Usage:
NotificationService.NotifyUser(671496, new
{
fromUserId = 655093,
text = "Hello 👋",
createdAt = DateTime.UtcNow
});
6. Browser Client (Production JS)
<script>
let source = new EventSource("/sse/subscribe");
source.addEventListener("message", function (e) {
let data = JSON.parse(e.data);
console.log("Message:", data);
});
source.addEventListener("ping", function () {
console.log("heartbeat");
});
source.onerror = function () {
console.log("SSE disconnected. Reconnecting...");
};
</script>
- Auto-reconnect handled by browser
- No polling
- Low memory footprint
7. Multi-Tab Handling (WhatsApp-Like Behavior)
If same user opens multiple tabs, enforce single active tab:
public static bool AddClient(int userId, HttpResponse response)
{
RemoveClient(userId); // kicks old tab
return _clients.TryAdd(userId, new SseClient
{
UserId = userId,
Response = response,
ConnectedAt = DateTime.UtcNow
});
}
Other tabs will automatically disconnect and can show popup:
“This account is active in another tab”
8. Production Hardening Checklist
IIS / Hosting
- Disable response buffering
- Increase request timeout
- Enable keep-alive
Security
- Auth-based user binding
- No userId in query string
- Rate-limit notification sends
Scalability
- In-memory only → single server
- Multi-server → use Redis Pub/Sub
9. Multi-Server (Redis) Architecture
Server A ─┐
Server B ─┼── Redis Pub/Sub ──► SSE Clients
Server C ─┘
Each server:
- Subscribes to Redis channel
- Pushes events to connected users
10. Why Big Companies Use SSE
- GitHub notifications
- Slack web notifications
- Trading dashboards
- Monitoring systems
- Lower cost than WebSockets
- Works perfectly behind proxies
- Auto-reconnect built-in
11. SSE vs WebSocket (Quick Decision)
| Feature | SSE | WebSocket |
|---|---|---|
| Direction | Server → Client | Two-way |
| Complexity | Low | High |
| Scaling | Easy | Harder |
| Browser support | Excellent | Excellent |
| Chat notifications | Best | Overkill |
Final Recommendation (For Your Chat App)
Use:
- SSE → notifications, unread count, alerts
- API → send message
- Optional WebSocket → typing indicators only