Edit

Share via


Include OpenAPI metadata in an ASP.NET Core app

Include OpenAPI metadata for endpoints

ASP.NET collects metadata from the web app's endpoints and uses it to generate an OpenAPI document. In controller-based apps, metadata is collected from attributes like [EndpointDescription], [HttpPost], and [Produces]. In minimal APIs, metadata can be collected from attributes, but may also be set by using extension methods and other strategies, such as returning TypedResults from route handlers. The following table provides an overview of the metadata collected and the strategies for setting it.

Metadata Attribute Extension method Other strategies
summary [EndpointSummary] WithSummary
description [EndpointDescription] WithDescription
tags [Tags] WithTags
operationId [EndpointName] WithName
parameters [FromQuery], [FromRoute], [FromHeader], [FromForm]
parameter description [Description]
requestBody [FromBody] Accepts
responses [Produces] Produces, ProducesProblem TypedResults
Excluding endpoints [ExcludeFromDescription], [ApiExplorerSettings] ExcludeFromDescription

ASP.NET Core can also collect metadata from XML doc comments. For more information, see ASP.NET Core OpenAPI XML documentation comment support in ASP.NET Core for more details.

The following sections demonstrate how to include metadata in an app to customize the generated OpenAPI document.

Summary and description

The endpoint summary and description can be set using the [EndpointSummary] and [EndpointDescription] attributes, or in minimal APIs, using the WithSummary and WithDescription extension methods.

The following sample demonstrates how to set summaries and descriptions.

[EndpointSummary("This is a summary.")]
[EndpointDescription("This is a description.")]
[HttpGet("attributes")]
public IResult Attributes()
{
    return Results.Ok("Hello world!");
}

tags

OpenAPI supports specifying tags on each endpoint as a form of categorization.

In controller-based apps, the controller name is automatically added as a tag on each of its endpoints, but this can be overridden using the [Tags] attribute.

The following sample demonstrates how to set tags.

[Tags(["todos", "projects"])]
[HttpGet("attributes")]
public IResult Attributes()
{
    return Results.Ok("Hello world!");
}

operationId

OpenAPI supports an operationId on each endpoint as a unique identifier or name for the operation.

In controller-based apps, the operationId can be set using the [EndpointName] attribute.

The following sample demonstrates how to set the operationId.

[EndpointName("FromAttributes")]
[HttpGet("attributes")]
public IResult Attributes()
{
    return Results.Ok("Hello world!");
}

parameters

OpenAPI supports annotating path, query string, header, and cookie parameters that are consumed by an API.

The framework infers the types for request parameters automatically based on the signature of the route handler.

The [Description] attribute can be used to provide a description for a parameter.

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

[HttpGet("attributes")]
public IResult Attributes([Description("This is a description.")] string name)
{
    return Results.Ok("Hello world!");
}

Describe the request body

The requestBody field in OpenAPI describes the body of a request that an API client can send to the server, including the content type(s) supported and the schema for the body content.

When the endpoint handler method accepts parameters that are bound from the request body, ASP.NET Core generates a corresponding requestBody for the operation in the OpenAPI document. Metadata for the request body can also be specified using attributes or extension methods. Additional metadata can be set with a document transformer or operation transformer.

If the endpoint doesn't define any parameters bound to the request body, but instead consumes the request body from the HttpContext directly, ASP.NET Core provides mechanisms to specify request body metadata. This is a common scenario for endpoints that process the request body as a stream.

Some request body metadata can be determined from the FromBody or FromForm parameters of the route handler method.

A description for the request body can be set with a [Description] attribute on the parameter with FromBody or FromForm.

If the FromBody parameter is non-nullable and EmptyBodyBehavior is not set to Allow in the FromBody attribute, the request body is required and the required field of the requestBody is set to true in the generated OpenAPI document. Form bodies are always required and have required set to true.

Use a document transformer or an operation transformer to set the example, examples, or encoding fields, or to add specification extensions for the request body in the generated OpenAPI document.

Other mechanisms for setting request body metadata depend on the type of app being developed and are described in the following sections.

In controller-based apps, the content type(s) for the request body in the generated OpenAPI document are determined from the type of the parameter that is bound to the request body, the InputFormatter types configured in the application, or by a [Consumes] attribute on the route handler method.

ASP.NET Core uses an InputFormatter to deserialize a FromBody request body. InputFormatters are configured in the MvcOptions passed to the AddControllers extension method for the app's service collection. Each input formatter declares the content types it can handle, in its SupportedMediaTypes property, and the type(s) of body content it can handle, with its CanReadType method.

ASP.NET Core MVC includes built-in input formatters for JSON and XML, though only the JSON input formatter is enabled by default. The built-in JSON input formatter supports the application/json, text/json, and application/*+json content types, and the built-in XML input formatter supports the application/xml, text/xml, and application/*+xml content types.

By default, the content type of a FromBody request body may be any content type accepted by an InputFormatter for the FromBody parameter type. For a request body with FromForm parameter(s) the default content types are multipart/form-data or application/x-www-form-urlencoded.These content types will be included in the generated OpenAPI document if the [Consumes] attribute is not specified on the route handler method.

The content type(s) accepted by a route handler can be restricted using a filter on the endpoint (action scope). The [Consumes] attribute adds an action scope filter to the endpoint that restricts the content types that a route handler will accept. In this case, the requestBody in the generated OpenAPI document will include only the content type(s) specified in the [Consumes] attribute.

A [Consumes] attribute can't add support for a content type that doesn't have an associated input formatter, and the generated OpenAPI document doesn't include any content types that don't have an associated input formatter.

For content types other than JSON or XML, you need to create a custom input formatter. For more information and examples, see Custom formatters in ASP.NET Core Web API.

If the route handler doesn't have a FromBody or FromForm parameter, the route handler might read the request body directly from the Request.Body stream and might use the [Consumes] attribute to restrict the content types allowed, but no requestBody is generated in the OpenAPI document.

Describe response types

OpenAPI supports providing a description of the responses returned from an API. ASP.NET Core provides several strategies for setting the response metadata of an endpoint. Response metadata that can be set includes the status code, the type of the response body, and content type(s) of a response. Responses in OpenAPI may have additional metadata, such as description, headers, links, and examples. This additional metadata can be set with a document transformer or operation transformer.

The specific mechanisms for setting response metadata depend on the type of app being developed.

In controller-based apps, ASP.NET Core can extract the response metadata from the action method signature, attributes, and conventions.

Only one [Produces] or ProducesAttribute<T> attributes may be applied to an action method, but multiple [ProducesResponseType] or ProducesResponseTypeAttribute<T> attributes with different status codes may be applied to a single action method.

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.

[ProducesResponseType], [Produces], and [ProducesDefaultResponseType] 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:

[HttpGet("/todos/{id}")]
[ProducesResponseType<Todo>(StatusCodes.Status200OK, 
    "application/json", Description = "Returns the requested Todo item.")]
[ProducesResponseType(StatusCodes.Status404NotFound, 
    Description = "Requested Todo item not found.")]
[ProducesDefault(Description = "Undocumented status code.")]
public async Task<ActionResult<Todo>> GetTodoItem(string id, Todo todo)

When not specified by an attribute:

  • The status code for the response defaults to 200.
  • 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 ActionResult<TValue>, but otherwise is considered to be not specified.
  • The schema for the response body of 4xx responses is inferred to be a problem details object.
  • The schema for the response body of 3xx and 5xx responses is considered to be not specified.
  • The content-type for the response body can be inferred from the return type of the action method and the set of output formatters.

By default, there are no compile-time checks to ensure that the response metadata specified with a [ProducesResponseType] attribute 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.

In controller-based apps, ASP.NET responds with a ProblemDetails response type when model validation fails or when the action method returns a result with a 4xx or 5xx HTTP status code. Validation errors typically use the 400 status code, so you can use the [ProducesResponseType] attribute to specify the error response for an action, as shown in the following example:

[HttpPut("/todos/{id}")]
[ProducesResponseType<Todo>(StatusCodes.Status200OK, "application/json")]
[ProducesResponseType<Todo>(StatusCodes.Status201Created, "application/json")]
[ProducesResponseType<ProblemDetails>(StatusCodes.Status400BadRequest, "application/problem+json")]
public async Task<ActionResult<Todo>> CreateOrReplaceTodo(string id, Todo todo)

This example also illustrates how to define multiple response types for an action method, including the content type of the response body.

Exclude endpoints from the generated document

By default, all endpoints that are defined in an app are documented in the generated OpenAPI file, but endpoints can be excluded from the document using attributes or extension methods.

The mechanism for specifying an endpoint that should be excluded depends on the type of app being developed.

In controller-based apps, the [ApiExplorerSettings] attribute can be used to exclude an endpoint or all endpoints in a controller class from the OpenAPI document.

The following example demonstrates how to exclude an endpoint from the generated OpenAPI document:

[HttpGet("/private")]
[ApiExplorerSettings(IgnoreApi = true)]
public IActionResult PrivateEndpoint() {
    return Ok("This is a private endpoint");
}

Include OpenAPI metadata for data types

C# classes or records used in request or response bodies are represented as schemas in the generated OpenAPI document. By default, only public properties are represented in the schema, but there are JsonSerializerOptions to also create schema properties for fields.

When the PropertyNamingPolicy is set to camel-case (this is the default in ASP.NET web applications), property names in a schema are the camel-case form of the class or record property name. The [JsonPropertyName] can be used on an individual property to specify the name of the property in the schema.

type and format

Numeric types

The JSON Schema library maps standard C# numeric types to OpenAPI type and format based on the NumberHandling property of the JsonSerializerOptions used in the app. In ASP.NET Core Web API apps, the default value of this property is JsonNumberHandling.AllowReadingFromString.

When the NumberHandling property is set to JsonNumberHandling.AllowReadingFromString, the numeric types are mapped as follows:

C# Type OpenAPI type OpenAPI format Other assertions
int [integer,string] int32 pattern <digits>
long [integer,string] int64 pattern <digits>
short [integer,string] int16 pattern <digits>
byte [integer,string] uint8 pattern <digits>
float [number,string] float pattern <digits with decimal >
double [number,string] double pattern <digits with decimal >
decimal [number,string] double pattern <digits with decimal >

If the app is configured to produce OpenAPI 3.0 or OpenAPI v2 documents, where the type field cannot have an array value, the type field is dropped.

When the NumberHandling property is set to JsonNumberHandling.Strict, the numeric types are mapped as follows:

C# Type OpenAPI type OpenAPI format
int integer int32
long integer int64
short integer int16
byte integer uint8
float number float
double number double
decimal number double

String types

The following table shows how C# types map to string type properties in the generated OpenAPI document:

C# Type OpenAPI type OpenAPI format Other assertions
string string
char string char minLength: 1, maxLength: 1
byte[] string byte
DateTimeOffset string date-time
DateOnly string date
TimeOnly string time
Uri string uri
Guid string uuid

Other types

Other C# types are represented in the generated OpenAPI document as shown in the following table:

C# Type OpenAPI type OpenAPI format
bool boolean
object omitted
dynamic omitted

Use attributes to add metadata

ASP.NET uses metadata from attributes on class or record properties to set metadata on the corresponding properties of the generated schema.

The following table summarizes attributes from the System.ComponentModel namespace that provide metadata for the generated schema:

Attribute Description
[Description] Sets the description of a property in the schema.
[Required] Marks a property as required in the schema.
[DefaultValue] Sets the default value of a property in the schema.
[Range] Sets the minimum and maximum value of an integer or number.
[MinLength] Sets the minLength of a string or minItems of an array.
[MaxLength] Sets the maxLength of a string or maxItems of an array.
[RegularExpression] 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.

Attributes can also be placed on parameters in the parameter list of a record definition but must include the property modifier. For example:

public record Todo(
    [property: Required]
    [property: Description("The unique identifier for the todo")]
    int Id,
    [property: Description("The title of the todo")]
    [property: MaxLength(120)]
    string Title,
    [property: Description("Whether the todo has been completed")]
    bool Completed
) {}

Other sources of metadata for generated schemas

required

In a class, struct, or record, properties with the [Required] attribute or required modifier are always required in the corresponding schema.

Other properties may also be required based on the constructors (implicit and explicit) for the class, struct, or record.

  • For a class or record class with a single public constructor, any property with the same type and name (case-insensitive match) as a parameter to the constructor is required in the corresponding schema.
  • For a class or record class with multiple public constructors, no other properties are required.
  • For a struct or record struct, no other properties are required since C# always defines an implicit parameterless constructor for a struct.

enum

Enum types in C# are integer-based, but can be represented as strings in JSON with a [JsonConverter] and a JsonStringEnumConverter. When an enum type is represented as a string in JSON, the generated schema will have an enum property with the string values of the enum.

The following example demonstrates how to use a JsonStringEnumConverter to represent an enum as a string in JSON:

[JsonConverter(typeof(JsonStringEnumConverter<DayOfTheWeekAsString>))]
public enum DayOfTheWeekAsString
{
    Sunday,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
}

A special case is when an enum type has the [Flags] attribute, which indicates that the enum can be treated as a bit field; that is, a set of flags. A flags enum with a [JsonConverterAttribute] is defined as type: string in the generated schema with no enum property, since the value could be any combination of the enum values. For example, the following enum:

[Flags, JsonConverter(typeof(JsonStringEnumConverter<PizzaToppings>))]
public enum PizzaToppings { Pepperoni = 1, Sausage = 2, Mushrooms = 4, Anchovies = 8 }

could have values such as "Pepperoni, Sausage" or "Sausage, Mushrooms, Anchovies".

An enum type without a [JsonConverter] will be defined as type: integer in the generated schema.

Note: The [AllowedValues] attribute does not set the enum values of a property.

nullable

Properties defined as a nullable value or reference type appear in the generated schema with a type keyword whose value is an array that includes null as one of the types. This is consistent with the default behavior of the System.Text.Json deserializer, which accepts null as a valid value for a nullable property.

For example, a C# property defined as string? is represented in the generated schema as:

  "nullableString": {
    "description": "A property defined as string?",
    "type": [
      "null",
      "string"
    ]
  },

If the app is configured to produce OpenAPI v3.0 or OpenAPI v2 documents, nullable value or reference types have nullable: true in the generated schema because these OpenAPI versions do not allow the type field to be an array.

additionalProperties

Schemas are generated without an additionalProperties assertion by default, which implies the default of true. This is consistent with the default behavior of the System.Text.Json deserializer, which silently ignores additional properties in a JSON object.

If the additional properties of a schema should only have values of a specific type, define the property or class as a Dictionary<string, type>. The key type for the dictionary must be string. This generates a schema with additionalProperties specifying the schema for "type" as the required value types.

Polymorphic types

Use the [JsonPolymorphic] and [JsonDerivedType] attributes on a parent class to specify the discriminator field and subtypes for a polymorphic type.

The [JsonDerivedType] adds the discriminator field to the schema for each subclass, with an enum specifying the specific discriminator value for the subclass. This attribute also modifies the constructor of each derived class to set the discriminator value.

An abstract class with a [JsonPolymorphic] attribute has a discriminator field in the schema, but a concrete class with a [JsonPolymorphic] attribute doesn't have a discriminator field. OpenAPI requires that the discriminator property be a required property in the schema, but since the discriminator property isn't defined in the concrete base class, the schema cannot include a discriminator field.

Add metadata with a schema transformer

A schema transformer can be used to override any default metadata or add additional metadata, such as example values, to the generated schema. See Use schema transformers for more information.

Additional resources

ASP.NET collects metadata from the web app's endpoints and uses it to generate an OpenAPI document. In controller-based apps, metadata is collected from attributes like [EndpointDescription], [HttpPost], and [Produces]. In minimal APIs, metadata can be collected from attributes, but may also be set by using extension methods and other strategies, such as returning TypedResults from route handlers. The following table provides an overview of the metadata collected and the strategies for setting it.

Metadata Attribute Extension method Other strategies
summary [EndpointSummary] WithSummary
description [EndpointDescription] WithDescription
tags [Tags] WithTags
operationId [EndpointName] WithName
parameters [FromQuery], [FromRoute], [FromHeader], [FromForm]
parameter description [Description]
requestBody [FromBody] Accepts
responses [Produces] Produces, ProducesProblem TypedResults
Excluding endpoints [ExcludeFromDescription], [ApiExplorerSettings] ExcludeFromDescription

ASP.NET Core does not collect metadata from XML doc comments.

The following sections demonstrate how to include metadata in an app to customize the generated OpenAPI document.

Summary and description

The endpoint summary and description can be set using the [EndpointSummary] and [EndpointDescription] attributes, or in minimal APIs, using the WithSummary and WithDescription extension methods.

The following sample demonstrates how to set summaries and descriptions.

[EndpointSummary("This is a summary.")]
[EndpointDescription("This is a description.")]
[HttpGet("attributes")]
public IResult Attributes()
{
    return Results.Ok("Hello world!");
}

tags

OpenAPI supports specifying tags on each endpoint as a form of categorization.

In controller-based apps, the controller name is automatically added as a tag on each of its endpoints, but this can be overridden using the [Tags] attribute.

The following sample demonstrates how to set tags.

[Tags(["todos", "projects"])]
[HttpGet("attributes")]
public IResult Attributes()
{
    return Results.Ok("Hello world!");
}

operationId

OpenAPI supports an operationId on each endpoint as a unique identifier or name for the operation.

In controller-based apps, the operationId can be set using the [EndpointName] attribute.

The following sample demonstrates how to set the operationId.

[EndpointName("FromAttributes")]
[HttpGet("attributes")]
public IResult Attributes()
{
    return Results.Ok("Hello world!");
}

parameters

OpenAPI supports annotating path, query string, header, and cookie parameters that are consumed by an API.

The framework infers the types for request parameters automatically based on the signature of the route handler.

The [Description] attribute can be used to provide a description for a parameter.

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

[HttpGet("attributes")]
public IResult Attributes([Description("This is a description.")] string name)
{
    return Results.Ok("Hello world!");
}

Describe the request body

The requestBody field in OpenAPI describes the body of a request that an API client can send to the server, including the content type(s) supported and the schema for the body content.

When the endpoint handler method accepts parameters that are bound from the request body, ASP.NET Core generates a corresponding requestBody for the operation in the OpenAPI document. Metadata for the request body can also be specified using attributes or extension methods. Additional metadata can be set with a document transformer or operation transformer.

If the endpoint doesn't define any parameters bound to the request body, but instead consumes the request body from the HttpContext directly, ASP.NET Core provides mechanisms to specify request body metadata. This is a common scenario for endpoints that process the request body as a stream.

Some request body metadata can be determined from the FromBody or FromForm parameters of the route handler method.

A description for the request body can be set with a [Description] attribute on the parameter with FromBody or FromForm.

If the FromBody parameter is non-nullable and EmptyBodyBehavior is not set to Allow in the FromBody attribute, the request body is required and the required field of the requestBody is set to true in the generated OpenAPI document. Form bodies are always required and have required set to true.

Use a document transformer or an operation transformer to set the example, examples, or encoding fields, or to add specification extensions for the request body in the generated OpenAPI document.

Other mechanisms for setting request body metadata depend on the type of app being developed and are described in the following sections.

In controller-based apps, the content type(s) for the request body in the generated OpenAPI document are determined from the type of the parameter that is bound to the request body, the InputFormatter types configured in the application, or by a [Consumes] attribute on the route handler method.

ASP.NET Core uses an InputFormatter to deserialize a FromBody request body. InputFormatters are configured in the MvcOptions passed to the AddControllers extension method for the app's service collection. Each input formatter declares the content types it can handle, in its SupportedMediaTypes property, and the type(s) of body content it can handle, with its CanReadType method.

ASP.NET Core MVC includes built-in input formatters for JSON and XML, though only the JSON input formatter is enabled by default. The built-in JSON input formatter supports the application/json, text/json, and application/*+json content types, and the built-in XML input formatter supports the application/xml, text/xml, and application/*+xml content types.

By default, the content type of a FromBody request body may be any content type accepted by an InputFormatter for the FromBody parameter type. For a request body with FromForm parameter(s) the default content types are multipart/form-data or application/x-www-form-urlencoded.These content types will be included in the generated OpenAPI document if the [Consumes] attribute is not specified on the route handler method.

The content type(s) accepted by a route handler can be restricted using a filter on the endpoint (action scope). The [Consumes] attribute adds an action scope filter to the endpoint that restricts the content types that a route handler will accept. In this case, the requestBody in the generated OpenAPI document will include only the content type(s) specified in the [Consumes] attribute.

A [Consumes] attribute can't add support for a content type that doesn't have an associated input formatter, and the generated OpenAPI document doesn't include any content types that don't have an associated input formatter.

For content types other than JSON or XML, you need to create a custom input formatter. For more information and examples, see Custom formatters in ASP.NET Core Web API.

If the route handler doesn't have a FromBody or FromForm parameter, the route handler might read the request body directly from the Request.Body stream and might use the [Consumes] attribute to restrict the content types allowed, but no requestBody is generated in the OpenAPI document.

Describe response types

OpenAPI supports providing a description of the responses returned from an API. ASP.NET Core provides several strategies for setting the response metadata of an endpoint. Response metadata that can be set includes the status code, the type of the response body, and content type(s) of a response. Responses in OpenAPI may have additional metadata, such as description, headers, links, and examples. This additional metadata can be set with a document transformer or operation transformer.

The specific mechanisms for setting response metadata depend on the type of app being developed.

In controller-based apps, ASP.NET Core can extract the response metadata from the action method signature, attributes, and conventions.

Only one [Produces] or ProducesAttribute<T> attributes may be applied to an action method, but multiple [ProducesResponseType] or ProducesResponseTypeAttribute<T> attributes with different status codes may be applied to a single action method.

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.

When not specified by an attribute:

  • The status code for the response defaults to 200.
  • 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 ActionResult<TValue>, but otherwise is considered to be not specified.
  • The schema for the response body of 4xx responses is inferred to be a problem details object.
  • The schema for the response body of 3xx and 5xx responses is considered to be not specified.
  • The content-type for the response body can be inferred from the return type of the action method and the set of output formatters.

By default, there are no compile-time checks to ensure that the response metadata specified with a [ProducesResponseType] attribute 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.

In controller-based apps, ASP.NET responds with a ProblemDetails response type when model validation fails or when the action method returns a result with a 4xx or 5xx HTTP status code. Validation errors typically use the 400 status code, so you can use the [ProducesResponseType] attribute to specify the error response for an action, as shown in the following example:

[HttpPut("/todos/{id}")]
[ProducesResponseType<Todo>(StatusCodes.Status200OK, "application/json")]
[ProducesResponseType<Todo>(StatusCodes.Status201Created, "application/json")]
[ProducesResponseType<ProblemDetails>(StatusCodes.Status400BadRequest, "application/problem+json")]
public async Task<ActionResult<Todo>> CreateOrReplaceTodo(string id, Todo todo)

This example also illustrates how to define multiple response types for an action method, including the content type of the response body.

Exclude endpoints from the generated document

By default, all endpoints that are defined in an app are documented in the generated OpenAPI file, but endpoints can be excluded from the document using attributes or extension methods.

The mechanism for specifying an endpoint that should be excluded depends on the type of app being developed.

In controller-based apps, the [ApiExplorerSettings] attribute can be used to exclude an endpoint or all endpoints in a controller class from the OpenAPI document.

The following example demonstrates how to exclude an endpoint from the generated OpenAPI document:

[HttpGet("/private")]
[ApiExplorerSettings(IgnoreApi = true)]
public IActionResult PrivateEndpoint() {
    return Ok("This is a private endpoint");
}

Include OpenAPI metadata for data types

C# classes or records used in request or response bodies are represented as schemas in the generated OpenAPI document. By default, only public properties are represented in the schema, but there are JsonSerializerOptions to also create schema properties for fields.

When the PropertyNamingPolicy is set to camel-case (this is the default in ASP.NET web applications), property names in a schema are the camel-case form of the class or record property name. The [JsonPropertyName] can be used on an individual property to specify the name of the property in the schema.

type and format

The JSON Schema library maps standard C# types to OpenAPI type and format as follows:

C# Type OpenAPI type OpenAPI format
int integer int32
long integer int64
short integer int16
byte integer uint8
float number float
double number double
decimal number double
bool boolean
string string
char string char
byte[] string byte
DateTimeOffset string date-time
DateOnly string date
TimeOnly string time
Uri string uri
Guid string uuid
object omitted
dynamic omitted

Note that object and dynamic types have no type defined in the OpenAPI because these can contain data of any type, including primitive types like int or string.

The type and format can also be set with a Schema Transformer. For example, you may want the format of decimal types to be decimal instead of double.

Use attributes to add metadata

ASP.NET uses metadata from attributes on class or record properties to set metadata on the corresponding properties of the generated schema.

The following table summarizes attributes from the System.ComponentModel namespace that provide metadata for the generated schema:

Attribute Description
[Description] Sets the description of a property in the schema.
[Required] Marks a property as required in the schema.
[DefaultValue] Sets the default value of a property in the schema.
[Range] Sets the minimum and maximum value of an integer or number.
[MinLength] Sets the minLength of a string or minItems of an array.
[MaxLength] Sets the maxLength of a string or maxItems of an array.
[RegularExpression] 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.

Attributes can also be placed on parameters in the parameter list of a record definition but must include the property modifier. For example:

public record Todo(
    [property: Required]
    [property: Description("The unique identifier for the todo")]
    int Id,
    [property: Description("The title of the todo")]
    [property: MaxLength(120)]
    string Title,
    [property: Description("Whether the todo has been completed")]
    bool Completed
) {}

Other sources of metadata for generated schemas

required

In a class, struct, or record, properties with the [Required] attribute or required modifier are always required in the corresponding schema.

Other properties may also be required based on the constructors (implicit and explicit) for the class, struct, or record.

  • For a class or record class with a single public constructor, any property with the same type and name (case-insensitive match) as a parameter to the constructor is required in the corresponding schema.
  • For a class or record class with multiple public constructors, no other properties are required.
  • For a struct or record struct, no other properties are required since C# always defines an implicit parameterless constructor for a struct.

enum

Enum types in C# are integer-based, but can be represented as strings in JSON with a [JsonConverter] and a JsonStringEnumConverter. When an enum type is represented as a string in JSON, the generated schema will have an enum property with the string values of the enum.

The following example demonstrates how to use a JsonStringEnumConverter to represent an enum as a string in JSON:

[JsonConverter(typeof(JsonStringEnumConverter<DayOfTheWeekAsString>))]
public enum DayOfTheWeekAsString
{
    Sunday,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
}

A special case is when an enum type has the [Flags] attribute, which indicates that the enum can be treated as a bit field; that is, a set of flags. A flags enum with a [JsonConverterAttribute] is defined as type: string in the generated schema with no enum property, since the value could be any combination of the enum values. For example, the following enum:

[Flags, JsonConverter(typeof(JsonStringEnumConverter<PizzaToppings>))]
public enum PizzaToppings { Pepperoni = 1, Sausage = 2, Mushrooms = 4, Anchovies = 8 }

could have values such as "Pepperoni, Sausage" or "Sausage, Mushrooms, Anchovies".

An enum type without a [JsonConverter] will be defined as type: integer in the generated schema.

Note: The [AllowedValues] attribute does not set the enum values of a property.

nullable

Properties defined as a nullable value or reference type have nullable: true in the generated schema. This is consistent with the default behavior of the System.Text.Json deserializer, which accepts null as a valid value for a nullable property.

additionalProperties

Schemas are generated without an additionalProperties assertion by default, which implies the default of true. This is consistent with the default behavior of the System.Text.Json deserializer, which silently ignores additional properties in a JSON object.

If the additional properties of a schema should only have values of a specific type, define the property or class as a Dictionary<string, type>. The key type for the dictionary must be string. This generates a schema with additionalProperties specifying the schema for "type" as the required value types.

Polymorphic types

Use the [JsonPolymorphic] and [JsonDerivedType] attributes on a parent class to specify the discriminator field and subtypes for a polymorphic type.

The [JsonDerivedType] adds the discriminator field to the schema for each subclass, with an enum specifying the specific discriminator value for the subclass. This attribute also modifies the constructor of each derived class to set the discriminator value.

An abstract class with a [JsonPolymorphic] attribute has a discriminator field in the schema, but a concrete class with a [JsonPolymorphic] attribute doesn't have a discriminator field. OpenAPI requires that the discriminator property be a required property in the schema, but since the discriminator property isn't defined in the concrete base class, the schema cannot include a discriminator field.

Add metadata with a schema transformer

A schema transformer can be used to override any default metadata or add additional metadata, such as example values, to the generated schema. See Use schema transformers for more information.

Additional resources

:::moniker-end


Additional resources