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 - + + + + - + + + +