-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
Background and motivation
.NET 10 introduces System.Net.ServerSentEvents with SseParser, which provides solid low-level support for parsing Server-Sent Events streams. This is a welcome addition and fills an important gap on the protocol side.
However, consuming SSE in real-world applications requires more than parsing. In practice, every client must also handle:
- connection lifecycle (open, closed, faulted)
- transient network failures
- automatic reconnection
- honoring
retry:values sent by the server - propagating
Last-Event-IDon reconnect - distinguishing cancellation from transport errors
At the moment, all of this logic is left to application code. As a result, each consumer re-implements roughly the same reconnection and lifecycle machinery on top of HttpClient + SseParser, with varying quality and subtle differences.
In contrast, the JavaScript EventSource API provides a well-understood, widely adopted abstraction that addresses these concerns consistently. The absence of an equivalent high-level client in .NET makes SSE consumption significantly more complex than necessary and discourages adoption for otherwise straightforward streaming scenarios.
The goal of this proposal is not to replace SseParser, but to build a small, opinionated client abstraction on top of it.
API Proposal
Introduce a high-level SSE client in System.Net.ServerSentEvents that manages connection lifecycle and reconnection semantics, while delegating parsing to SseParser.
The API should:
- Use
HttpClientinternally - Automatically reconnect on transient failures
- Honor
retry:values provided by the server - Automatically send
Last-Event-IDon reconnect - Expose explicit lifecycle signals (connected, disconnected, error)
Conceptually, this is a client-side equivalent of EventSource, adapted to .NET conventions.
Proposal:
public sealed class EventSourceClient : IAsyncDisposable
{
public event EventHandler Connected;
public event EventHandler Disconnected;
public event EventHandler<Exception> Error;
public event EventHandler<SseItem<string>> Message;
public Task StartAsync(CancellationToken cancellationToken);
public Task StopAsync(CancellationToken cancellationToken);
}This client would internally:
- manage the
HttpClientrequest lifecycle - consume the stream via
SseParser - track
LastEventId - apply reconnection delays based on
retry:or defaults
The existing SseParser API remains unchanged and continues to serve advanced or custom scenarios.
API Usage
Example usage from an application perspective:
var client = new EventSourceClient(
new Uri("https://example.com/events"),
httpClient);
client.Connected += (_, _) =>
{
logger.LogInformation("SSE connected");
};
client.Message += (_, evt) =>
{
Process(evt.Data);
};
client.Error += (_, ex) =>
{
logger.LogWarning(ex, "SSE error");
};
await client.StartAsync(ct);Alternative Designs
-
Status quo (manual composition)
Continue requiring users to composeHttpClient + SseParserthemselves.
This works, but leads to duplicated, inconsistent implementations and subtle bugs, especially around reconnection and lifecycle signaling. -
Third-party libraries
Some existing libraries implement EventSource-like behavior, but most predateSseParser, use older streaming patterns, or are no longer actively maintained. -
Parser-only helpers
Adding small helpers aroundSseParser(e.g., exposing more metadata) would improve ergonomics but would still leave reconnection and lifecycle management to application code.
Risks
No response