From d27177170f3693a1e867240228f6ed682d0229b3 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Fri, 3 Jan 2025 20:52:32 -0600 Subject: [PATCH 1/6] MaxLength and MinLength set maxItems / minItems on arrays --- aspnetcore/fundamentals/openapi/include-metadata.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aspnetcore/fundamentals/openapi/include-metadata.md b/aspnetcore/fundamentals/openapi/include-metadata.md index 185d7f4f3911..faa45f434822 100644 --- a/aspnetcore/fundamentals/openapi/include-metadata.md +++ b/aspnetcore/fundamentals/openapi/include-metadata.md @@ -489,8 +489,8 @@ The following table summarizes attributes from the `System.ComponentModel` names | [`[Required]`](xref:System.ComponentModel.DataAnnotations.RequiredAttribute) | Marks a property as `required` in the schema. | | [`[DefaultValue]`](xref:System.ComponentModel.DefaultValueAttribute) | Sets the `default` value of a property in the schema. | | [`[Range]`](xref:System.ComponentModel.DataAnnotations.RangeAttribute) | Sets the `minimum` and `maximum` value of an integer or number. | -| [`[MinLength]`](xref:System.ComponentModel.DataAnnotations.MinLengthAttribute) | Sets the `minLength` of a string. | -| [`[MaxLength]`](xref:System.ComponentModel.DataAnnotations.MaxLengthAttribute) | Sets the `maxLength` of a string. | +| [`[MinLength]`](xref:System.ComponentModel.DataAnnotations.MinLengthAttribute) | Sets the `minLength` of a string or `minItems` of an array. | +| [`[MaxLength]`](xref:System.ComponentModel.DataAnnotations.MaxLengthAttribute) | Sets the `maxLength` of a string or `maxItems` of an array. | | [`[RegularExpression]`](xref:System.ComponentModel.DataAnnotations.RegularExpressionAttribute) | Sets the `pattern` of a string. | 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. From 2ddf75eeb28a5a376bbfa8270e5b9008bbe3818a Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Fri, 3 Jan 2025 20:53:07 -0600 Subject: [PATCH 2/6] Better TOC in the Customization page --- aspnetcore/fundamentals/openapi/customize-openapi.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/aspnetcore/fundamentals/openapi/customize-openapi.md b/aspnetcore/fundamentals/openapi/customize-openapi.md index 1fc84999e7b8..89722ce894a5 100644 --- a/aspnetcore/fundamentals/openapi/customize-openapi.md +++ b/aspnetcore/fundamentals/openapi/customize-openapi.md @@ -14,10 +14,6 @@ uid: fundamentals/openapi/customize-openapi ## OpenAPI document transformers -This section demonstrates how to customize OpenAPI documents with transformers. - -### Customize OpenAPI documents with transformers - Transformers provide an API for modifying the OpenAPI document with user-defined customizations. Transformers are useful for scenarios like: * Adding parameters to all operations in a document. @@ -50,7 +46,7 @@ Transformers execute in first-in first-out order based on registration. In the f [!code-csharp[](~/fundamentals/openapi/samples/9.x/WebMinOpenApi/Program.cs?name=snippet_transInOut&highlight=3-9)] -### Use document transformers +## Use document transformers Document transformers have access to a context object that includes: @@ -73,7 +69,7 @@ Document transformers are unique to the document instance they're associated wit [!code-csharp[](~/fundamentals/openapi/samples/9.x/WebMinOpenApi/Program.cs?name=snippet_multidoc_operationtransformer1)] -### Use operation transformers +## Use operation transformers Operations are unique combinations of HTTP paths and methods in an OpenAPI document. Operation transformers are helpful when a modification: @@ -90,7 +86,7 @@ For example, the following operation transformer adds `500` as a response status [!code-csharp[](~/fundamentals/openapi/samples/9.x/WebMinOpenApi/Program.cs?name=snippet_operationtransformer1)] -### Use schema transformers +## Use schema transformers Schemas are the data models that are used in request and response bodies in an OpenAPI document. Schema transformers are useful when a modification: From c165b8e9d6460863cf35c74571077ae33467b781 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Fri, 3 Jan 2025 21:12:29 -0600 Subject: [PATCH 3/6] Add the Customize schema reuse section --- .../fundamentals/openapi/customize-openapi.md | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/aspnetcore/fundamentals/openapi/customize-openapi.md b/aspnetcore/fundamentals/openapi/customize-openapi.md index 89722ce894a5..a9130149743e 100644 --- a/aspnetcore/fundamentals/openapi/customize-openapi.md +++ b/aspnetcore/fundamentals/openapi/customize-openapi.md @@ -103,6 +103,43 @@ For example, the following schema transformer sets the `format` of decimal types [!code-csharp[](~/fundamentals/openapi/samples/9.x/WebMinOpenApi/Program.cs?name=snippet_schematransformer1)] +## Customize schema reuse + +After all transformers have been applied, the framework makes a pass over the document to transfer certain schemas +to the `components.schemas` section, replacing them with `$ref` references to the transferred schema. +This reduces the size of the document and makes it easier to read. + +The details of this processing are a bit complicated, and might change in future versions of .NET, but in general: + +* Schemas for class/record/struct types are replaced with a `$ref` to a schema in `components.schemas` + if they appear more than once in the document. +* Schemas for primitive types and standard collections are left inline. +* Schemas for enum types are always replaced with a `$ref` to a schema in to components.schemas. + +Typically the name of the schema in `components.schemas` is the name of the class/record/struct type, +but in some circumstances a different name must be used. + +ASP.NET Core lets you customize which schemas are replaced with a `$ref` to a schema in `components.schemas` +using the property of . +This property is a delegate that takes a object and returns the name of the schema +in `components.schemas` that should be used for that type. +The framework provides a default implementation of this delegate, +that uses the name of the type, but you can replace it with your own implementation. + +As a simple example of this customization, you might choose to always inline enum schemas. +This is done by setting to a delegate +that returns null for enum types, and otherwise returns value from the default implementation. +The following code shows how to do this: + +```csharp +builder.Services.AddOpenApi(options => +{ + // Always inline enum schemas + options.CreateSchemaReferenceId = (type) => + type.Type.IsEnum ? null : OpenApiOptions.CreateDefaultSchemaReferenceId(type); +}); +``` + ## Additional resources * From 578990b1f77cadbd4acb66346355aa5c51c92442 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Sat, 11 Jan 2025 11:30:18 -0600 Subject: [PATCH 4/6] Add doc on generating multiple OpenAPI documents --- .../openapi/aspnetcore-openapi.md | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/aspnetcore/fundamentals/openapi/aspnetcore-openapi.md b/aspnetcore/fundamentals/openapi/aspnetcore-openapi.md index 68409d790e3a..0f3a06e44ecf 100644 --- a/aspnetcore/fundamentals/openapi/aspnetcore-openapi.md +++ b/aspnetcore/fundamentals/openapi/aspnetcore-openapi.md @@ -45,8 +45,8 @@ dotnet add package Microsoft.AspNetCore.OpenApi The following code: -* Adds OpenAPI services. -* Enables the endpoint for viewing the OpenAPI document in JSON format. +* Adds OpenAPI services using the extension method on the app builder's service collection. +* Maps an endpoint for viewing the OpenAPI document in JSON format with the extension method on the app. [!code-csharp[](~/fundamentals/openapi/samples/9.x/WebMinOpenApi/Program.cs?name=snippet_first&highlight=3,7)] @@ -64,7 +64,7 @@ Each OpenAPI document in an app has a unique name. The default document name tha builder.Services.AddOpenApi(); // Document name is v1 ``` -The document name can be modified by passing the name as a parameter to the `AddOpenApi` call. +The document name can be modified by passing the name as a parameter to the call. ```csharp builder.Services.AddOpenApi("internal"); // Document name is internal @@ -116,6 +116,36 @@ The OpenAPI document is regenerated every time a request to the OpenAPI endpoint [!code-csharp[](~/fundamentals/openapi/samples/9.x/WebMinOpenApi/Program.cs?name=snippet_mapopenapiwithcaching)] +## Generate multiple OpenAPI documents + +In some scenarios, it's helpful to generate multiple OpenAPI documents with different content from a single ASP.NET Core API app. These scenarios include: + +* Generating OpenAPI documentation for different audiences, such as public and internal APIs. +* Generating OpenAPI documentation for different versions of an API. +* Generating OpenAPI documentation for different parts of an application, such as a frontend and backend API. + +To generate multiple OpenAPI documents, call the extension method once for each document, specifying a different document name in the first parameter each time. + +```csharp +builder.Services.AddOpenApi("v1"); +builder.Services.AddOpenApi("v2"); +``` + +Each invocation of can specify its own set of options, so that you can choose to use the same or different transformers / customizations for each OpenApi document. + +The framework uses the delegate method of to determine which endpoints to include in each document. + +For each document, the delegate method is called for each endpoint in the application, passing the object for the endpoint. The method should return a boolean value indicating whether the endpoint should be included in the document. The object contains information about the endpoint, such as the HTTP method, route, and response types, as well as metadata attached to the endpoint via attributes or extension methods. + +The default implementation of this delegate uses the field of , which is set on an endpoint using either the extension method or the attribute, to determine which endpoints to include in the document. + +```csharp + // Include endpoints without a group name or with a group name that matches the document name + ShouldInclude = (description) => description.GroupName == null || description.GroupName == DocumentName; +``` + +You can customize the delegate method to include or exclude endpoints based on any criteria you choose. + ## Generate OpenAPI documents at build-time In typical web applications, OpenAPI documents are generated at run-time and served via an HTTP request to the application server. From b584249349bb3a507a680c9a3a6a0d4a0f931dc1 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 16 Jan 2025 11:34:20 -0600 Subject: [PATCH 5/6] Apply suggestions from PR review Co-authored-by: Tom Dykstra --- aspnetcore/fundamentals/openapi/aspnetcore-openapi.md | 4 ++-- aspnetcore/fundamentals/openapi/customize-openapi.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/aspnetcore/fundamentals/openapi/aspnetcore-openapi.md b/aspnetcore/fundamentals/openapi/aspnetcore-openapi.md index 0f3a06e44ecf..30b30052984a 100644 --- a/aspnetcore/fundamentals/openapi/aspnetcore-openapi.md +++ b/aspnetcore/fundamentals/openapi/aspnetcore-openapi.md @@ -131,11 +131,11 @@ builder.Services.AddOpenApi("v1"); builder.Services.AddOpenApi("v2"); ``` -Each invocation of can specify its own set of options, so that you can choose to use the same or different transformers / customizations for each OpenApi document. +Each invocation of can specify its own set of options, so that you can choose to use the same or different customizations for each OpenAPI document. The framework uses the delegate method of to determine which endpoints to include in each document. -For each document, the delegate method is called for each endpoint in the application, passing the object for the endpoint. The method should return a boolean value indicating whether the endpoint should be included in the document. The object contains information about the endpoint, such as the HTTP method, route, and response types, as well as metadata attached to the endpoint via attributes or extension methods. +For each document, the delegate method is called for each endpoint in the application, passing the object for the endpoint. The method returns a boolean value indicating whether the endpoint should be included in the document. The object contains information about the endpoint, such as the HTTP method, route, and response types, as well as metadata attached to the endpoint via attributes or extension methods. The default implementation of this delegate uses the field of , which is set on an endpoint using either the extension method or the attribute, to determine which endpoints to include in the document. diff --git a/aspnetcore/fundamentals/openapi/customize-openapi.md b/aspnetcore/fundamentals/openapi/customize-openapi.md index a9130149743e..92952162b52b 100644 --- a/aspnetcore/fundamentals/openapi/customize-openapi.md +++ b/aspnetcore/fundamentals/openapi/customize-openapi.md @@ -109,12 +109,12 @@ After all transformers have been applied, the framework makes a pass over the do to the `components.schemas` section, replacing them with `$ref` references to the transferred schema. This reduces the size of the document and makes it easier to read. -The details of this processing are a bit complicated, and might change in future versions of .NET, but in general: +The details of this processing are complicated and might change in future versions of .NET, but in general: * Schemas for class/record/struct types are replaced with a `$ref` to a schema in `components.schemas` if they appear more than once in the document. * Schemas for primitive types and standard collections are left inline. -* Schemas for enum types are always replaced with a `$ref` to a schema in to components.schemas. +* Schemas for enum types are always replaced with a `$ref` to a schema in components.schemas. Typically the name of the schema in `components.schemas` is the name of the class/record/struct type, but in some circumstances a different name must be used. @@ -128,7 +128,7 @@ that uses the name of the type, but you can replace it with your own implementat As a simple example of this customization, you might choose to always inline enum schemas. This is done by setting to a delegate -that returns null for enum types, and otherwise returns value from the default implementation. +that returns null for enum types, and otherwise returns the value from the default implementation. The following code shows how to do this: ```csharp From 5e499771fe4736e7f1419a2c293727a79819a665 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 16 Jan 2025 11:46:43 -0600 Subject: [PATCH 6/6] Updates to address PR review comments --- aspnetcore/fundamentals/openapi/aspnetcore-openapi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/fundamentals/openapi/aspnetcore-openapi.md b/aspnetcore/fundamentals/openapi/aspnetcore-openapi.md index 30b30052984a..08efa3aa9eee 100644 --- a/aspnetcore/fundamentals/openapi/aspnetcore-openapi.md +++ b/aspnetcore/fundamentals/openapi/aspnetcore-openapi.md @@ -137,7 +137,7 @@ The framework uses the delegate method is called for each endpoint in the application, passing the object for the endpoint. The method returns a boolean value indicating whether the endpoint should be included in the document. The object contains information about the endpoint, such as the HTTP method, route, and response types, as well as metadata attached to the endpoint via attributes or extension methods. -The default implementation of this delegate uses the field of , which is set on an endpoint using either the extension method or the attribute, to determine which endpoints to include in the document. +The default implementation of this delegate uses the field of , which is set on an endpoint using either the extension method or the attribute, to determine which endpoints to include in the document. Any endpoint that has not been assigned a group name is included all OpenAPI documents. ```csharp // Include endpoints without a group name or with a group name that matches the document name