Skip to content

Commit 2d7329a

Browse files
sander1095guardrex
andauthored
Add ProducesResponseType Description documentation (#35009)
* Add ProducesResponseType Description documentation The "What's new in .NET 10" release notes mention the new Description property I added, but the .NET 10 OpenAPI docs do not. This commit adds consistency * Updates * Apply suggestions from code review * Cross-link for the Minimal API content --------- Co-authored-by: Luke Latham <[email protected]>
1 parent 4758fd9 commit 2d7329a

File tree

1 file changed

+114
-70
lines changed

1 file changed

+114
-70
lines changed

aspnetcore/fundamentals/openapi/include-metadata.md

Lines changed: 114 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -48,28 +48,29 @@ Note that the attributes are placed on the delegate method and not on the app.Ma
4848

4949
```csharp
5050
app.MapGet("/extension-methods", () => "Hello world!")
51-
.WithSummary("This is a summary.")
52-
.WithDescription("This is a description.");
51+
.WithSummary("This is a summary.")
52+
.WithDescription("This is a description.");
5353

5454
app.MapGet("/attributes",
55-
[EndpointSummary("This is a summary.")]
56-
[EndpointDescription("This is a description.")]
57-
() => "Hello world!");
55+
[EndpointSummary("This is a summary.")]
56+
[EndpointDescription("This is a description.")]
57+
() => "Hello world!");
5858
```
5959

6060
#### [Controllers](#tab/controllers)
6161

6262
The following sample demonstrates how to set summaries and descriptions.
6363

6464
```csharp
65-
[EndpointSummary("This is a summary.")]
66-
[EndpointDescription("This is a description.")]
67-
[HttpGet("attributes")]
68-
public IResult Attributes()
69-
{
70-
return Results.Ok("Hello world!");
71-
}
65+
[EndpointSummary("This is a summary.")]
66+
[EndpointDescription("This is a description.")]
67+
[HttpGet("attributes")]
68+
public IResult Attributes()
69+
{
70+
return Results.Ok("Hello world!");
71+
}
7272
```
73+
7374
---
7475

7576
### tags
@@ -84,11 +85,11 @@ The following sample demonstrates the different strategies for setting tags.
8485

8586
```csharp
8687
app.MapGet("/extension-methods", () => "Hello world!")
87-
.WithTags("todos", "projects");
88+
.WithTags("todos", "projects");
8889

8990
app.MapGet("/attributes",
90-
[Tags("todos", "projects")]
91-
() => "Hello world!");
91+
[Tags("todos", "projects")]
92+
() => "Hello world!");
9293
```
9394

9495
#### [Controllers](#tab/controllers)
@@ -98,13 +99,14 @@ In controller-based apps, the controller name is automatically added as a tag on
9899
The following sample demonstrates how to set tags.
99100

100101
```csharp
101-
[Tags(["todos", "projects"])]
102-
[HttpGet("attributes")]
103-
public IResult Attributes()
104-
{
105-
return Results.Ok("Hello world!");
106-
}
102+
[Tags(["todos", "projects"])]
103+
[HttpGet("attributes")]
104+
public IResult Attributes()
105+
{
106+
return Results.Ok("Hello world!");
107+
}
107108
```
109+
108110
---
109111

110112
### operationId
@@ -119,11 +121,11 @@ The following sample demonstrates the different strategies for setting the opera
119121

120122
```csharp
121123
app.MapGet("/extension-methods", () => "Hello world!")
122-
.WithName("FromExtensionMethods");
124+
.WithName("FromExtensionMethods");
123125

124126
app.MapGet("/attributes",
125-
[EndpointName("FromAttributes")]
126-
() => "Hello world!");
127+
[EndpointName("FromAttributes")]
128+
() => "Hello world!");
127129
```
128130

129131
#### [Controllers](#tab/controllers)
@@ -133,13 +135,14 @@ In controller-based apps, the operationId can be set using the [`[EndpointName]`
133135
The following sample demonstrates how to set the operationId.
134136

135137
```csharp
136-
[EndpointName("FromAttributes")]
137-
[HttpGet("attributes")]
138-
public IResult Attributes()
139-
{
140-
return Results.Ok("Hello world!");
141-
}
138+
[EndpointName("FromAttributes")]
139+
[HttpGet("attributes")]
140+
public IResult Attributes()
141+
{
142+
return Results.Ok("Hello world!");
143+
}
142144
```
145+
143146
---
144147

145148
### parameters
@@ -152,24 +155,31 @@ The [`[Description]`](xref:System.ComponentModel.DescriptionAttribute) attribute
152155

153156
#### [Minimal APIs](#tab/minimal-apis)
154157

158+
The [`[Description]`](xref:System.ComponentModel.DescriptionAttribute) attribute works in an MVC app but doesn't work in a Minimal API app at this time. For more information, see [`Description` parameter of `ProducesResponseTypeAttribute` does not work in minimal API app (`dotnet/aspnetcore` #60518)](https://github.com/dotnet/aspnetcore/issues/60518).
159+
160+
<!-- For activation when https://github.com/dotnet/aspnetcore/issues/60518 is resolved ...
161+
155162
The follow sample demonstrates how to set a description for a parameter.
156163
157164
```csharp
158165
app.MapGet("/attributes",
159-
([Description("This is a description.")] string name) => "Hello world!");
166+
([Description("This is a description.")] string name) => "Hello world!");
160167
```
161168
169+
-->
170+
162171
#### [Controllers](#tab/controllers)
163172

164173
The following sample demonstrates how to set a description for a parameter.
165174

166175
```csharp
167-
[HttpGet("attributes")]
168-
public IResult Attributes([Description("This is a description.")] string name)
169-
{
170-
return Results.Ok("Hello world!");
171-
}
176+
[HttpGet("attributes")]
177+
public IResult Attributes([Description("This is a description.")] string name)
178+
{
179+
return Results.Ok("Hello world!");
180+
}
172181
```
182+
173183
---
174184

175185
### Describe the request body
@@ -218,7 +228,7 @@ In the following example, the endpoint accepts a `Todo` object in the request bo
218228

219229
```csharp
220230
app.MapPut("/todos/{id}", (int id, Todo todo) => ...)
221-
.Accepts<Todo>("application/xml");
231+
.Accepts<Todo>("application/xml");
222232
```
223233

224234
Since `application/xml` is not a built-in content type, the `Todo` class must implement the <xref:Microsoft.AspNetCore.Http.IBindableFromHttpContext%601> interface to provide a custom binding for the request body. For example:
@@ -232,6 +242,7 @@ public class Todo : IBindableFromHttpContext<Todo>
232242
var serializer = new XmlSerializer(typeof(Todo));
233243
return (Todo?)serializer.Deserialize(xmlDoc.CreateReader());
234244
}
245+
}
235246
```
236247

237248
If the endpoint doesn't define any parameters bound to the request body, use the <xref:Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.Accepts%2A> extension method to specify the content type that the endpoint accepts.
@@ -292,7 +303,7 @@ The <xref:Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.Produce
292303

293304
```csharp
294305
app.MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync())
295-
.Produces<IList<Todo>>();
306+
.Produces<IList<Todo>>();
296307
```
297308

298309
The [`[ProducesResponseType]`](xref:Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute) can be used to add response metadata to an endpoint. Note that the attribute is applied to the route handler method, not the method invocation to create the route, as shown in the following example:
@@ -303,6 +314,21 @@ app.MapGet("/todos",
303314
async (TodoDb db) => await db.Todos.ToListAsync());
304315
```
305316

317+
:::moniker range=">= aspnetcore-10.0"
318+
319+
[`[ProducesResponseType]`](xref:Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute), [`[Produces]`](xref:Microsoft.AspNetCore.Mvc.ProducesAttribute), and [`[ProducesDefaultResponseType]`](xref:Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute) also support an optional string property called `Description` that can be used to describe the response. This is useful for explaining why or when clients can expect a specific response:
320+
321+
```csharp
322+
app.MapGet("/todos/{id}",
323+
[ProducesResponseType<Todo>(200,
324+
Description = "Returns the requested Todo item.")]
325+
[ProducesResponseType(404, Description = "Requested item not found.")]
326+
[ProducesDefault(Description = "Undocumented status code.")]
327+
async (int id, TodoDb db) => /* Code here */);
328+
```
329+
330+
:::moniker-end
331+
306332
Using <xref:Microsoft.AspNetCore.Http.TypedResults> in the implementation of an endpoint's route handler automatically includes the response type metadata for the endpoint. For example, the following code automatically annotates the endpoint with a response under the `200` status code with an `application/json` content type.
307333

308334
```csharp
@@ -315,19 +341,19 @@ app.MapGet("/todos", async (TodoDb db) =>
315341

316342
Only return types that implement <xref:Microsoft.AspNetCore.Http.Metadata.IEndpointMetadataProvider> create a `responses` entry in the OpenAPI document. The following is a partial list of some of the <xref:Microsoft.AspNetCore.Http.TypedResults> helper methods that produce a `responses` entry:
317343

318-
| TypedResults helper method | status code |
319-
| -------------------------- | ----------- |
320-
| Ok() | 200 |
321-
| Created() | 201 |
322-
| CreatedAtRoute() | 201 |
323-
| Accepted() | 202 |
324-
| AcceptedAtRoute() | 202 |
325-
| NoContent() | 204 |
326-
| BadRequest() | 400 |
327-
| ValidationProblem() | 400 |
328-
| NotFound() | 404 |
329-
| Conflict() | 409 |
330-
| UnprocessableEntity() | 422 |
344+
| `TypedResults` helper method | Status code |
345+
| ---------------------------- | ----------- |
346+
| Ok() | 200 |
347+
| Created() | 201 |
348+
| CreatedAtRoute() | 201 |
349+
| Accepted() | 202 |
350+
| AcceptedAtRoute() | 202 |
351+
| NoContent() | 204 |
352+
| BadRequest() | 400 |
353+
| ValidationProblem() | 400 |
354+
| NotFound() | 404 |
355+
| Conflict() | 409 |
356+
| UnprocessableEntity() | 422 |
331357

332358
All of these methods except `NoContent` have a generic overload that specifies the type of the response body.
333359

@@ -372,12 +398,29 @@ Only one [`[Produces]`](xref:Microsoft.AspNetCore.Mvc.ProducesAttribute) or <xre
372398

373399
All of the above attributes can be applied to individual action methods or to the controller class where it applies to all action methods in the controller.
374400

401+
:::moniker range=">= aspnetcore-10.0"
402+
403+
[`[ProducesResponseType]`](xref:Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute), [`[Produces]`](xref:Microsoft.AspNetCore.Mvc.ProducesAttribute), and [`[ProducesDefaultResponseType]`](xref:Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute) also support an optional string property called `Description` that can be used to describe the response. This is useful for explaining why or when clients can expect a specific response:
404+
405+
```csharp
406+
[HttpGet("/todos/{id}")]
407+
[ProducesResponseType<Todo>(StatusCodes.Status200OK,
408+
"application/json", Description = "Returns the requested Todo item.")]
409+
[ProducesResponseType(StatusCodes.Status404NotFound,
410+
Description = "Requested Todo item not found.")]
411+
[ProducesDefault(Description = "Undocumented status code.")]
412+
public async Task<ActionResult<Todo>> GetTodoItem(string id, Todo todo)
413+
```
414+
415+
:::moniker-end
416+
375417
When not specified by an attribute:
376-
* the status code for the response defaults to 200,
377-
* the schema for the response body of 2xx responses may be inferred from the return type of the action method, e.g. from `T` in <xref:Microsoft.AspNetCore.Mvc.ActionResult%601>, but otherwise is considered to be not specified,
378-
* the schema for the response body of 4xx responses is inferred to be a problem details object,
379-
* the schema for the response body of 3xx and 5xx responses is considered to be not specified,
380-
* the content-type for the response body can be inferred from the return type of the action method and the set of output formatters.
418+
419+
* The status code for the response defaults to 200.
420+
* The schema for the response body of 2xx responses may be inferred from the return type of the action method, e.g. from `T` in <xref:Microsoft.AspNetCore.Mvc.ActionResult%601>, but otherwise is considered to be not specified.
421+
* The schema for the response body of 4xx responses is inferred to be a problem details object.
422+
* The schema for the response body of 3xx and 5xx responses is considered to be not specified.
423+
* The content-type for the response body can be inferred from the return type of the action method and the set of output formatters.
381424

382425
By default, there are no compile-time checks to ensure that the response metadata specified with a [`[ProducesResponseType]` attribute](xref:Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute) is consistent with the actual behavior of the action method, which may return a different status code or response body type than specified by the metadata. To enable these checks, [enable Web API analyzers](xref:web-api/advanced/analyzers).
383426

@@ -412,11 +455,11 @@ The following sample demonstrates the different strategies for excluding a given
412455

413456
```csharp
414457
app.MapGet("/extension-method", () => "Hello world!")
415-
.ExcludeFromDescription();
458+
.ExcludeFromDescription();
416459

417460
app.MapGet("/attributes",
418-
[ExcludeFromDescription]
419-
() => "Hello world!");
461+
[ExcludeFromDescription]
462+
() => "Hello world!");
420463
```
421464

422465
#### [Controllers](#tab/controllers)
@@ -426,12 +469,13 @@ In controller-based apps, the [`[ApiExplorerSettings]`](xref:Microsoft.AspNetCor
426469
The following example demonstrates how to exclude an endpoint from the generated OpenAPI document:
427470

428471
```csharp
429-
[HttpGet("/private")]
430-
[ApiExplorerSettings(IgnoreApi = true)]
431-
public IActionResult PrivateEndpoint() {
432-
return Ok("This is a private endpoint");
433-
}
472+
[HttpGet("/private")]
473+
[ApiExplorerSettings(IgnoreApi = true)]
474+
public IActionResult PrivateEndpoint() {
475+
return Ok("This is a private endpoint");
476+
}
434477
```
478+
435479
---
436480

437481
## Include OpenAPI metadata for data types
@@ -484,12 +528,12 @@ The following table summarizes attributes from the `System.ComponentModel` names
484528

485529
| Attribute | Description |
486530
| ---------------------------- | ----------- |
487-
| [`[Description]`](xref:System.ComponentModel.DescriptionAttribute) | Sets the `description` of a property in the schema. |
488-
| [`[Required]`](xref:System.ComponentModel.DataAnnotations.RequiredAttribute) | Marks a property as `required` in the schema. |
489-
| [`[DefaultValue]`](xref:System.ComponentModel.DefaultValueAttribute) | Sets the `default` value of a property in the schema. |
490-
| [`[Range]`](xref:System.ComponentModel.DataAnnotations.RangeAttribute) | Sets the `minimum` and `maximum` value of an integer or number. |
491-
| [`[MinLength]`](xref:System.ComponentModel.DataAnnotations.MinLengthAttribute) | Sets the `minLength` of a string or `minItems` of an array. |
492-
| [`[MaxLength]`](xref:System.ComponentModel.DataAnnotations.MaxLengthAttribute) | Sets the `maxLength` of a string or `maxItems` of an array. |
531+
| [`[Description]`](xref:System.ComponentModel.DescriptionAttribute) | Sets the `description` of a property in the schema. |
532+
| [`[Required]`](xref:System.ComponentModel.DataAnnotations.RequiredAttribute) | Marks a property as `required` in the schema. |
533+
| [`[DefaultValue]`](xref:System.ComponentModel.DefaultValueAttribute) | Sets the `default` value of a property in the schema. |
534+
| [`[Range]`](xref:System.ComponentModel.DataAnnotations.RangeAttribute) | Sets the `minimum` and `maximum` value of an integer or number. |
535+
| [`[MinLength]`](xref:System.ComponentModel.DataAnnotations.MinLengthAttribute) | Sets the `minLength` of a string or `minItems` of an array. |
536+
| [`[MaxLength]`](xref:System.ComponentModel.DataAnnotations.MaxLengthAttribute) | Sets the `maxLength` of a string or `maxItems` of an array. |
493537
| [`[RegularExpression]`](xref:System.ComponentModel.DataAnnotations.RegularExpressionAttribute) | Sets the `pattern` of a string. |
494538

495539
Note that in controller-based apps, these attributes add filters to the operation to validate that any incoming data satisfies the constraints. In Minimal APIs, these attributes set the metadata in the generated schema but validation must be performed explicitly via an endpoint filter, in the route handler's logic, or via a third-party package.

0 commit comments

Comments
 (0)