Skip to content

Commit 0ff1db4

Browse files
authored
Merge pull request #35148 from dotnet/main
2 parents b1107f9 + 85856f2 commit 0ff1db4

File tree

7 files changed

+354
-5
lines changed

7 files changed

+354
-5
lines changed

aspnetcore/fundamentals/configuration/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Application configuration in ASP.NET Core is performed using one or more [config
2828
* Directory files
2929
* In-memory .NET objects
3030

31-
This article provides information on configuration in ASP.NET Core. For information on using configuration in console apps, see [.NET Configuration](/dotnet/core/extensions/configuration).
31+
This article provides information on configuration in ASP.NET Core. For information on using configuration in non-ASP.NET Core apps, see [.NET Configuration](/dotnet/core/extensions/configuration).
3232

3333
For Blazor configuration guidance, which adds to or supersedes the guidance in this node, see <xref:blazor/fundamentals/configuration>.
3434

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<RootNamespace>MinimalServerSentEvents</RootNamespace>
8+
</PropertyGroup>
9+
10+
</Project>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@baseUrl = http://localhost:5293
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: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System.Net.ServerSentEvents;
2+
using System.Runtime.CompilerServices;
3+
4+
var builder = WebApplication.CreateBuilder(args);
5+
var app = builder.Build();
6+
7+
// <snippet_string>
8+
app.MapGet("/string-item", (CancellationToken cancellationToken) =>
9+
{
10+
async IAsyncEnumerable<string> GetHeartRate(
11+
[EnumeratorCancellation] CancellationToken cancellationToken)
12+
{
13+
while (!cancellationToken.IsCancellationRequested)
14+
{
15+
var heartRate = Random.Shared.Next(60, 100);
16+
yield return $"Hear Rate: {heartRate} bpm";
17+
await Task.Delay(2000, cancellationToken);
18+
}
19+
}
20+
21+
return TypedResults.ServerSentEvents(GetHeartRate(cancellationToken), eventType: "heartRate");
22+
});
23+
// </snippet_string>
24+
25+
// <snippet_json>
26+
app.MapGet("/json-item", (CancellationToken cancellationToken) =>
27+
{
28+
async IAsyncEnumerable<HearRate> GetHeartRate(
29+
[EnumeratorCancellation] CancellationToken cancellationToken)
30+
{
31+
while (!cancellationToken.IsCancellationRequested)
32+
{
33+
var heartRate = Random.Shared.Next(60, 100);
34+
yield return HearRate.Create(heartRate);
35+
await Task.Delay(2000, cancellationToken);
36+
}
37+
}
38+
39+
return TypedResults.ServerSentEvents(GetHeartRate(cancellationToken), eventType: "heartRate");
40+
});
41+
// </snippet_json>
42+
43+
// <snippet_item>
44+
app.MapGet("sse-item", (CancellationToken cancellationToken) =>
45+
{
46+
async IAsyncEnumerable<SseItem<int>> GetHeartRate(
47+
[EnumeratorCancellation] CancellationToken cancellationToken)
48+
{
49+
while (!cancellationToken.IsCancellationRequested)
50+
{
51+
var heartRate = Random.Shared.Next(60, 100);
52+
yield return new SseItem<int>(heartRate, eventType: "heartRate")
53+
{
54+
ReconnectionInterval = TimeSpan.FromMinutes(1)
55+
};
56+
await Task.Delay(2000, cancellationToken);
57+
}
58+
}
59+
60+
return TypedResults.ServerSentEvents(GetHeartRate(cancellationToken));
61+
});
62+
// </snippet_item>
63+
64+
65+
app.Run();
66+
67+
public record HearRate(DateTime Timestamp, int HeartRate)
68+
{
69+
public static HearRate Create(int heartRate) => new(DateTime.UtcNow, heartRate);
70+
}
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
:::moniker range="= aspnetcore-9.0"
2+
3+
Minimal endpoints support the following types of return values:
4+
5+
1. `string` - This includes `Task<string>` and `ValueTask<string>`.
6+
1. `T` (Any other type) - This includes `Task<T>` and `ValueTask<T>`.
7+
1. `IResult` based - This includes `Task<IResult>` and `ValueTask<IResult>`.
8+
9+
## `string` return values
10+
11+
|Behavior|Content-Type|
12+
|--|--|
13+
| The framework writes the string directly to the response. | `text/plain`
14+
15+
Consider the following route handler, which returns a `Hello world` text.
16+
17+
:::code language="csharp" source="~/fundamentals/minimal-apis/9.0-samples/Snippets/Program.cs" id="snippet_01":::
18+
19+
The `200` status code is returned with `text/plain` Content-Type header and the following content.
20+
21+
```text
22+
Hello World
23+
```
24+
25+
## `T` (Any other type) return values
26+
27+
|Behavior|Content-Type|
28+
|--|--|
29+
| The framework JSON-serializes the response.| `application/json`
30+
31+
Consider the following route handler, which returns an anonymous type containing a `Message` string property.
32+
33+
:::code language="csharp" source="~/fundamentals/minimal-apis/9.0-samples/Snippets/Program.cs" id="snippet_02":::
34+
35+
The `200` status code is returned with `application/json` Content-Type header and the following content.
36+
37+
```json
38+
{"message":"Hello World"}
39+
```
40+
41+
## `IResult` return values
42+
43+
|Behavior|Content-Type|
44+
|--|--|
45+
| The framework calls [IResult.ExecuteAsync](xref:Microsoft.AspNetCore.Http.IResult.ExecuteAsync%2A).| Decided by the `IResult` implementation.
46+
47+
The `IResult` interface defines a contract that represents the result of an HTTP endpoint. The static [Results](<xref:Microsoft.AspNetCore.Http.Results>) class and the static [TypedResults](<xref:Microsoft.AspNetCore.Http.TypedResults>) are used to create various `IResult` objects that represent different types of responses.
48+
49+
### TypedResults vs Results
50+
51+
The <xref:Microsoft.AspNetCore.Http.Results> and <xref:Microsoft.AspNetCore.Http.TypedResults> static classes provide similar sets of results helpers. The `TypedResults` class is the *typed* equivalent of the `Results` class. However, the `Results` helpers' return type is <xref:Microsoft.AspNetCore.Http.IResult>, while each `TypedResults` helper's return type is one of the `IResult` implementation types. The difference means that for `Results` helpers a conversion is needed when the concrete type is needed, for example, for unit testing. The implementation types are defined in the <xref:Microsoft.AspNetCore.Http.HttpResults> namespace.
52+
53+
Returning `TypedResults` rather than `Results` has the following advantages:
54+
55+
* `TypedResults` helpers return strongly typed objects, which can improve code readability, unit testing, and reduce the chance of runtime errors.
56+
* The implementation type [automatically provides the response type metadata for OpenAPI](/aspnet/core/fundamentals/openapi/aspnetcore-openapi#describe-response-types) to describe the endpoint.
57+
58+
Consider the following endpoint, for which a `200 OK` status code with the expected JSON response is produced.
59+
60+
:::code language="csharp" source="~/tutorials/min-web-api/samples/7.x/todo/Program.cs" id="snippet_11b":::
61+
62+
In order to document this endpoint correctly the extensions method `Produces` is called. However, it's not necessary to call `Produces` if `TypedResults` is used instead of `Results`, as shown in the following code. `TypedResults` automatically provides the metadata for the endpoint.
63+
64+
:::code language="csharp" source="~/tutorials/min-web-api/samples/7.x/todo/Program.cs" id="snippet_112b":::
65+
66+
For more information about describing a response type, see [OpenAPI support in minimal APIs](/aspnet/core/fundamentals/openapi/aspnetcore-openapi#describe-response-types-1).
67+
68+
As mentioned previously, when using `TypedResults`, a conversion is not needed. Consider the following minimal API which returns a `TypedResults` class
69+
70+
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/minimal-apis/samples/MinApiTestsSample/WebMinRouteGroup/TodoEndpointsV1.cs" id="snippet_1":::
71+
72+
The following test checks for the full concrete type:
73+
74+
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/minimal-apis/samples/MinApiTestsSample/UnitTests/TodoInMemoryTests.cs" id="snippet_11" highlight="26":::
75+
76+
Because all methods on `Results` return `IResult` in their signature, the compiler automatically infers that as the request delegate return type when returning different results from a single endpoint. `TypedResults` requires the use of `Results<T1, TN>` from such delegates.
77+
78+
The following method compiles because both [`Results.Ok`](xref:Microsoft.AspNetCore.Http.Results.Ok%2A) and [`Results.NotFound`](xref:Microsoft.AspNetCore.Http.Results.NotFound%2A) are declared as returning `IResult`, even though the actual concrete types of the objects returned are different:
79+
80+
:::code language="csharp" source="~/tutorials/min-web-api/samples/7.x/todo/Program.cs" id="snippet_1a":::
81+
82+
The following method does not compile, because `TypedResults.Ok` and `TypedResults.NotFound` are declared as returning different types and the compiler won't attempt to infer the best matching type:
83+
84+
:::code language="csharp" source="~/tutorials/min-web-api/samples/7.x/todo/Program.cs" id="snippet_111":::
85+
86+
To use `TypedResults`, the return type must be fully declared, which when asynchronous requires the `Task<>` wrapper. Using `TypedResults` is more verbose, but that's the trade-off for having the type information be statically available and thus capable of self-describing to OpenAPI:
87+
88+
:::code language="csharp" source="~/tutorials/min-web-api/samples/7.x/todo/Program.cs" id="snippet_1b":::
89+
90+
### Results<TResult1, TResultN>
91+
92+
Use [`Results<TResult1, TResultN>`](/dotnet/api/microsoft.aspnetcore.http.httpresults.results-2) as the endpoint handler return type instead of `IResult` when:
93+
94+
* Multiple `IResult` implementation types are returned from the endpoint handler.
95+
* The static `TypedResult` class is used to create the `IResult` objects.
96+
97+
This alternative is better than returning `IResult` because the generic union types automatically retain the endpoint metadata. And since the `Results<TResult1, TResultN>` union types implement implicit cast operators, the compiler can automatically convert the types specified in the generic arguments to an instance of the union type.
98+
99+
This has the added benefit of providing compile-time checking that a route handler actually only returns the results that it declares it does. Attempting to return a type that isn't declared as one of the generic arguments to `Results<>` results in a compilation error.
100+
101+
Consider the following endpoint, for which a `400 BadRequest` status code is returned when the `orderId` is greater than `999`. Otherwise, it produces a `200 OK` with the expected content.
102+
103+
:::code language="csharp" source="~/fundamentals/minimal-apis/9.0-samples/Snippets/Program.cs" id="snippet_03":::
104+
105+
In order to document this endpoint correctly the extension method `Produces` is called. However, since the `TypedResults` helper automatically includes the metadata for the endpoint, you can return the `Results<T1, Tn>` union type instead, as shown in the following code.
106+
107+
:::code language="csharp" source="~/fundamentals/minimal-apis/9.0-samples/Snippets/Program.cs" id="snippet_04":::
108+
109+
<a name="binr7"></a>
110+
111+
### Built-in results
112+
113+
[!INCLUDE [results-helpers](~/fundamentals/minimal-apis/includes/results-helpers.md)]
114+
115+
The following sections demonstrate the usage of the common result helpers.
116+
117+
#### JSON
118+
119+
:::code language="csharp" source="~/fundamentals/minimal-apis/9.0-samples/Snippets/Program.cs" id="snippet_05":::
120+
121+
<xref:Microsoft.AspNetCore.Http.HttpResponseJsonExtensions.WriteAsJsonAsync%2A> is an alternative way to return JSON:
122+
123+
:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/WebMinJson/Program.cs" id="snippet_writeasjsonasync":::
124+
125+
#### Custom Status Code
126+
127+
:::code language="csharp" source="~/fundamentals/minimal-apis/9.0-samples/Snippets/Program.cs" id="snippet_06":::
128+
129+
#### Internal Server Error
130+
131+
:::code language="csharp" source="~/fundamentals/minimal-apis/9.0-samples/Snippets/Program.cs" id="snippet_07":::
132+
133+
The preceding example returns a 500 status code.
134+
135+
#### Problem and ValidationProblem
136+
137+
:::code language="csharp" source="~/fundamentals/minimal-apis/9.0-samples/Snippets/Program.cs" id="snippet_12":::
138+
139+
#### Text
140+
141+
:::code language="csharp" source="~/fundamentals/minimal-apis/9.0-samples/Snippets/Program.cs" id="snippet_08":::
142+
143+
<a name="stream7"></a>
144+
145+
#### Stream
146+
147+
[!code-csharp[](~/fundamentals/minimal-apis/7.0-samples/WebMinAPIs/Program.cs?name=snippet_stream)]
148+
149+
[`Results.Stream`](/dotnet/api/microsoft.aspnetcore.http.results.stream?view=aspnetcore-7.0&preserve-view=true) overloads allow access to the underlying HTTP response stream without buffering. The following example uses [ImageSharp](https://sixlabors.com/products/imagesharp) to return a reduced size of the specified image:
150+
151+
[!code-csharp[](~/fundamentals/minimal-apis/resultsStream/7.0-samples/ResultsStreamSample/Program.cs?name=snippet)]
152+
153+
The following example streams an image from [Azure Blob storage](/azure/storage/blobs/storage-blobs-introduction):
154+
155+
[!code-csharp[](~/fundamentals/minimal-apis/resultsStream/7.0-samples/ResultsStreamSample/Program.cs?name=snippet_abs)]
156+
157+
The following example streams a video from an Azure Blob:
158+
159+
[!code-csharp[](~/fundamentals/minimal-apis/resultsStream/7.0-samples/ResultsStreamSample/Program.cs?name=snippet_video)]
160+
161+
#### Redirect
162+
163+
:::code language="csharp" source="~/fundamentals/minimal-apis/9.0-samples/Snippets/Program.cs" id="snippet_09":::
164+
165+
#### File
166+
167+
:::code language="csharp" source="~/fundamentals/minimal-apis/9.0-samples/Snippets/Program.cs" id="snippet_10":::
168+
169+
<a name="httpresultinterfaces7"></a>
170+
171+
### HttpResult interfaces
172+
173+
The following interfaces in the <xref:Microsoft.AspNetCore.Http> namespace provide a way to detect the `IResult` type at runtime, which is a common pattern in filter implementations:
174+
175+
* <xref:Microsoft.AspNetCore.Http.IContentTypeHttpResult>
176+
* <xref:Microsoft.AspNetCore.Http.IFileHttpResult>
177+
* <xref:Microsoft.AspNetCore.Http.INestedHttpResult>
178+
* <xref:Microsoft.AspNetCore.Http.IStatusCodeHttpResult>
179+
* <xref:Microsoft.AspNetCore.Http.IValueHttpResult>
180+
* <xref:Microsoft.AspNetCore.Http.IValueHttpResult%601>
181+
182+
Here's an example of a filter that uses one of these interfaces:
183+
184+
:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/HttpResultInterfaces/Program.cs" id="snippet_filter":::
185+
186+
For more information, see [Filters in Minimal API apps](xref:fundamentals/minimal-apis/min-api-filters) and [IResult implementation types](xref:fundamentals/minimal-apis/test-min-api#iresult-implementation-types).
187+
188+
## Modifying Headers
189+
190+
Use the `HttpResponse` object to modify response headers:
191+
192+
```csharp
193+
app.MapGet("/", (HttpContext context) => {
194+
// Set a custom header
195+
context.Response.Headers["X-Custom-Header"] = "CustomValue";
196+
197+
// Set a known header
198+
context.Response.Headers.CacheControl = $"public,max-age=3600";
199+
200+
return "Hello World";
201+
});
202+
```
203+
204+
## Customizing responses
205+
206+
Applications can control responses by implementing a custom <xref:Microsoft.AspNetCore.Http.IResult> type. The following code is an example of an HTML result type:
207+
208+
[!code-csharp[](~/fundamentals/minimal-apis/7.0-samples/WebMinAPIs/ResultsExtensions.cs)]
209+
210+
We recommend adding an extension method to <xref:Microsoft.AspNetCore.Http.IResultExtensions?displayProperty=fullName> to make these custom results more discoverable.
211+
212+
[!code-csharp[](~/fundamentals/minimal-apis/7.0-samples/WebMinAPIs/Program.cs?name=snippet_xtn)]
213+
214+
Also, a custom `IResult` type can provide its own annotation by implementing the <xref:Microsoft.AspNetCore.Http.Metadata.IEndpointMetadataProvider> interface. For example, the following code adds an annotation to the preceding `HtmlResult` type that describes the response produced by the endpoint.
215+
216+
[!code-csharp[](~/fundamentals/minimal-apis/7.0-samples/WebMinAPIs/Snippets/ResultsExtensions.cs?name=snippet_IEndpointMetadataProvider&highlight=1,17-20)]
217+
218+
The `ProducesHtmlMetadata` is an implementation of <xref:Microsoft.AspNetCore.Http.Metadata.IProducesResponseTypeMetadata> that defines the produced response content type `text/html` and the status code `200 OK`.
219+
220+
[!code-csharp[](~/fundamentals/minimal-apis/7.0-samples/WebMinAPIs/Snippets/ResultsExtensions.cs?name=snippet_ProducesHtmlMetadata&highlight=5,7)]
221+
222+
An alternative approach is using the <xref:Microsoft.AspNetCore.Mvc.ProducesAttribute?displayProperty=fullName> to describe the produced response. The following code changes the `PopulateMetadata` method to use `ProducesAttribute`.
223+
224+
:::code language="csharp" source="~/fundamentals/minimal-apis/9.0-samples/Snippets/Program.cs" id="snippet_11":::
225+
226+
## Configure JSON serialization options
227+
228+
By default, minimal API apps use [`Web defaults`](/dotnet/standard/serialization/system-text-json-configure-options#web-defaults-for-jsonserializeroptions) options during JSON serialization and deserialization.
229+
230+
### Configure JSON serialization options globally
231+
232+
Options can be configured globally for an app by invoking <xref:Microsoft.Extensions.DependencyInjection.HttpJsonServiceExtensions.ConfigureHttpJsonOptions%2A>. The following example includes public fields and formats JSON output.
233+
234+
:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/WebMinJson/Program.cs" id="snippet_confighttpjsonoptions" highlight="3-6":::
235+
236+
Since fields are included, the preceding code reads `NameField` and includes it in the output JSON.
237+
238+
### Configure JSON serialization options for an endpoint
239+
240+
To configure serialization options for an endpoint, invoke <xref:Microsoft.AspNetCore.Http.Results.Json%2A?displayProperty=nameWithType> and pass to it a <xref:System.Text.Json.JsonSerializerOptions> object, as shown in the following example:
241+
242+
:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/WebMinJson/Program.cs" id="snippet_resultsjsonwithoptions" highlight="5-6,9":::
243+
244+
As an alternative, use an overload of <xref:Microsoft.AspNetCore.Http.HttpResponseJsonExtensions.WriteAsJsonAsync%2A> that accepts a <xref:System.Text.Json.JsonSerializerOptions> object. The following example uses this overload to format the output JSON:
245+
246+
:::code language="csharp" source="~/fundamentals/minimal-apis/7.0-samples/WebMinJson/Program.cs" id="snippet_writeasjsonasyncwithoptions" highlight="5-6,10":::
247+
248+
## Additional Resources
249+
250+
* <xref:fundamentals/minimal-apis/security>
251+
252+
:::moniker-end

aspnetcore/fundamentals/minimal-apis/responses.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ uid: fundamentals/minimal-apis/responses
1212

1313
[!INCLUDE[](~/includes/not-latest-version.md)]
1414

15-
:::moniker range="> aspnetcore-8.0"
15+
:::moniker range=">= aspnetcore-10.0"
1616

1717
Minimal endpoints support the following types of return values:
1818

@@ -124,7 +124,7 @@ In order to document this endpoint correctly the extension method `Produces` is
124124

125125
### Built-in results
126126

127-
[!INCLUDE [results-helpers](includes/results-helpers.md)]
127+
[!INCLUDE [results-helpers](~/fundamentals/minimal-apis/includes/results-helpers.md)]
128128

129129
The following sections demonstrate the usage of the common result helpers.
130130

@@ -265,4 +265,6 @@ As an alternative, use an overload of <xref:Microsoft.AspNetCore.Http.HttpRespon
265265

266266
:::moniker-end
267267

268+
[!INCLUDE[](~/fundamentals/minimal-apis/includes/responses9.md)]
269+
268270
[!INCLUDE[](~/fundamentals/minimal-apis/includes/responses7-8.md)]

aspnetcore/grpc/native-aot.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ AOT compilation happens when the app is published. Native AOT is enabled with th
3030

3131
[!code-xml[](~/grpc/native-aot/Server.csproj?highlight=5)]
3232

33-
Native AOT can also be enabled by specifying the `-aot` option with the ASP.NET Core gRPC template:
33+
Native AOT can also be enabled by specifying the `--aot` option with the ASP.NET Core gRPC template:
3434

3535
```dotnetcli
36-
dotnet new grpc -aot
36+
dotnet new grpc --aot
3737
```
3838
3939
2. Publish the app for a specific [runtime identifier (RID)](/dotnet/core/rid-catalog) using `dotnet publish -r <RID>`.

0 commit comments

Comments
 (0)