diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 7c9ee52..806954c 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -27,6 +27,7 @@ jobs:
dotnet-version: |
8.0.x
9.0.x
+ 10.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
diff --git a/Directory.Build.props b/Directory.Build.props
index 6dc475d..5a67be1 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -18,6 +18,6 @@
MIT
True
- 1.3.5
+ 1.4.0
\ No newline at end of file
diff --git a/README.md b/README.md
index aa26c42..4ffb115 100644
--- a/README.md
+++ b/README.md
@@ -117,10 +117,7 @@ internal class HelloWorldRequestValidator : AbstractValidator
{
public HelloWorldRequestValidator()
{
- RuleFor(x => x.Name)
- .NotEmpty()
- .MinimumLength(3)
- .MaximumLength(50);
+ RuleFor(x => x.Name).NotEmpty().MinimumLength(3).MaximumLength(50);
}
}
@@ -151,19 +148,7 @@ internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary
internal class GetWeatherForecast : MinimalEndpoint
{
- private static readonly string[] _summaries =
- [
- "Freezing",
- "Bracing",
- "Chilly",
- "Cool",
- "Mild",
- "Warm",
- "Balmy",
- "Hot",
- "Sweltering",
- "Scorching"
- ];
+ private static readonly string[] _summaries = ["Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"];
protected override void Configure(
EndpointConfigurationBuilder builder,
diff --git a/docs/IAsyncEnumerableResponse.md b/docs/IAsyncEnumerableResponse.md
index e24b643..90e1df2 100644
--- a/docs/IAsyncEnumerableResponse.md
+++ b/docs/IAsyncEnumerableResponse.md
@@ -2,12 +2,10 @@
`MinimalEndpointWithStreamingResponse` is a specialized base class designed to simplify the implementation of minimal APIs that return streaming responses in .NET. It provides a structured way to define endpoints that stream data asynchronously using `IAsyncEnumerable`.
+> **Note:** .Net 10 introduced `ServerSentEventsResult` IResult type to return IAsyncEnumerable responses which can be used by a `MinimalEndpoint`. See below for more information.
+
``` csharp
-public record ListCustomersResponse(
- Guid Id,
- string FirstName,
- string? MiddleName,
- string LastName);
+public record ListCustomersResponse(Guid Id, string FirstName, string? MiddleName, string LastName);
internal class ListCustomers(ServiceDbContext db)
: MinimalEndpointWithStreamingResponse
@@ -58,4 +56,91 @@ internal class ListStores(ServiceDbContext db)
}
}
}
+```
+
+## For .Net 10 or Later
+
+By utilizing `ServerSentEventResult`, it's possible to return streaming responses with `MinimalEndpoint`.
+```csharp
+internal class GetStreamingWeatherForecastSse
+ : MinimalEndpoint
+{
+ private static readonly string[] _summaries = ["Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"];
+
+ protected override void Configure(
+ EndpointConfigurationBuilder builder,
+ ConfigurationContext configurationContext)
+ {
+ builder.MapGet("/streamingweatherforecastsse")
+ .WithName("GetStreamingWeatherForecastSse")
+ .WithTags("WeatherForecastWebApi")
+ .Produces>(contentType: "text/event-stream");
+ }
+
+ protected override async Task HandleAsync(CancellationToken ct)
+ {
+ await Task.CompletedTask;
+ return Results.ServerSentEvents(GetForecast(ct));
+
+ async IAsyncEnumerable GetForecast([EnumeratorCancellation] CancellationToken ct)
+ {
+ var forecast = Enumerable.Range(1, 5).Select(index =>
+ new WeatherForecast
+ (
+ DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
+ Random.Shared.Next(-20, 55),
+ _summaries[Random.Shared.Next(_summaries.Length)]
+ ))
+ .ToArray();
+
+ foreach (var item in forecast)
+ {
+ yield return item;
+ await Task.Delay(500, ct);
+ }
+ }
+ }
+}
+```
+
+Instead of using `IResult` response type, it's also possible to use TypedResults for a more type safe approach.
+```csharp
+internal class GetStreamingWeatherForecastTypedSse
+ : MinimalEndpoint, ProblemHttpResult>>
+{
+ private static readonly string[] _summaries = ["Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"];
+
+ protected override void Configure(
+ EndpointConfigurationBuilder builder,
+ ConfigurationContext configurationContext)
+ {
+ builder.MapGet("/streamingweatherforecasttypedsse")
+ .WithName("GetStreamingWeatherForecastTypedSse")
+ .WithTags("WeatherForecastWebApi");
+ }
+
+ protected override async Task, ProblemHttpResult>> HandleAsync(CancellationToken ct)
+ {
+ await Task.CompletedTask;
+ return TypedResults.ServerSentEvents(GetForecast(ct));
+
+ async IAsyncEnumerable GetForecast([EnumeratorCancellation] CancellationToken ct)
+ {
+ var forecast = Enumerable.Range(1, 5).Select(index =>
+ new WeatherForecast
+ (
+ DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
+ Random.Shared.Next(-20, 55),
+ _summaries[Random.Shared.Next(_summaries.Length)]
+ ))
+ .ToArray();
+
+ foreach (var item in forecast)
+ {
+ yield return item;
+ await Task.Delay(500, ct);
+ }
+ }
+ }
+}
```
\ No newline at end of file
diff --git a/samples/BenchmarkWebApi/BenchmarkFiles/Results/1.4.0/basic_benchmark_results.txt b/samples/BenchmarkWebApi/BenchmarkFiles/Results/1.4.0/basic_benchmark_results.txt
new file mode 100644
index 0000000..451e66d
--- /dev/null
+++ b/samples/BenchmarkWebApi/BenchmarkFiles/Results/1.4.0/basic_benchmark_results.txt
@@ -0,0 +1,121 @@
+ k6 .\k6 run minimal_api_basic.js
+
+ /\ Grafana /‾‾/
+ /\ / \ |\ __ / /
+ / \/ \ | |/ / / ‾‾\
+ / \ | ( | (‾) |
+ / __________ \ |_|\_\ \_____/
+
+ execution: local
+ script: minimal_api_basic.js
+ output: -
+
+ scenarios: (100.00%) 1 scenario, 100 max VUs, 2m20s max duration (incl. graceful stop):
+ * default: Up to 100 looping VUs for 1m50s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s)
+
+
+ ✓ status was 200
+
+ checks.........................: 100.00% 8933755 out of 8933755
+ data_received..................: 1.5 GB 14 MB/s
+ data_sent......................: 911 MB 8.3 MB/s
+ http_req_blocked...............: avg=2.97µs min=0s med=0s max=30.58ms p(90)=0s p(95)=0s
+ http_req_connecting............: avg=9ns min=0s med=0s max=2.99ms p(90)=0s p(95)=0s
+ ✓ http_req_duration..............: avg=836.33µs min=0s med=999.1µs max=49.71ms p(90)=1.67ms p(95)=2ms
+ { expected_response:true }...: avg=836.33µs min=0s med=999.1µs max=49.71ms p(90)=1.67ms p(95)=2ms
+ http_req_failed................: 0.00% 0 out of 8933755
+ http_req_receiving.............: avg=29.59µs min=0s med=0s max=41.83ms p(90)=0s p(95)=0s
+ http_req_sending...............: avg=9.27µs min=0s med=0s max=33.06ms p(90)=0s p(95)=0s
+ http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
+ http_req_waiting...............: avg=797.46µs min=0s med=998.8µs max=39.71ms p(90)=1.58ms p(95)=2ms
+ http_reqs......................: 8933755 81215.461568/s
+ iteration_duration.............: avg=934.51µs min=0s med=999.6µs max=72.4ms p(90)=1.99ms p(95)=2.02ms
+ iterations.....................: 8933755 81215.461568/s
+ vus............................: 1 min=1 max=100
+ vus_max........................: 100 min=100 max=100
+
+
+running (1m50.0s), 000/100 VUs, 8933755 complete and 0 interrupted iterations
+default ✓ [======================================] 000/100 VUs 1m50s
+
+
+ k6 .\k6 run minimal_endpoint_basic.js
+
+ /\ Grafana /‾‾/
+ /\ / \ |\ __ / /
+ / \/ \ | |/ / / ‾‾\
+ / \ | ( | (‾) |
+ / __________ \ |_|\_\ \_____/
+
+ execution: local
+ script: minimal_endpoint_basic.js
+ output: -
+
+ scenarios: (100.00%) 1 scenario, 100 max VUs, 2m20s max duration (incl. graceful stop):
+ * default: Up to 100 looping VUs for 1m50s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s)
+
+
+ ✓ status was 200
+
+ checks.........................: 100.00% 8791923 out of 8791923
+ data_received..................: 1.5 GB 14 MB/s
+ data_sent......................: 941 MB 8.6 MB/s
+ http_req_blocked...............: avg=2.95µs min=0s med=0s max=36.05ms p(90)=0s p(95)=0s
+ http_req_connecting............: avg=8ns min=0s med=0s max=2.54ms p(90)=0s p(95)=0s
+ ✓ http_req_duration..............: avg=861.35µs min=0s med=999.4µs max=42.99ms p(90)=1.69ms p(95)=2ms
+ { expected_response:true }...: avg=861.35µs min=0s med=999.4µs max=42.99ms p(90)=1.69ms p(95)=2ms
+ http_req_failed................: 0.00% 0 out of 8791923
+ http_req_receiving.............: avg=30.08µs min=0s med=0s max=40.5ms p(90)=0s p(95)=0s
+ http_req_sending...............: avg=9.51µs min=0s med=0s max=41.34ms p(90)=0s p(95)=0s
+ http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
+ http_req_waiting...............: avg=821.75µs min=0s med=999.1µs max=42.51ms p(90)=1.6ms p(95)=2ms
+ http_reqs......................: 8791923 79926.279906/s
+ iteration_duration.............: avg=951.89µs min=0s med=999.8µs max=58.78ms p(90)=1.99ms p(95)=2.01ms
+ iterations.....................: 8791923 79926.279906/s
+ vus............................: 1 min=1 max=100
+ vus_max........................: 100 min=100 max=100
+
+
+running (1m50.0s), 000/100 VUs, 8791923 complete and 0 interrupted iterations
+default ✓ [======================================] 000/100 VUs 1m50s
+
+
+ k6 .\k6 run webresult_endpoint_basic.js
+
+ /\ Grafana /‾‾/
+ /\ / \ |\ __ / /
+ / \/ \ | |/ / / ‾‾\
+ / \ | ( | (‾) |
+ / __________ \ |_|\_\ \_____/
+
+ execution: local
+ script: webresult_endpoint_basic.js
+ output: -
+
+ scenarios: (100.00%) 1 scenario, 100 max VUs, 2m20s max duration (incl. graceful stop):
+ * default: Up to 100 looping VUs for 1m50s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s)
+
+
+ ✓ status was 200
+
+ checks.........................: 100.00% 8763420 out of 8763420
+ data_received..................: 1.5 GB 14 MB/s
+ data_sent......................: 955 MB 8.7 MB/s
+ http_req_blocked...............: avg=3µs min=0s med=0s max=31.14ms p(90)=0s p(95)=0s
+ http_req_connecting............: avg=11ns min=0s med=0s max=16.58ms p(90)=0s p(95)=0s
+ ✓ http_req_duration..............: avg=867.84µs min=0s med=999.5µs max=110.19ms p(90)=1.73ms p(95)=2ms
+ { expected_response:true }...: avg=867.84µs min=0s med=999.5µs max=110.19ms p(90)=1.73ms p(95)=2ms
+ http_req_failed................: 0.00% 0 out of 8763420
+ http_req_receiving.............: avg=29.66µs min=0s med=0s max=42.15ms p(90)=0s p(95)=0s
+ http_req_sending...............: avg=9.25µs min=0s med=0s max=52.18ms p(90)=0s p(95)=0s
+ http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
+ http_req_waiting...............: avg=828.92µs min=0s med=999.2µs max=109.87ms p(90)=1.62ms p(95)=2ms
+ http_reqs......................: 8763420 79666.408091/s
+ iteration_duration.............: avg=958.27µs min=0s med=999.9µs max=110.7ms p(90)=1.99ms p(95)=2.01ms
+ iterations.....................: 8763420 79666.408091/s
+ vus............................: 1 min=1 max=100
+ vus_max........................: 100 min=100 max=100
+
+
+running (1m50.0s), 000/100 VUs, 8763420 complete and 0 interrupted iterations
+default ✓ [======================================] 000/100 VUs 1m50s
\ No newline at end of file
diff --git a/samples/BenchmarkWebApi/BenchmarkFiles/Results/1.4.0/inprocess_benchmark_results.txt b/samples/BenchmarkWebApi/BenchmarkFiles/Results/1.4.0/inprocess_benchmark_results.txt
new file mode 100644
index 0000000..5167431
--- /dev/null
+++ b/samples/BenchmarkWebApi/BenchmarkFiles/Results/1.4.0/inprocess_benchmark_results.txt
@@ -0,0 +1,121 @@
+ k6 .\k6 run minimal_api_inprocess.js
+
+ /\ Grafana /‾‾/
+ /\ / \ |\ __ / /
+ / \/ \ | |/ / / ‾‾\
+ / \ | ( | (‾) |
+ / __________ \ |_|\_\ \_____/
+
+ execution: local
+ script: minimal_api_inprocess.js
+ output: -
+
+ scenarios: (100.00%) 1 scenario, 100 max VUs, 2m20s max duration (incl. graceful stop):
+ * default: Up to 100 looping VUs for 1m50s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s)
+
+
+ ✓ status was 200
+
+ checks.........................: 100.00% 6423029 out of 6423029
+ data_received..................: 1.2 GB 11 MB/s
+ data_sent......................: 1.5 GB 14 MB/s
+ http_req_blocked...............: avg=3.81µs min=0s med=0s max=25.95ms p(90)=0s p(95)=0s
+ http_req_connecting............: avg=13ns min=0s med=0s max=4.06ms p(90)=0s p(95)=0s
+ ✓ http_req_duration..............: avg=1.17ms min=0s med=1ms max=97.84ms p(90)=2.01ms p(95)=2.52ms
+ { expected_response:true }...: avg=1.17ms min=0s med=1ms max=97.84ms p(90)=2.01ms p(95)=2.52ms
+ http_req_failed................: 0.00% 0 out of 6423029
+ http_req_receiving.............: avg=39.26µs min=0s med=0s max=65.01ms p(90)=0s p(95)=0s
+ http_req_sending...............: avg=16.14µs min=0s med=0s max=32.89ms p(90)=0s p(95)=0s
+ http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
+ http_req_waiting...............: avg=1.12ms min=0s med=1ms max=90.55ms p(90)=2.01ms p(95)=2.48ms
+ http_reqs......................: 6423029 58390.960662/s
+ iteration_duration.............: avg=1.31ms min=0s med=1.01ms max=97.84ms p(90)=2.14ms p(95)=2.63ms
+ iterations.....................: 6423029 58390.960662/s
+ vus............................: 1 min=1 max=100
+ vus_max........................: 100 min=100 max=100
+
+
+running (1m50.0s), 000/100 VUs, 6423029 complete and 0 interrupted iterations
+default ✓ [======================================] 000/100 VUs 1m50s
+
+
+ k6 .\k6 run minimal_endpoint_inprocess.js
+
+ /\ Grafana /‾‾/
+ /\ / \ |\ __ / /
+ / \/ \ | |/ / / ‾‾\
+ / \ | ( | (‾) |
+ / __________ \ |_|\_\ \_____/
+
+ execution: local
+ script: minimal_endpoint_inprocess.js
+ output: -
+
+ scenarios: (100.00%) 1 scenario, 100 max VUs, 2m20s max duration (incl. graceful stop):
+ * default: Up to 100 looping VUs for 1m50s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s)
+
+
+ ✓ status was 200
+
+ checks.........................: 100.00% 6333710 out of 6333710
+ data_received..................: 1.2 GB 11 MB/s
+ data_sent......................: 1.6 GB 14 MB/s
+ http_req_blocked...............: avg=3.7µs min=0s med=0s max=31.74ms p(90)=0s p(95)=0s
+ http_req_connecting............: avg=13ns min=0s med=0s max=3.36ms p(90)=0s p(95)=0s
+ ✓ http_req_duration..............: avg=1.18ms min=0s med=1ms max=61.27ms p(90)=2.03ms p(95)=2.52ms
+ { expected_response:true }...: avg=1.18ms min=0s med=1ms max=61.27ms p(90)=2.03ms p(95)=2.52ms
+ http_req_failed................: 0.00% 0 out of 6333710
+ http_req_receiving.............: avg=39.05µs min=0s med=0s max=47.52ms p(90)=0s p(95)=0s
+ http_req_sending...............: avg=16.27µs min=0s med=0s max=42.91ms p(90)=0s p(95)=0s
+ http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
+ http_req_waiting...............: avg=1.12ms min=0s med=1ms max=60.49ms p(90)=2.01ms p(95)=2.51ms
+ http_reqs......................: 6333710 57578.945745/s
+ iteration_duration.............: avg=1.32ms min=0s med=1.01ms max=61.27ms p(90)=2.22ms p(95)=2.77ms
+ iterations.....................: 6333710 57578.945745/s
+ vus............................: 1 min=1 max=100
+ vus_max........................: 100 min=100 max=100
+
+
+running (1m50.0s), 000/100 VUs, 6333710 complete and 0 interrupted iterations
+default ✓ [======================================] 000/100 VUs 1m50s
+
+
+ k6 .\k6 run webresult_endpoint_inprocess.js
+
+ /\ Grafana /‾‾/
+ /\ / \ |\ __ / /
+ / \/ \ | |/ / / ‾‾\
+ / \ | ( | (‾) |
+ / __________ \ |_|\_\ \_____/
+
+ execution: local
+ script: webresult_endpoint_inprocess.js
+ output: -
+
+ scenarios: (100.00%) 1 scenario, 100 max VUs, 2m20s max duration (incl. graceful stop):
+ * default: Up to 100 looping VUs for 1m50s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s)
+
+
+ ✓ status was 200
+
+ checks.........................: 100.00% 6217339 out of 6217339
+ data_received..................: 1.2 GB 11 MB/s
+ data_sent......................: 1.5 GB 14 MB/s
+ http_req_blocked...............: avg=3.77µs min=0s med=0s max=28.18ms p(90)=0s p(95)=0s
+ http_req_connecting............: avg=12ns min=0s med=0s max=2.12ms p(90)=0s p(95)=0s
+ ✓ http_req_duration..............: avg=1.2ms min=0s med=1ms max=73.32ms p(90)=2.05ms p(95)=2.55ms
+ { expected_response:true }...: avg=1.2ms min=0s med=1ms max=73.32ms p(90)=2.05ms p(95)=2.55ms
+ http_req_failed................: 0.00% 0 out of 6217339
+ http_req_receiving.............: avg=40.78µs min=0s med=0s max=54.83ms p(90)=0s p(95)=0s
+ http_req_sending...............: avg=16.74µs min=0s med=0s max=71.7ms p(90)=0s p(95)=0s
+ http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
+ http_req_waiting...............: avg=1.14ms min=0s med=1ms max=73.32ms p(90)=2.01ms p(95)=2.52ms
+ http_reqs......................: 6217339 56521.103168/s
+ iteration_duration.............: avg=1.35ms min=0s med=1.01ms max=73.83ms p(90)=2.27ms p(95)=2.86ms
+ iterations.....................: 6217339 56521.103168/s
+ vus............................: 1 min=1 max=100
+ vus_max........................: 100 min=100 max=100
+
+
+running (1m50.0s), 000/100 VUs, 6217339 complete and 0 interrupted iterations
+default ✓ [======================================] 000/100 VUs 1m50s
\ No newline at end of file
diff --git a/samples/BenchmarkWebApi/BenchmarkWebApi.csproj b/samples/BenchmarkWebApi/BenchmarkWebApi.csproj
index 81a8569..ff9ff3d 100644
--- a/samples/BenchmarkWebApi/BenchmarkWebApi.csproj
+++ b/samples/BenchmarkWebApi/BenchmarkWebApi.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net10.0
diff --git a/samples/ServiceEndpointClient/ServiceEndpointClient.csproj b/samples/ServiceEndpointClient/ServiceEndpointClient.csproj
index d64a314..12f8154 100644
--- a/samples/ServiceEndpointClient/ServiceEndpointClient.csproj
+++ b/samples/ServiceEndpointClient/ServiceEndpointClient.csproj
@@ -6,8 +6,8 @@
-
-
+
+
diff --git a/samples/ShowcaseWebApi/ShowcaseWebApi.csproj b/samples/ShowcaseWebApi/ShowcaseWebApi.csproj
index 2eabba7..78c8a16 100644
--- a/samples/ShowcaseWebApi/ShowcaseWebApi.csproj
+++ b/samples/ShowcaseWebApi/ShowcaseWebApi.csproj
@@ -5,12 +5,12 @@
-
-
+
+
-
+
diff --git a/samples/WeatherForecastWebApi/GetStreamingWeatherForecast.cs b/samples/WeatherForecastWebApi/GetStreamingWeatherForecast.cs
index c5e7822..454a8da 100644
--- a/samples/WeatherForecastWebApi/GetStreamingWeatherForecast.cs
+++ b/samples/WeatherForecastWebApi/GetStreamingWeatherForecast.cs
@@ -7,19 +7,7 @@ namespace WeatherForecastWebApi;
internal class GetStreamingWeatherForecast
: MinimalEndpointWithStreamingResponse
{
- private static readonly string[] _summaries =
- [
- "Freezing",
- "Bracing",
- "Chilly",
- "Cool",
- "Mild",
- "Warm",
- "Balmy",
- "Hot",
- "Sweltering",
- "Scorching"
- ];
+ private static readonly string[] _summaries = ["Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"];
protected override void Configure(
EndpointConfigurationBuilder builder,
diff --git a/samples/WeatherForecastWebApi/GetStreamingWeatherForecastSse.cs b/samples/WeatherForecastWebApi/GetStreamingWeatherForecastSse.cs
new file mode 100644
index 0000000..50601e5
--- /dev/null
+++ b/samples/WeatherForecastWebApi/GetStreamingWeatherForecastSse.cs
@@ -0,0 +1,46 @@
+using System.Net.ServerSentEvents;
+using System.Runtime.CompilerServices;
+using FluentValidation;
+using ModEndpoints.Core;
+
+namespace WeatherForecastWebApi;
+
+internal class GetStreamingWeatherForecastSse
+ : MinimalEndpoint
+{
+ private static readonly string[] _summaries = ["Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"];
+
+ protected override void Configure(
+ EndpointConfigurationBuilder builder,
+ ConfigurationContext configurationContext)
+ {
+ builder.MapGet("/streamingweatherforecastsse")
+ .WithName("GetStreamingWeatherForecastSse")
+ .WithTags("WeatherForecastWebApi")
+ .Produces>(contentType: "text/event-stream");
+ }
+
+ protected override async Task HandleAsync(CancellationToken ct)
+ {
+ await Task.CompletedTask;
+ return Results.ServerSentEvents(GetForecast(ct));
+
+ async IAsyncEnumerable GetForecast([EnumeratorCancellation] CancellationToken ct)
+ {
+ var forecast = Enumerable.Range(1, 5).Select(index =>
+ new WeatherForecast
+ (
+ DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
+ Random.Shared.Next(-20, 55),
+ _summaries[Random.Shared.Next(_summaries.Length)]
+ ))
+ .ToArray();
+
+ foreach (var item in forecast)
+ {
+ yield return item;
+ await Task.Delay(500, ct);
+ }
+ }
+ }
+}
diff --git a/samples/WeatherForecastWebApi/GetStreamingWeatherForecastTypedSse.cs b/samples/WeatherForecastWebApi/GetStreamingWeatherForecastTypedSse.cs
new file mode 100644
index 0000000..8d19761
--- /dev/null
+++ b/samples/WeatherForecastWebApi/GetStreamingWeatherForecastTypedSse.cs
@@ -0,0 +1,45 @@
+using System.Runtime.CompilerServices;
+using FluentValidation;
+using Microsoft.AspNetCore.Http.HttpResults;
+using ModEndpoints.Core;
+
+namespace WeatherForecastWebApi;
+
+internal class GetStreamingWeatherForecastTypedSse
+ : MinimalEndpoint, ProblemHttpResult>>
+{
+ private static readonly string[] _summaries = ["Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"];
+
+ protected override void Configure(
+ EndpointConfigurationBuilder builder,
+ ConfigurationContext configurationContext)
+ {
+ builder.MapGet("/streamingweatherforecasttypedsse")
+ .WithName("GetStreamingWeatherForecastTypedSse")
+ .WithTags("WeatherForecastWebApi");
+ }
+
+ protected override async Task, ProblemHttpResult>> HandleAsync(CancellationToken ct)
+ {
+ await Task.CompletedTask;
+ return TypedResults.ServerSentEvents(GetForecast(ct));
+
+ async IAsyncEnumerable GetForecast([EnumeratorCancellation] CancellationToken ct)
+ {
+ var forecast = Enumerable.Range(1, 5).Select(index =>
+ new WeatherForecast
+ (
+ DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
+ Random.Shared.Next(-20, 55),
+ _summaries[Random.Shared.Next(_summaries.Length)]
+ ))
+ .ToArray();
+
+ foreach (var item in forecast)
+ {
+ yield return item;
+ await Task.Delay(500, ct);
+ }
+ }
+ }
+}
diff --git a/samples/WeatherForecastWebApi/GetWeatherForecast.cs b/samples/WeatherForecastWebApi/GetWeatherForecast.cs
index 1d5f8bf..cd025f9 100644
--- a/samples/WeatherForecastWebApi/GetWeatherForecast.cs
+++ b/samples/WeatherForecastWebApi/GetWeatherForecast.cs
@@ -9,19 +9,7 @@ internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary
internal class GetWeatherForecast : MinimalEndpoint
{
- private static readonly string[] _summaries =
- [
- "Freezing",
- "Bracing",
- "Chilly",
- "Cool",
- "Mild",
- "Warm",
- "Balmy",
- "Hot",
- "Sweltering",
- "Scorching"
- ];
+ private static readonly string[] _summaries = ["Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"];
protected override void Configure(
EndpointConfigurationBuilder builder,
diff --git a/samples/WeatherForecastWebApi/WeatherForecastWebApi.csproj b/samples/WeatherForecastWebApi/WeatherForecastWebApi.csproj
index 26265c1..5020fcf 100644
--- a/samples/WeatherForecastWebApi/WeatherForecastWebApi.csproj
+++ b/samples/WeatherForecastWebApi/WeatherForecastWebApi.csproj
@@ -1,14 +1,14 @@
- net9.0
+ net10.0
enable
enable
-
-
+
+
diff --git a/src/ModEndpoints.Core/ModEndpoints.Core.csproj b/src/ModEndpoints.Core/ModEndpoints.Core.csproj
index 4d11b25..a3c7151 100644
--- a/src/ModEndpoints.Core/ModEndpoints.Core.csproj
+++ b/src/ModEndpoints.Core/ModEndpoints.Core.csproj
@@ -1,7 +1,7 @@
- net8.0;net9.0
+ net8.0;net9.0;net10.0
diff --git a/src/ModEndpoints.RemoteServices.Core/ModEndpoints.RemoteServices.Core.csproj b/src/ModEndpoints.RemoteServices.Core/ModEndpoints.RemoteServices.Core.csproj
index a73e342..8705317 100644
--- a/src/ModEndpoints.RemoteServices.Core/ModEndpoints.RemoteServices.Core.csproj
+++ b/src/ModEndpoints.RemoteServices.Core/ModEndpoints.RemoteServices.Core.csproj
@@ -1,7 +1,7 @@
- netstandard2.0;net8.0;net9.0
+ netstandard2.0;net8.0;net9.0;net10.0
latest
diff --git a/src/ModEndpoints.RemoteServices/ModEndpoints.RemoteServices.csproj b/src/ModEndpoints.RemoteServices/ModEndpoints.RemoteServices.csproj
index 4b1d04c..b8e2e85 100644
--- a/src/ModEndpoints.RemoteServices/ModEndpoints.RemoteServices.csproj
+++ b/src/ModEndpoints.RemoteServices/ModEndpoints.RemoteServices.csproj
@@ -1,7 +1,7 @@
- net8.0;net9.0
+ net8.0;net9.0;net10.0
@@ -21,8 +21,8 @@
-
-
+
+
diff --git a/src/ModEndpoints/ModEndpoints.csproj b/src/ModEndpoints/ModEndpoints.csproj
index 2e25322..3861fc8 100644
--- a/src/ModEndpoints/ModEndpoints.csproj
+++ b/src/ModEndpoints/ModEndpoints.csproj
@@ -1,7 +1,7 @@
- net8.0;net9.0
+ net8.0;net9.0;net10.0
@@ -21,7 +21,7 @@
-
+
diff --git a/tests/ModEndpoints.TestServer.Customers/ModEndpoints.TestServer.Customers.csproj b/tests/ModEndpoints.TestServer.Customers/ModEndpoints.TestServer.Customers.csproj
index ef2300c..5c95dc6 100644
--- a/tests/ModEndpoints.TestServer.Customers/ModEndpoints.TestServer.Customers.csproj
+++ b/tests/ModEndpoints.TestServer.Customers/ModEndpoints.TestServer.Customers.csproj
@@ -1,7 +1,7 @@
- net8.0;net9.0
+ net8.0;net9.0;net10.0
enable
enable
diff --git a/tests/ModEndpoints.TestServer/ModEndpoints.TestServer.csproj b/tests/ModEndpoints.TestServer/ModEndpoints.TestServer.csproj
index 0681100..40f7e13 100644
--- a/tests/ModEndpoints.TestServer/ModEndpoints.TestServer.csproj
+++ b/tests/ModEndpoints.TestServer/ModEndpoints.TestServer.csproj
@@ -1,7 +1,7 @@
- net8.0;net9.0
+ net8.0;net9.0;net10.0
enable
enable
diff --git a/tests/ModEndpoints.Tests/ModEndpoints.Tests.csproj b/tests/ModEndpoints.Tests/ModEndpoints.Tests.csproj
index 2edc9ea..ca6b63f 100644
--- a/tests/ModEndpoints.Tests/ModEndpoints.Tests.csproj
+++ b/tests/ModEndpoints.Tests/ModEndpoints.Tests.csproj
@@ -1,7 +1,7 @@
- net8.0;net9.0
+ net8.0;net9.0;net10.0
enable
enable
@@ -14,23 +14,28 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
-
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+
+
+
-
+
+
+
+