Skip to content

Commit 45a33f8

Browse files
SSE return types /2 (#35152)
* SSE return types /2 * SSE return types /2 * SSE return types /2 * SSE return types /2 * SSE return types /2 * fixes * Update aspnetcore/fundamentals/minimal-apis/responses.md Co-authored-by: Mike Kistler <[email protected]> * Apply suggestions from code review Co-authored-by: Mike Kistler <[email protected]> * react to feedback * Update aspnetcore/web-api/action-return-types/samples/10/ControllerSSE/HearRate.cs Co-authored-by: Mike Kistler <[email protected]> * react to feedback --------- Co-authored-by: Mike Kistler <[email protected]>
1 parent ade268a commit 45a33f8

File tree

11 files changed

+175
-12
lines changed

11 files changed

+175
-12
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
public record HeartRateRecord(DateTime Timestamp, int HeartRate)
2+
{
3+
public static HeartRateRecord Create(int heartRate) => new(DateTime.UtcNow, heartRate);
4+
}

aspnetcore/fundamentals/minimal-apis/10.0-samples/MinimalServerSentEvents/MinimalServerSentEvents.http

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@baseUrl = http://localhost:5293
1+
@baseUrl = http://localhost:58489
22

33
### Connect to SSE stream
44
# This request will open an SSE connection that stays open
@@ -12,4 +12,4 @@ Accept: text/event-stream
1212
###
1313

1414
GET {{baseUrl}}/sse-item
15-
Accept: text/event-stream
15+
Accept: text/event-stream

aspnetcore/fundamentals/minimal-apis/10.0-samples/MinimalServerSentEvents/Program.cs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,32 @@ async IAsyncEnumerable<string> GetHeartRate(
1313
while (!cancellationToken.IsCancellationRequested)
1414
{
1515
var heartRate = Random.Shared.Next(60, 100);
16-
yield return $"Hear Rate: {heartRate} bpm";
16+
yield return $"Heart Rate: {heartRate} bpm";
1717
await Task.Delay(2000, cancellationToken);
1818
}
1919
}
2020

21-
return TypedResults.ServerSentEvents(GetHeartRate(cancellationToken), eventType: "heartRate");
21+
return TypedResults.ServerSentEvents(GetHeartRate(cancellationToken),
22+
eventType: "heartRate");
2223
});
2324
// </snippet_string>
2425

2526
// <snippet_json>
2627
app.MapGet("/json-item", (CancellationToken cancellationToken) =>
2728
{
28-
async IAsyncEnumerable<HearRate> GetHeartRate(
29+
async IAsyncEnumerable<HeartRateRecord> GetHeartRate(
2930
[EnumeratorCancellation] CancellationToken cancellationToken)
3031
{
3132
while (!cancellationToken.IsCancellationRequested)
3233
{
3334
var heartRate = Random.Shared.Next(60, 100);
34-
yield return HearRate.Create(heartRate);
35+
yield return HeartRateRecord.Create(heartRate);
3536
await Task.Delay(2000, cancellationToken);
3637
}
3738
}
3839

39-
return TypedResults.ServerSentEvents(GetHeartRate(cancellationToken), eventType: "heartRate");
40+
return TypedResults.ServerSentEvents(GetHeartRate(cancellationToken),
41+
eventType: "heartRate");
4042
});
4143
// </snippet_json>
4244

@@ -64,7 +66,3 @@ async IAsyncEnumerable<SseItem<int>> GetHeartRate(
6466

6567
app.Run();
6668

67-
public record HearRate(DateTime Timestamp, int HeartRate)
68-
{
69-
public static HearRate Create(int heartRate) => new(DateTime.UtcNow, heartRate);
70-
}

aspnetcore/fundamentals/minimal-apis/responses.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ In order to document this endpoint correctly the extension method `Produces` is
120120

121121
:::code language="csharp" source="~/fundamentals/minimal-apis/9.0-samples/Snippets/Program.cs" id="snippet_04":::
122122

123-
<a name="binr7"></a>
123+
<a name="binr10"></a>
124124

125125
### Built-in results
126126

@@ -172,6 +172,20 @@ The following example streams a video from an Azure Blob:
172172

173173
[!code-csharp[](~/fundamentals/minimal-apis/resultsStream/7.0-samples/ResultsStreamSample/Program.cs?name=snippet_video)]
174174

175+
#### Server-Sent Events (SSE)
176+
177+
The [TypedResults.ServerSentEvents](https://source.dot.net/#Microsoft.AspNetCore.Http.Results/TypedResults.cs,051e6796e1492f84) API supports returning a [ServerSentEvents](xref:System.Net.ServerSentEvents) result.
178+
179+
[Server-Sent Events](https://developer.mozilla.org/docs/Web/API/Server-sent_events) is a server push technology that allows a server to send a stream of event messages to a client over a single HTTP connection. In .NET, the event messages are represented as [`SseItem<T>`](/dotnet/api/system.net.serversentevents.sseitem-1) objects, which may contain an event type, an ID, and a data payload of type `T`.
180+
181+
The [TypedResults](xref:Microsoft.AspNetCore.Http.TypedResults) class has a static method called [ServerSentEvents](https://source.dot.net/#Microsoft.AspNetCore.Http.Results/TypedResults.cs,ceb980606eb9e295) that can be used to return a `ServerSentEvents` result. The first parameter to this method is an `IAsyncEnumerable<SseItem<T>>` that represents the stream of event messages to be sent to the client.
182+
183+
The following example illustrates how to use the `TypedResults.ServerSentEvents` API to return a stream of heart rate events as JSON objects to the client:
184+
185+
:::code language="csharp" source="~/fundamentals/minimal-apis/10.0-samples/MinimalServerSentEvents/Program.cs" id="snippet_item" :::
186+
187+
For more information, see the [Minimal API sample app](https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/fundamentals/minimal-apis/10.0-samples/MinimalServerSentEvents/Program.cs) using the `TypedResults.ServerSentEvents` API to return a stream of heart rate events as string, `ServerSentEvents`, and JSON objects to the client.
188+
175189
#### Redirect
176190

177191
:::code language="csharp" source="~/fundamentals/minimal-apis/9.0-samples/Snippets/Program.cs" id="snippet_09":::

aspnetcore/release-notes/aspnetcore-10.0.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ This section describes new features for minimal APIs.
3737

3838
[!INCLUDE[](~/release-notes/aspnetcore-10/includes/MinApiEmptyStringInFormPost.md)]
3939

40+
[!INCLUDE[](~/release-notes/aspnetcore-10/includes/sse.md)]
41+
4042
## OpenAPI
4143

4244
This section describes new features for OpenAPI.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
### Support for Server-Sent Events (SSE)
2+
3+
ASP.NET Core now supports returning a [ServerSentEvents](xref:System.Net.ServerSentEvents) result using the [TypedResults.ServerSentEvents](https://source.dot.net/#Microsoft.AspNetCore.Http.Results/TypedResults.cs,051e6796e1492f84) API. This feature is supported in both Minimal APIs and controller-based apps.
4+
5+
Server-Sent Events is a server push technology that allows a server to send a stream of event messages to a client over a single HTTP connection. In .NET the event messages are represented as [`SseItem<T>`](/dotnet/api/system.net.serversentevents.sseitem-1) objects, which may contain an event type, an ID, and a data payload of type `T`.
6+
7+
The [TypedResults](xref:Microsoft.AspNetCore.Http.TypedResults) class has a new static method called [ServerSentEvents](https://source.dot.net/#Microsoft.AspNetCore.Http.Results/TypedResults.cs,ceb980606eb9e295) that can be used to return a `ServerSentEvents` result. The first parameter to this method is an `IAsyncEnumerable<SseItem<T>>` that represents the stream of event messages to be sent to the client.
8+
9+
The following example illustrates how to use the `TypedResults.ServerSentEvents` API to return a stream of heart rate events as JSON objects to the client:
10+
11+
:::code language="csharp" source="~/fundamentals/minimal-apis/10.0-samples/MinimalServerSentEvents/Program.cs" id="snippet_json" :::
12+
13+
For more information, see:
14+
15+
- [Server-Sent Events](https://developer.mozilla.org/docs/Web/API/Server-sent_events) on MDN.
16+
- [Minimal API sample app](https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/fundamentals/minimal-apis/10.0-samples/MinimalServerSentEvents/Program.cs) using the `TypedResults.ServerSentEvents` API to return a stream of heart rate events as string, `ServerSentEvents`, and JSON objects to the client.
17+
- [Controller API sample app](https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/web-api/action-return-types/samples/10/ControllerSSE) using the `TypedResults.ServerSentEvents` API to return a stream of heart rate events as string, `ServerSentEvents`, and JSON objects to the client.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0-*" />
11+
</ItemGroup>
12+
13+
</Project>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@baseUrl = http://localhost:5201/HeartRate
2+
3+
### Connect to SSE stream
4+
# This request will open an SSE connection that stays open
5+
GET {{baseUrl}}/string-item
6+
Accept: text/event-stream
7+
8+
###
9+
GET {{baseUrl}}/json-item
10+
Accept: text/event-stream
11+
12+
###
13+
14+
GET {{baseUrl}}/sse-item
15+
Accept: text/event-stream
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using System.Runtime.CompilerServices;
3+
using System.Net.ServerSentEvents;
4+
5+
[ApiController]
6+
[Route("[controller]")]
7+
8+
public class HeartRateController : ControllerBase
9+
{
10+
// /HeartRate/json-item
11+
[HttpGet("json-item")]
12+
public IResult GetHeartRateJson(CancellationToken cancellationToken)
13+
{
14+
async IAsyncEnumerable<HearRate> StreamHeartRates(
15+
[EnumeratorCancellation] CancellationToken cancellationToken)
16+
{
17+
while (!cancellationToken.IsCancellationRequested)
18+
{
19+
var heartRate = Random.Shared.Next(60, 100);
20+
yield return HearRate.Create(heartRate);
21+
await Task.Delay(2000, cancellationToken);
22+
}
23+
}
24+
25+
return TypedResults.ServerSentEvents(StreamHeartRates(cancellationToken), eventType: "heartRate");
26+
}
27+
28+
// /HeartRate/string-item
29+
[HttpGet("string-item")]
30+
31+
public IResult GetHeartRateString(CancellationToken cancellationToken)
32+
{
33+
async IAsyncEnumerable<string> GetHeartRate(
34+
[EnumeratorCancellation] CancellationToken cancellationToken)
35+
{
36+
while (!cancellationToken.IsCancellationRequested)
37+
{
38+
var heartRate = Random.Shared.Next(60, 100);
39+
yield return $"Hear Rate: {heartRate} bpm";
40+
await Task.Delay(2000, cancellationToken);
41+
}
42+
}
43+
44+
return TypedResults.ServerSentEvents(GetHeartRate(cancellationToken), eventType: "heartRate");
45+
}
46+
47+
// /HeartRate/sse-item
48+
[HttpGet("sse-item")]
49+
50+
public IResult GetHeartRateSSE(CancellationToken cancellationToken)
51+
{
52+
async IAsyncEnumerable<SseItem<int>> GetHeartRate(
53+
[EnumeratorCancellation] CancellationToken cancellationToken)
54+
{
55+
while (!cancellationToken.IsCancellationRequested)
56+
{
57+
var heartRate = Random.Shared.Next(60, 100);
58+
yield return new SseItem<int>(heartRate, eventType: "heartRate")
59+
{
60+
ReconnectionInterval = TimeSpan.FromMinutes(1)
61+
};
62+
await Task.Delay(2000, cancellationToken);
63+
}
64+
}
65+
66+
return TypedResults.ServerSentEvents(GetHeartRate(cancellationToken));
67+
}
68+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
public record HeartRate(DateTime Timestamp, int HeartRate)
2+
{
3+
public static HeartRate Create(int heartRate) => new(DateTime.UtcNow, heartRate);
4+
}

0 commit comments

Comments
 (0)