Closed
Description
Background and Motivation
This proposal adds first-class support for SSE responses through the IResult
pattern, making it consistent with other response types in minimal APIs. While ASP.NET Core has supported SSE through manual response writing, there hasn't been a built-in IResult implementation to return SSE streams from minimal API endpoints.
Proposed API
// Assembly: Microsoft.AspNetCore.Http.Results
namespace Microsoft.AspNetCore.Http;
public static class TypedResults
{
+ public static ServerSentEventResult<string> ServerSentEvents<T>(
+ IAsyncEnumerable<string> value,
+ string? eventType = null);
+
+ public static ServerSentEventResult<T> ServerSentEvents<T>(
+ IAsyncEnumerable<T> value,
+ string? eventType = null);
+
+ public static ServerSentEventResult<T> ServerSentEvents<T>(
+ IAsyncEnumerable<SseItem<T>> value);
}
+ public sealed class ServerSentEventResult<T> : IResult, IEndpointMetadataProvider
+ {
+ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, EndpointBuilder builder)
+ }
Usage Examples
Basic usage with simple values:
app.MapGet("/sse", () =>
{
async IAsyncEnumerable<string> GenerateEvents(
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var counter = 0;
while (!cancellationToken.IsCancellationRequested)
{
yield return $"Event {counter++} at {DateTime.UtcNow}";
await Task.Delay(1000, cancellationToken);
}
}
return TypedResults.ServerSentEvents(GenerateEvents());
});
Usage with custom event types:
app.MapGet("/notifications", () =>
{
async IAsyncEnumerable<Notification> GetNotifications(
[EnumeratorCancellation] CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
yield return new Notification("New message received");
await Task.Delay(5000, cancellationToken);
}
}
return TypedResults.ServerSentEvents(GetNotifications(), eventType: "notification");
});
Raw string processing with string
overload:
app.MapGet("/mixed-events", () =>
{
async IAsyncEnumerable<string> GetMixedEvents(
[EnumeratorCancellation] CancellationToken cancellationToken)
{
yield return "Starting";
yield return "Going";
yield return "Finished";
}
return TypedResults.ServerSentEvents(GetMixedEvents());
});
Fine-grained control with the SseItem<T>
overload:
app.MapGet("/mixed-events", () =>
{
async IAsyncEnumerable<SseItem<string>> GetMixedEvents(
[EnumeratorCancellation] CancellationToken cancellationToken)
{
yield return new SseItem<string>("System starting", "init");
yield return new SseItem<string>("Processing data", "progress") { EventId = "some-guid" };
yield return new SseItem<string>("Complete", "done");
}
return TypedResults.ServerSentEvents(GetMixedEvents());
});
Alternative Designs
- The
TypedResults
extension methods do not expose an overload that takes an event ID. Instead, the user must use theSseItem<T>
based overload of the extension method if they want to control the event ID at a more granular level.
Risks
N/A