Compartilhar via


Gerar documentos OpenAPI

O pacote Microsoft.AspNetCore.OpenApi fornece suporte interno para a geração de documentos OpenAPI no ASP.NET Core. O pacote fornece os seguintes recursos:

  • Suporte para geração de documentos OpenAPI em tempo de execução e acesso a esses documentos por meio de um ponto de extremidade no aplicativo.
  • Suporte para APIs "transformadoras" que permitem modificar o documento gerado.
  • Suporte para gerar vários documentos OpenAPI a partir de um único aplicativo.
  • Aproveita o suporte ao esquema JSON fornecido por System.Text.Json.
  • É compatível com o AoT nativo.

Instalação do pacote

Instalar o pacote Microsoft.AspNetCore.OpenApi:

Execute o seguinte comando no Console do Gerenciador de Pacotes:

Install-Package Microsoft.AspNetCore.OpenApi -IncludePrerelease

Para adicionar suporte para a geração de documentos OpenAPI no momento da compilação, instale o pacote Microsoft.Extensions.ApiDescription.Server:

Execute o seguinte comando no Console do Gerenciador de Pacotes:

Install-Package Microsoft.Extensions.ApiDescription.Server -IncludePrerelease

Configurar a geração de documentos OpenAPI

O seguinte código:

  • Adiciona serviços OpenAPI.
  • Habilita o ponto de extremidade para exibir o documento OpenAPI no formato JSON.
var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi();

var app = builder.Build();

app.MapOpenApi();

app.MapGet("/", () => "Hello world!");

app.Run();

Inicie o aplicativo e navegue até https://localhost:<port>/openapi/v1.json para exibir o documento OpenAPI gerado.

Incluir metadados OpenAPI em um aplicativo Web ASP.NET

Inclusão de metadados OpenAPI para pontos de extremidade

O ASP.NET coleta metadados dos pontos de extremidade do aplicativo Web e os usa para gerar um documento OpenAPI. Em aplicativos baseados em controlador, os metadados são coletados de atributos como [EndpointDescription], [HttpPost] e [Produces]. Em APIs mínimas, os metadados podem ser coletados de atributos, mas também podem ser definidos usando métodos de extensão e outras estratégias, como retornar TypedResults de manipuladores de rotas. A tabela a seguir fornece uma visão geral dos metadados coletados e das estratégias para configurá-los.

Metadados Atributo Método de extensão Outras estratégias
summary [EndpointSummary] WithSummary
descrição [EndpointDescription] WithDescription
tags [Tags] WithTags
operationId [EndpointName] WithName
parâmetros [FromQuery], [FromRoute], [FromHeader], [FromForm]
Descrição do parâmetro [EndpointDescription]
requestBody [FromBody] Accepts
responses [Produces] Produces, ProducesProblem TypedResults
Exclusão de pontos de extremidade [ExcludeFromDescription], [ApiExplorerSettings] ExcludeFromDescription

O ASP.NET Core não coleta metadados de comentários de documentação XML.

As seções a seguir demonstram como incluir metadados em um aplicativo para personalizar o documento OpenAPI gerado.

Resumo e descrição

O resumo e a descrição do ponto de extremidade podem ser definidos usando os atributos [EndpointSummary] e [EndpointDescription] ou, em APIs mínimas, usando os métodos de extensão WithSummary e WithDescription.

A amostra a seguir demonstra as diferentes estratégias para configurar resumos e descrições.

Observe que os atributos são colocados no método delegado e não no método app.MapGet.

app.MapGet("/extension-methods", () => "Hello world!")
  .WithSummary("This is a summary.")
  .WithDescription("This is a description.");

app.MapGet("/attributes",
  [EndpointSummary("This is a summary.")]
  [EndpointDescription("This is a description.")]
  () => "Hello world!");

tags

A OpenAPI dá suporte à especificação de marcas em cada ponto de extremidade como uma forma de categorização.

Em APIs mínimas, as marcas podem ser definidas usando o atributo [Tags] ou o método de extensão WithTags.

O exemplo a seguir demonstra as diferentes estratégias para configurar marcas.

app.MapGet("/extension-methods", () => "Hello world!")
  .WithTags("todos", "projects");

app.MapGet("/attributes",
  [Tags("todos", "projects")]
  () => "Hello world!");

operationId

O OpenAPI oferece suporte a um operationId em cada ponto de extremidade como um identificador ou nome exclusivo para a operação.

Em APIs mínimas, o operationId pode ser definido usando o atributo [EndpointName] ou o método de extensão WithName.

O exemplo a seguir demonstra as diferentes estratégias para configurar o operationId.

app.MapGet("/extension-methods", () => "Hello world!")
  .WithName("FromExtensionMethods");

app.MapGet("/attributes",
  [EndpointName("FromAttributes")]
  () => "Hello world!");

parâmetros

O OpenAPI oferece suporte à anotação de caminho, string de consulta, cabeçalho e parâmetros cookie que são consumidos por uma API.

A estrutura infere os tipos de parâmetros de solicitação automaticamente com base na assinatura do manipulador de rotas.

O atributo [EndpointDescription] pode ser usado para fornecer uma descrição para um parâmetro.

O exemplo a seguir demonstra como definir uma descrição para um parâmetro.

app.MapGet("/attributes",
  ([Description("This is a description.")] string name) => "Hello world!");

Descreva o corpo da solicitação

O requestBody campo no OpenAPI descreve o corpo de uma solicitação que um cliente de API pode enviar ao servidor, incluindo os tipos de conteúdo suportados e o esquema para o conteúdo do corpo.

Quando o método do manipulador de endpoint aceita parâmetros vinculados ao corpo da solicitação, ASP.NET Core gera um correspondente requestBody para a operação no documento OpenAPI. Os metadados para o corpo da solicitação também podem ser especificados usando atributos ou métodos de extensão. Metadados adicionais podem ser definidos com um transformador de documento ou transformador de operação.

Se o endpoint não definir nenhum parâmetro vinculado ao corpo da solicitação, mas, em vez disso, consumir o corpo da solicitação diretamente, o HttpContext ASP.NET Core fornecerá mecanismos para especificar metadados do corpo da solicitação. Esse é um cenário comum para endpoints que processam o corpo da solicitação como um fluxo.

Alguns metadados do corpo da solicitação podem ser determinados a FromBody partir dos parâmetros ou FromForm do método do manipulador de rotas.

Uma descrição para o corpo da solicitação pode ser definida com um [Description] atributo no parâmetro com FromBody ou FromForm.

Se o FromBody parâmetro não for anulável e EmptyBodyBehavior não estiver definido como Allow no FromBody atributo, o corpo da solicitação será necessário e o required campo do requestBody será definido como true no documento OpenAPI gerado. Os corpos do formulário são sempre necessários e foram required definidos como true.

Use um transformador de documento ou um transformador de operação para definir os examplecampos , examples, ou encoding ou para adicionar extensões de especificação para o corpo da solicitação no documento OpenAPI gerado.

Outros mecanismos para definir metadados do corpo da solicitação dependem do tipo de aplicativo que está sendo desenvolvido e são descritos nas seções a seguir.

Os tipos de conteúdo para o corpo da solicitação no documento OpenAPI gerado são determinados a partir do tipo do parâmetro que está associado ao corpo da solicitação ou especificado com o Accepts método de extensão. Por padrão, o tipo de conteúdo de um FromBody parâmetro será application/json e o tipo de conteúdo do FromForm (s) parâmetro(s) será multipart/form-data ou application/x-www-form-urlencoded.

O suporte para esses tipos de conteúdo padrão é integrado às APIs mínimas e outros tipos de conteúdo podem ser tratados usando a associação personalizada. Consulte o tópico Associação personalizada da documentação de APIs mínimas para obter mais informações.

Há várias maneiras de especificar um tipo de conteúdo diferente para o corpo da solicitação. Se o tipo do FromBody parâmetro implementar IEndpointParameterMetadataProvider, ASP.NET Core usará essa interface para determinar os tipos de conteúdo no corpo da solicitação. A estrutura usa o PopulateMetadata método dessa interface para definir os tipos de conteúdo e o tipo de conteúdo do corpo do corpo da solicitação. Por exemplo, uma Todo classe que aceita application/xml ou content-type text/xml pode usar IEndpointParameterMetadataProvider para fornecer essas informações à estrutura.

public class Todo : IEndpointParameterMetadataProvider
{
    public static void PopulateMetadata(ParameterInfo parameter, EndpointBuilder builder)
    {
        builder.Metadata.Add(new AcceptsMetadata(["application/xml", "text/xml"], typeof(Todo)));
    }
}

O Accepts método de extensão também pode ser usado para especificar o tipo de conteúdo do corpo da solicitação. No exemplo a seguir, o ponto de extremidade aceita um objeto Todo no corpo da solicitação com um tipo de conteúdo esperado de application/xml.

app.MapPut("/todos/{id}", (int id, Todo todo) => ...)
  .Accepts<Todo>("application/xml");

Como application/xml não é um tipo de conteúdo interno, a Todo classe deve implementar a IBindableFromHttpContext<TSelf> interface para fornecer uma associação personalizada para o corpo da solicitação. Por exemplo:

public class Todo : IBindableFromHttpContext<Todo>
{
    public static async ValueTask<Todo?> BindAsync(HttpContext context, ParameterInfo parameter)
    {
        var xmlDoc = await XDocument.LoadAsync(context.Request.Body, LoadOptions.None, context.RequestAborted);
        var serializer = new XmlSerializer(typeof(Todo));
        return (Todo?)serializer.Deserialize(xmlDoc.CreateReader());
    }

Se o ponto de extremidade não definir nenhum parâmetro associado ao corpo da solicitação, use o Accepts método de extensão para especificar o tipo de conteúdo que o ponto de extremidade aceita.

Se você especificar <AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.Accepts%2A> várias vezes, somente os metadados do último serão usados – eles não serão combinados.

Descrever os tipos de resposta

O OpenAPI dá suporte ao fornecimento de uma descrição das respostas retornadas de uma API. ASP.NET Core fornece várias estratégias para definir os metadados de resposta de um ponto de extremidade. Os metadados de resposta que podem ser definidos incluem o código de status, o tipo do corpo da resposta e os tipos de conteúdo de uma resposta. As respostas no OpenAPI podem ter metadados adicionais, como descrição, cabeçalhos, links e exemplos. Esses metadados adicionais podem ser definidos com um transformador de documento ou um transformador de operação.

Os mecanismos específicos para definir metadados de resposta dependem do tipo de aplicativo que está sendo desenvolvido.

Em aplicativos de API mínimos, ASP.NET Core pode extrair os metadados de resposta adicionados por métodos de extensão no ponto de extremidade, atributos no manipulador de rotas e o tipo de retorno do manipulador de rotas.

  • O Produces método de extensão pode ser usado no ponto de extremidade para especificar o código de status, o tipo do corpo da resposta e os tipos de conteúdo de uma resposta de um ponto de extremidade.
  • O [ProducesResponseType] atributo or ProducesResponseTypeAttribute<T> pode ser usado para especificar o tipo do corpo da resposta.
  • Um manipulador de rotas pode ser usado para retornar um tipo que implementa IEndpointMetadataProvider para especificar o tipo e os tipos de conteúdo do corpo da resposta.
  • O ProducesProblem método de extensão no ponto de extremidade pode ser usado para especificar o código de status e os tipos de conteúdo de uma resposta de erro.

Observe que os Produces métodos de extensão e ProducesProblem são suportados em ambos e RouteHandlerBuilder em RouteGroupBuilder. Isso permite, por exemplo, que um conjunto comum de respostas de erro seja definido para todas as operações em um grupo.

Quando não especificado por uma das estratégias anteriores, o:

  • O código de status para a resposta é padronizado para 200.
  • O esquema para o corpo da resposta pode ser inferido do tipo de retorno implícito ou explícito do método de ponto de extremidade, por exemplo, de T in Task<TResult>; caso contrário, ele será considerado não especificado.
  • O tipo de conteúdo para o corpo de resposta especificado ou inferido é "application/json".

Em APIs mínimas, o Produces método de extensão e o [ProducesResponseType] atributo definem apenas os metadados de resposta para o ponto de extremidade. Eles não modificam ou restringem o comportamento do ponto de extremidade, que pode retornar um código de status ou tipo de corpo de resposta diferente do especificado pelos metadados, e o tipo de conteúdo é determinado pelo tipo de retorno do método do manipulador de rotas, independentemente de qualquer tipo de conteúdo especificado em atributos ou métodos de extensão.

O Produces método de extensão pode especificar o tipo de resposta de um ponto de extremidade, com um código de status padrão de 200 e um tipo de conteúdo padrão de application/json. O exemplo a seguir ilustra isso:

app.MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync())
  .Produces<IList<Todo>>();

O [ProducesResponseType] pode ser usado para adicionar metadados de resposta a um ponto de extremidade. Observe que o atributo é aplicado ao método do manipulador de rotas, não à invocação do método para criar a rota, conforme mostrado no exemplo a seguir:

app.MapGet("/todos",
    [ProducesResponseType<List<Todo>>(200)]
    async (TodoDb db) => await db.Todos.ToListAsync());

Usar TypedResults na implementação do manipulador de rotas de um ponto de extremidade inclui automaticamente os metadados de tipo de resposta para o ponto de extremidade. Por exemplo, o código a seguir anota automaticamente o ponto de extremidade com uma resposta no código de status 200 com um tipo de conteúdo application/json.

app.MapGet("/todos", async (TodoDb db) =>
{
    var todos = await db.Todos.ToListAsync();
    return TypedResults.Ok(todos);
});

Somente os tipos de retorno que implementam IEndpointMetadataProvider criam uma responses entrada no documento OpenAPI. Veja a seguir uma lista parcial de alguns dos TypedResults métodos auxiliares que produzem uma responses entrada:

Método auxiliar TypedResults código de status
Ok() 200
Criado() 201
CreatedAtRoute() 201
Aceito() 202
AcceptedAtRoute() 202
NoContent() 204
BadRequest() 400
ValidationProblem() 400
NotFound() 404
Conflito() 409
Entidade não processável() 422

Todos esses métodos, exceto NoContent têm uma sobrecarga genérica que especifica o tipo do corpo da resposta.

Uma classe pode ser implementada para definir os metadados do ponto de extremidade e retorná-los do manipulador de rotas.

Definir respostas para ProblemDetails

Ao definir o tipo de resposta para pontos de extremidade que podem retornar uma resposta ProblemDetails, o seguinte pode ser usado para adicionar os metadados de resposta apropriados para o ponto de extremidade:

Para obter mais informações sobre como configurar um aplicativo de API mínima para retornar respostas ProblemDetails, consulte Lidar com erros em APIs mínimas.

Vários tipos de resposta

Se um ponto de extremidade puder retornar diferentes tipos de resposta em cenários diferentes, você poderá fornecer metadados das seguintes maneiras:

  • Chame o método de extensão Produces várias vezes, conforme mostrado no exemplo a seguir:

    app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
             await db.Todos.FindAsync(id) 
             is Todo todo
             ? Results.Ok(todo) 
             : Results.NotFound())
       .Produces<Todo>(StatusCodes.Status200OK)
       .Produces(StatusCodes.Status404NotFound);
    
  • Use Results<TResult1,TResult2,TResult3,TResult4,TResult5,TResult6> na assinatura e TypedResults no corpo do manipulador, conforme mostrado no exemplo a seguir:

    app.MapGet("/book/{id}", Results<Ok<Book>, NotFound> (int id, List<Book> bookList) =>
    {
        return bookList.FirstOrDefault((i) => i.Id == id) is Book book
         ? TypedResults.Ok(book)
         : TypedResults.NotFound();
    });
    

    Os Results<TResult1,TResult2,TResultN> (tipos de união) declaram que um manipulador de rotas retorna vários tipos concretos de implementação IResult, e qualquer um desses tipos que implementam IEndpointMetadataProvider contribuirá para os metadados do ponto de extremidade.

    Os tipos de união implementam operadores de conversão implícitos. Esses operadores permitem que o compilador converta automaticamente os tipos especificados nos argumentos genéricos em uma instância do tipo de união. Essa funcionalidade tem o benefício adicional de verificar durante o tempo de compilação se um manipulador de rotas retorna apenas os resultados que ele declara retornar. Tentar retornar um tipo que não é declarado como um dos argumentos genéricos para Results<TResult1,TResult2,TResultN> resulta em um erro de compilação.

Excluindo pontos de extremidade do documento gerado

Por padrão, todos os pontos de extremidade definidos em um aplicativo são documentados no arquivo OpenAPI gerado, mas os pontos de extremidade podem ser excluídos do documento usando atributos ou métodos de extensão.

O mecanismo para especificar um ponto de extremidade que deve ser excluído depende do tipo de aplicativo que está sendo desenvolvido.

As APIs mínimas dão suporte a duas estratégias para excluir um determinado endpoint do documento OpenAPI:

A amostra a seguir demonstra as diferentes estratégias para excluir um determinado ponto de extremidade do documento OpenAPI gerado.

app.MapGet("/extension-method", () => "Hello world!")
  .ExcludeFromDescription();

app.MapGet("/attributes",
  [ExcludeFromDescription]
  () => "Hello world!");

Inclusão de metadados OpenAPI para tipos de dados

As classes ou registros C# usados em corpos de solicitação ou resposta são representados como esquemas no documento OpenAPI gerado. Por padrão, somente as propriedades públicas são representadas no esquema, mas também há JsonSerializerOptions para criar propriedades de esquema para campos.

Quando o PropertyNamingPolicy é definido como camelCase (esse é o padrão em aplicativos Web do ASP.NET), os nomes de propriedade em um esquema são a forma camelCase do nome da propriedade de classe ou registro. O [JsonPropertyName] pode ser usado em uma propriedade individual para especificar o nome da propriedade no esquema.

tipo e formato

A biblioteca de esquema JSON mapeia tipos C# padrão para OpenAPI type e format da seguinte maneira:

Tipo de C# OpenAPI type OpenAPI format
INT Número inteiro int32
longo Número inteiro int64
short Número inteiro int16
byte Número inteiro uint8
float número FLOAT
double número duplo
decimal número duplo
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 omitido
dynamic omitido

Observe que os tipos objeto e dinâmico não têm nenhum tipo definido no OpenAPI porque podem conter dados de qualquer tipo, incluindo tipos primitivos como número inteiro ou cadeia de caracteres.

O type e format também podem ser definidos com um Transformador de Esquema. Por exemplo, você pode querer que o format de tipos decimais sejam decimal em vez de double.

Uso de atributos para adicionar metadados

ASP.NET usa metadados de atributos em propriedades de classe ou registro para definir metadados nas propriedades correspondentes do esquema gerado.

A tabela a seguir resume os atributos do namespace System.ComponentModel que fornecem metadados para o esquema gerado:

Atributo Descrição
[Description] Define o description de uma propriedade no esquema.
[Required] Marca uma propriedade como required no esquema.
[DefaultValue] Define o valor default de uma propriedade no esquema.
[Range] Define o valor minimum e maximum de um inteiro ou número.
[MinLength] Define o minLength de uma cadeia de caracteres.
[MaxLength] Define o maxLength de uma string.
[RegularExpression] Define o pattern de uma cadeia de caracteres.

Observe que, em aplicativos baseados em controlador, esses atributos adicionam filtros à operação para validar se os dados de entrada atendem às restrições. Nas APIs mínimas, esses atributos definem os metadados no esquema gerado, mas a validação deve ser executada explicitamente por meio de um filtro de ponto de extremidade, na lógica do manipulador de rotas ou por meio de um pacote de terceiros.

Outras fontes de metadados para esquemas gerados

obrigatório

As propriedades também podem ser marcadas como required com o modificador necessário.

enum

Os tipos enumeração em C# são baseados em inteiros, mas podem ser representados como cadeias de caracteres em JSON com um [JsonConverter] e um JsonStringEnumConverter. Quando um tipo enumeração é representado como uma cadeia de caracteres em JSON, o esquema gerado terá uma propriedade enum com os valores de cadeia de caracteres da enumeração. Um tipo enumeração sem um [JsonConverter] será definido como type: integer no esquema gerado.

Observação: o [AllowedValues] não define os valores enum de uma propriedade.

nullable

As propriedades definidas como um valor anulável ou tipo de referência têm nullable: true no esquema gerado. Isso é consistente com o comportamento padrão do desserializador System.Text.Json, que aceita null como um valor válido para uma propriedade que permite valor nulo.

additionalProperties

Os esquemas são gerados sem uma asserção additionalProperties por padrão, o que implica o padrão de true. Isso é consistente com o comportamento padrão do desserializador System.Text.Json, que ignora silenciosamente propriedades adicionais em um objeto JSON.

Se as propriedades adicionais de um esquema tiverem apenas valores de um tipo específico, defina a propriedade ou classe como um Dictionary<string, type>. O tipo da chave para o dicionário deve ser string. Isso gera um esquema com additionalProperties, especificando o esquema para "tipo" como os tipos de valor necessários.

Metadados para tipos polimórficos

Use os atributos [JsonPolymorphic] e [JsonDerivedType] em uma classe pai para especificar o campo discriminador e os subtipos para um tipo polimórfico.

O [JsonDerivedType] adiciona o campo discriminador ao esquema para cada subclasse, com uma enumeração especificando o valor discriminador específico para a subclasse. Esse atributo também modifica o construtor de cada classe derivada para definir o valor do discriminador.

Uma classe abstrata com um atributo [JsonPolymorphic] tem um campo discriminator no esquema, mas uma classe concreta com um atributo [JsonPolymorphic] não tem um campo discriminator. O OpenAPI requer que a propriedade discriminatória seja uma propriedade obrigatória no esquema, mas como a propriedade discriminatória não está definida na classe base concreta, o esquema não pode incluir um campo discriminator.

Adicionar metadados com um transformador de esquema

Um transformador de esquema pode ser usado para substituir quaisquer metadados padrão ou incluir metadados adicionais, como valores example, ao esquema gerado. Para obter mais informações, consulte Usar transformadores de esquema.

Opções para personalizar a geração de documentos OpenAPI

As seções a seguir demonstram como personalizar a geração de documentos OpenAPI.

Personalizar o nome do documento OpenAPI

Cada documento OpenAPI em um aplicativo tem um nome exclusivo. O nome do documento padrão registrado é v1.

builder.Services.AddOpenApi(); // Document name is v1

Para modificar o nome do documento, passe o nome como um parâmetro para a chamada AddOpenApi.

builder.Services.AddOpenApi("internal"); // Document name is internal

O nome do documento aparece em vários locais na implementação do OpenAPI.

Ao buscar o documento OpenAPI gerado, o nome do documento é fornecido como o argumento de parâmetro documentName na solicitação. As solicitações a seguir resolvem os documentos v1 e internal.

GET http://localhost:5000/openapi/v1.json
GET http://localhost:5000/openapi/internal.json

Personalizar a versão do OpenAPI de um documento gerado

Por padrão, a geração de documentos OpenAPI cria um documento compatível com a v3.0 da especificação OpenAPI. O código a seguir demonstra como modificar a versão padrão do documento OpenAPI:

builder.Services.AddOpenApi(options =>
{
    options.OpenApiVersion = OpenApiSpecVersion.OpenApi2_0;
});

Personalizar a rota do ponto de extremidade OpenAPI

Por padrão, o ponto de extremidade OpenAPI registrado por meio de uma chamada para MapOpenApi expõe o documento no ponto de extremidade /openapi/{documentName}.json. O código a seguir demonstra como personalizar a rota na qual o documento OpenAPI está registrado:

app.MapOpenApi("/openapi/{documentName}/openapi.json");

É possível, mas não recomendado, remover o parâmetro de rota documentName da rota do ponto de extremidade. Quando o parâmetro de rota documentName é removido da rota do ponto de extremidade, a estrutura tenta resolver o nome do documento a partir do parâmetro de consulta. Não fornecer o documentName na rota ou na consulta pode resultar em um comportamento inesperado.

Personalizar o ponto de extremidade OpenAPI

Como o documento OpenAPI é servido por meio de um ponto de extremidade do manipulador de rota, qualquer personalização disponível para ponto de extremidade mínimos padrão estará disponível para o ponto de extremidade OpenAPI.

Limitar o acesso a documentos OpenAPI a usuários autorizados

O ponto de extremidade OpenAPI não permite nenhuma verificação de autorização por padrão. No entanto, as verificações de autorização podem ser aplicadas ao documento OpenAPI. No código a seguir, o acesso ao documento OpenAPI é limitado a quem possui a função tester:

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization(o =>
{
    o.AddPolicy("ApiTesterPolicy", b => b.RequireRole("tester"));
});
builder.Services.AddOpenApi();

var app = builder.Build();

app.MapOpenApi()
    .RequireAuthorization("ApiTesterPolicy");

app.MapGet("/", () => "Hello world!");

app.Run();

Documento OpenAPI gerado pelo cache

O documento OpenAPI é gerado novamente sempre que uma solicitação ao ponto de extremidade OpenAPI é enviada. A regeneração permite que os transformadores incorporem o estado dinâmico do aplicativo em sua operação. Por exemplo, regenerar uma solicitação com detalhes do contexto HTTP. Quando aplicável, o documento OpenAPI pode ser armazenado em cache para evitar a execução do pipeline de geração de documentos em cada solicitação HTTP.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOutputCache(options =>
{
    options.AddBasePolicy(policy => policy.Expire(TimeSpan.FromMinutes(10)));
});
builder.Services.AddOpenApi();

var app = builder.Build();

app.UseOutputCache();

app.MapOpenApi()
    .CacheOutput();

app.MapGet("/", () => "Hello world!");

app.Run();

Transformadores de documento OpenAPI

Esta seção demonstra como personalizar documentos OpenAPI com transformadores.

Personalizar documentos OpenAPI com transformadores

Os transformadores fornecem uma API para modificar o documento OpenAPI com personalizações definidas pelo usuário. Transformadores são úteis para cenários como:

  • Adicionar parâmetros a todas as operações em um documento.
  • Modificar descrições para parâmetros ou operações.
  • Adicionar informações de nível superior ao documento OpenAPI.

Os transformadores se enquadram em três categorias:

  • Os transformadores de documento têm acesso a todo o documento OpenAPI. Eles podem ser usados para fazer modificações globais no documento.
  • Os transformadores de operação se aplicam a cada operação individual. Cada operação individual é uma combinação de caminho e método HTTP. Elas podem ser usadas para modificar parâmetros ou respostas em pontos de extremidade.
  • Os transformadores de esquema se aplicam a cada esquema no documento. Eles podem ser usados para modificar o esquema de corpos de solicitação ou resposta ou qualquer esquema aninhado.

Os transformadores podem ser registrados no documento chamando o metodo AddDocumentTransformer no objeto OpenApiOptions. O seguinte trecho de código mostra diferentes maneiras de registrar transformadores no documento:

using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer((document, context, cancellationToken)
                             => Task.CompletedTask);
    options.AddDocumentTransformer(new MyDocumentTransformer());
    options.AddDocumentTransformer<MyDocumentTransformer>();
    options.AddOperationTransformer((operation, context, cancellationToken)
                            => Task.CompletedTask);
    options.AddOperationTransformer(new MyOperationTransformer());
    options.AddOperationTransformer<MyOperationTransformer>();
    options.AddSchemaTransformer((schema, context, cancellationToken)
                            => Task.CompletedTask);
    options.AddSchemaTransformer(new MySchemaTransformer());
    options.AddSchemaTransformer<MySchemaTransformer>();
});

var app = builder.Build();

app.MapOpenApi();

app.MapGet("/", () => "Hello world!");

app.Run();

Ordem de execução para transformadores

Os transformadores são executados na ordem "primeiro a entrar, primeiro a sair" com base no registro. No trecho de código a seguir, o transformador de documento tem acesso às modificações feitas pelo transformador de operação:

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi(options =>
{
    options.AddOperationTransformer((operation, context, cancellationToken)
                                     => Task.CompletedTask);
    options.AddDocumentTransformer((document, context, cancellationToken)
                                     => Task.CompletedTask);
});

var app = builder.Build();

app.MapOpenApi();

app.MapGet("/", () => "Hello world!");

app.Run();

Usar transformadores de documento

Os transformadores de documento têm acesso a um objeto de contexto que inclui:

Os transformadores de documento também podem alterar o documento OpenAPI gerado. O exemplo a seguir demonstra um transformador de documento que adiciona algumas informações sobre a API ao documento OpenAPI.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer((document, context, cancellationToken) =>
    {
        document.Info = new()
        {
            Title = "Checkout API",
            Version = "v1",
            Description = "API for processing checkouts from cart."
        };
        return Task.CompletedTask;
    });
});

var app = builder.Build();

app.MapOpenApi();

app.MapGet("/", () => "Hello world!");

app.Run();

Os transformadores de documento ativados pelo serviço podem utilizar instâncias de DI para modificar o aplicativo. O exemplo a seguir demonstra um transformador de documento que usa o serviço IAuthenticationSchemeProvider da camada de autenticação. Ele verifica se algum esquema relacionado ao portador JWT está registrado no aplicativo e os adiciona ao nível superior do documento OpenAPI:

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAuthentication().AddJwtBearer();

builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer<BearerSecuritySchemeTransformer>();
});

var app = builder.Build();

app.MapOpenApi();

app.MapGet("/", () => "Hello world!");

app.Run();

internal sealed class BearerSecuritySchemeTransformer(IAuthenticationSchemeProvider authenticationSchemeProvider) : IOpenApiDocumentTransformer
{
    public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
    {
        var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync();
        if (authenticationSchemes.Any(authScheme => authScheme.Name == "Bearer"))
        {
            var requirements = new Dictionary<string, OpenApiSecurityScheme>
            {
                ["Bearer"] = new OpenApiSecurityScheme
                {
                    Type = SecuritySchemeType.Http,
                    Scheme = "bearer", // "bearer" refers to the header name here
                    In = ParameterLocation.Header,
                    BearerFormat = "Json Web Token"
                }
            };
            document.Components ??= new OpenApiComponents();
            document.Components.SecuritySchemes = requirements;
        }
    }
}

Os transformadores de documento são exclusivos da instância do documento à qual estão associados. No exemplo a seguir, um transformador:

  • Registra requisitos relacionados à autenticação no documento internal.
  • Deixa o documento public não modificado.
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAuthentication().AddJwtBearer();

builder.Services.AddOpenApi("internal", options =>
{
    options.AddDocumentTransformer<BearerSecuritySchemeTransformer>();
});
builder.Services.AddOpenApi("public");

var app = builder.Build();

app.MapOpenApi();

app.MapGet("/world", () => "Hello world!")
    .WithGroupName("internal");
app.MapGet("/", () => "Hello universe!")
    .WithGroupName("public");

app.Run();

internal sealed class BearerSecuritySchemeTransformer(IAuthenticationSchemeProvider authenticationSchemeProvider) : IOpenApiDocumentTransformer
{
    public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
    {
        var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync();
        if (authenticationSchemes.Any(authScheme => authScheme.Name == "Bearer"))
        {
            // Add the security scheme at the document level
            var requirements = new Dictionary<string, OpenApiSecurityScheme>
            {
                ["Bearer"] = new OpenApiSecurityScheme
                {
                    Type = SecuritySchemeType.Http,
                    Scheme = "bearer", // "bearer" refers to the header name here
                    In = ParameterLocation.Header,
                    BearerFormat = "Json Web Token"
                }
            };
            document.Components ??= new OpenApiComponents();
            document.Components.SecuritySchemes = requirements;

            // Apply it as a requirement for all operations
            foreach (var operation in document.Paths.Values.SelectMany(path => path.Operations))
            {
                operation.Value.Security.Add(new OpenApiSecurityRequirement
                {
                    [new OpenApiSecurityScheme { Reference = new OpenApiReference { Id = "Bearer", Type = ReferenceType.SecurityScheme } }] = Array.Empty<string>()
                });
            }
        }
    }
}

Usar transformadores de operação

As operações são combinações exclusivas de métodos e caminhos HTTP em um documento OpenAPI. Os transformadores de operação são úteis quando uma modificação:

  • Deve ser feita para cada ponto de extremidade em um aplicativo ou
  • Aplicada condicionalmente a determinadas rotas.

Os transformadores de operação têm acesso a um objeto de contexto que contém:

Por exemplo, o transformador de operação a seguir adiciona 500 como um código de status de resposta compatível com todas as operações no documento.

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAuthentication().AddJwtBearer();

builder.Services.AddOpenApi(options =>
{
    options.AddOperationTransformer((operation, context, cancellationToken) =>
    {
        operation.Responses.Add("500", new OpenApiResponse { Description = "Internal server error" });
        return Task.CompletedTask;
    });
});

var app = builder.Build();

app.MapOpenApi();

app.MapGet("/", () => "Hello world!");

app.Run();

Usar transformadores de esquema

Esquemas são os modelos de dados usados em corpos de solicitação e resposta em um documento OpenAPI. Os transformadores de esquema são úteis quando uma modificação:

  • Deve ser feita a cada esquema no documento, ou
  • Aplicada condicionalmente a determinados esquemas.

Os transformadores de esquema têm acesso a um objeto de contexto que contém:

  • O nome do documento ao qual o esquema pertence.
  • As informações de tipo JSON associadas ao esquema de destino.
  • O IServiceProvider usado na geração de documentos.

Por exemplo, o transformador de esquema a seguir define o format de tipos decimais como decimal em vez de double:

using Microsoft.AspNetCore.OpenApi;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi(options => {
    // Schema transformer to set the format of decimal to 'decimal'
    options.AddSchemaTransformer((schema, context, cancellationToken) =>
    {
        if (context.JsonTypeInfo.Type == typeof(decimal))
        {
            schema.Format = "decimal";
        }
        return Task.CompletedTask;
    });
});

var app = builder.Build();

app.MapOpenApi();

app.MapGet("/", () => new Body { Amount = 1.1m });

app.Run();

public class Body {
    public decimal Amount { get; set; }
}

Recursos adicionais

As APIs mínimas fornecem suporte interno para gerar informações sobre pontos de extremidade em um aplicativo por meio do pacote Microsoft.AspNetCore.OpenApi. Expor a definição do OpenAPI gerada por meio de uma interface do usuário visual requer um pacote de terceiros. Para obter informações sobre o suporte para OpenAPI em APIs baseadas em controlador, consulte a versão .NET 9 deste artigo.

O código a seguir é gerado pelo modelo de API Web mínima do ASP.NET Core e usa o OpenAPI:

using Microsoft.AspNetCore.OpenApi;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weatherforecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateTime.Now.AddDays(index),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();

app.Run();

internal record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

No código realçado anterior:

  • O Microsoft.AspNetCore.OpenApi é explicado na próxima seção.
  • AddEndpointsApiExplorer: configura o aplicativo para usar a API Explorer para descobrir e descrever pontos de extremidade com anotações padrão. O WithOpenApisubstitui as anotações padrão e correspondentes geradas pela API Explorer com aquelas produzidas no pacote Microsoft.AspNetCore.OpenApi.
  • O UseSwaggeradiciona o middleware Swagger.
  • “UseSwaggerUI” permite uma versão incorporada da ferramenta Swagger UI.
  • WithName: o IEndpointNameMetadata no ponto de extremidade é usado para geração de links e é tratado como a ID da operação na especificação do OpenAPI do ponto de extremidade fornecida.
  • O WithOpenApi é explicado posteriormente neste artigo.

Pacote NuGet Microsoft.AspNetCore.OpenApi

O ASP.NET Core fornece o pacote Microsoft.AspNetCore.OpenApi para interagir com as especificações do OpenAPI para pontos de extremidade. O pacote atua como um link entre os modelos do OpenAPI definidos no pacote Microsoft.AspNetCore.OpenApi e os pontos de extremidade definidos em APIs Mínimas. O pacote fornece uma API que examina parâmetros, respostas e metadados de um ponto de extremidade para construir um tipo de anotação do OpenAPI usado para descrever um ponto de extremidade.

O Microsoft.AspNetCore.OpenApi é adicionado como um PackageReference a um arquivo de projeto:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>    
    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.*-*" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
  </ItemGroup>

</Project>

Ao usar Swashbuckle.AspNetCore com Microsoft.AspNetCore.OpenApi, a versão 6.4.0 do Swashbuckle.AspNetCore ou posterior deve ser usada. A versão 1.4.3 do Microsoft.OpenApi ou posterior deve ser usada para aproveitar os construtores de cópia nas invocações do WithOpenApi.

Adicionar anotações do OpenAPI a pontos de extremidade por meio do WithOpenApi

Chamar o WithOpenApi no ponto de extremidade complementa os metadados do ponto de extremidade. Esses metadados podem ser:

  • Consumidos em pacotes de terceiros, como Swashbuckle.AspNetCore.
  • Exibidos na interface do Swagger, no YAML ou no JSON gerado para definir a API.
app.MapPost("/todoitems/{id}", async (int id, Todo todo, TodoDb db) =>
{
    todo.Id = id;
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
})
.WithOpenApi();

Modificar a anotação do OpenAPI no WithOpenApi

O método WithOpenApi aceita uma função que pode ser usada para modificar a anotação do OpenAPI. Por exemplo, no código a seguir, uma descrição é adicionada ao primeiro parâmetro do ponto de extremidade:

app.MapPost("/todo2/{id}", async (int id, Todo todo, TodoDb db) =>
{
    todo.Id = id;
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
})
.WithOpenApi(generatedOperation =>
{
    var parameter = generatedOperation.Parameters[0];
    parameter.Description = "The ID associated with the created Todo";
    return generatedOperation;
});

Adicionar IDs de operação ao OpenAPI

As IDs de operação são usadas para identificar exclusivamente um determinado ponto de extremidade no OpenAPI. O método de extensão WithName pode ser usado para definir a ID da operação usada para um método .

app.MapGet("/todoitems2", async (TodoDb db) =>
    await db.Todos.ToListAsync())
    .WithName("GetToDoItems");

Como alternativa, a propriedade OperationId pode ser definida diretamente na anotação do OpenAPI.

app.MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync())
    .WithOpenApi(operation => new(operation)
    {
        OperationId = "GetTodos"
    });

Adicionar marcas à descrição do OpenAPI

O OpenAPI dá suporte ao uso de objetos de marca para categorizar operações. Normalmente, essas marcas são usadas para agrupar operações na interface do usuário do Swagger. Essas marcas podem ser adicionadas a uma operação invocando o método de extensão WithTags no ponto de extremidade com as marcas desejadas.

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.ToListAsync())
    .WithTags("TodoGroup");

Como alternativa, a lista de OpenApiTags pode ser definida na anotação do OpenAPI por meio do método de extensão WithOpenApi.

app.MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync())
    .WithOpenApi(operation => new(operation)
    {
        Tags = new List<OpenApiTag> { new() { Name = "Todos" } }
    });

Adicionar resumo ou descrição do ponto de extremidade

O resumo e a descrição do ponto de extremidade podem ser adicionados invocando o método de extensão WithOpenApi. No código a seguir, os resumos são definidos diretamente na anotação do OpenAPI.

app.MapGet("/todoitems2", async (TodoDb db) => await db.Todos.ToListAsync())
    .WithOpenApi(operation => new(operation)
    {
        Summary = "This is a summary",
        Description = "This is a description"
    });

Excluir descrição do OpenAPI

No exemplo a seguir, o ponto de extremidade /skipme é excluído da geração de uma descrição do OpenAPI:

using Microsoft.AspNetCore.OpenApi;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.MapGet("/swag", () => "Hello Swagger!")
    .WithOpenApi();
app.MapGet("/skipme", () => "Skipping Swagger.")
                    .ExcludeFromDescription();

app.Run();

Marcar uma API como obsoleta

Para marcar um ponto de extremidade como obsoleto, defina a propriedade Deprecated na anotação do OpenAPI.

app.MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync())
    .WithOpenApi(operation => new(operation)
    {
        Deprecated = true
    });

Descrever os tipos de resposta

O OpenAPI dá suporte ao fornecimento de uma descrição das respostas retornadas de uma API. As APIs mínimas dão suporte a três estratégias para definir o tipo de resposta de um ponto de extremidade:

O método de extensão Produces pode ser usado para adicionar metadados Produces a um ponto de extremidade. Quando nenhum parâmetro é fornecido, o método de extensão preenche metadados para o tipo de destino em um código de status 200 e um tipo de conteúdo application/json.

app
    .MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync())
    .Produces<IList<Todo>>();

Usar TypedResults na implementação do manipulador de rotas de um ponto de extremidade inclui automaticamente os metadados de tipo de resposta para o ponto de extremidade. Por exemplo, o código a seguir anota automaticamente o ponto de extremidade com uma resposta no código de status 200 com um tipo de conteúdo application/json.

app.MapGet("/todos", async (TodoDb db) =>
{
    var todos = await db.Todos.ToListAsync());
    return TypedResults.Ok(todos);
});

Definir respostas para ProblemDetails

Ao definir o tipo de resposta para pontos de extremidade que podem retornar uma resposta ProblemDetails, o método de extensão ProducesProblem, ProducesValidationProblem ou TypedResults.Problem pode ser usado para adicionar a anotação apropriada aos metadados do ponto de extremidade. Observe que os métodos de extensão ProducesProblem e ProducesValidationProblem não podem ser usados com grupos de rotas no .NET 8 e anteriores.

Quando não há anotações explícitas fornecidas por uma das estratégias acima, a estrutura tenta determinar um tipo de resposta padrão examinando a assinatura da resposta. Essa resposta padrão é preenchida sob o código de status 200 na definição do OpenAPI.

Vários tipos de resposta

Se um ponto de extremidade puder retornar diferentes tipos de resposta em cenários diferentes, você poderá fornecer metadados das seguintes maneiras:

  • Chame o método de extensão Produces várias vezes, conforme mostrado no exemplo a seguir:

    app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
             await db.Todos.FindAsync(id) 
             is Todo todo
             ? Results.Ok(todo) 
             : Results.NotFound())
       .Produces<Todo>(StatusCodes.Status200OK)
       .Produces(StatusCodes.Status404NotFound);
    
  • Use Results<TResult1,TResult2,TResultN> na assinatura e TypedResults no corpo do manipulador, conforme mostrado no exemplo a seguir:

    app.MapGet("/book/{id}", Results<Ok<Book>, NotFound> (int id, List<Book> bookList) =>
    {
        return bookList.FirstOrDefault((i) => i.Id == id) is Book book
         ? TypedResults.Ok(book)
         : TypedResults.NotFound();
    });
    

    Os Results<TResult1,TResult2,TResultN> (tipos de união) declaram que um manipulador de rotas retorna vários tipos concretos de implementação IResult, e qualquer um desses tipos que implementam IEndpointMetadataProvider contribuirá para os metadados do ponto de extremidade.

    Os tipos de união implementam operadores de conversão implícitos. Esses operadores permitem que o compilador converta automaticamente os tipos especificados nos argumentos genéricos em uma instância do tipo de união. Essa funcionalidade tem o benefício adicional de verificar durante o tempo de compilação se um manipulador de rotas retorna apenas os resultados que ele declara retornar. Tentar retornar um tipo que não é declarado como um dos argumentos genéricos para Results<TResult1,TResult2,TResultN> resulta em um erro de compilação.

Descrever o corpo da solicitação e os parâmetros

Além de descrever os tipos retornados por um ponto de extremidade, o OpenAPI também dá suporte à anotação das entradas consumidas por uma API. Essas entradas se enquadram em duas categorias:

  • Parâmetros que aparecem no caminho, cadeia de caracteres de consulta, cabeçalhos ou cookies
  • Dados transmitidos como parte do corpo da solicitação

A estrutura infere os tipos de parâmetros de solicitação no caminho, na consulta e na cadeia de caracteres de cabeçalho automaticamente com base na assinatura do manipulador de rotas.

Para definir o tipo de entradas transmitidas como o corpo da solicitação, configure as propriedades usando o método de extensão Accepts para definir o tipo de objeto e o tipo de conteúdo esperados pelo manipulador de solicitação. No exemplo a seguir, o ponto de extremidade aceita um objeto Todo no corpo da solicitação com um tipo de conteúdo esperado de application/xml.

app.MapPost("/todos/{id}", (int id, Todo todo) => ...)
  .Accepts<Todo>("application/xml");

Além do método de extensão Accepts, um tipo de parâmetro pode descrever sua própria anotação implementando a interface IEndpointParameterMetadataProvider. Por exemplo, o tipo Todo a seguir adiciona uma anotação que requer um corpo da solicitação com um tipo de conteúdo application/xml.

public class Todo : IEndpointParameterMetadataProvider
{
    public static void PopulateMetadata(ParameterInfo parameter, EndpointBuilder builder)
    {
        builder.Metadata.Add(new ConsumesAttribute(typeof(Todo), isOptional: false, "application/xml"));
    }
}

Quando nenhuma anotação explícita é fornecida, a estrutura tenta determinar o tipo de solicitação padrão se houver um parâmetro do corpo da solicitação no manipulador de ponto de extremidade. A inferência usa a seguinte heurística para produzir a anotação:

  • Os parâmetros do corpo da solicitação lidos de um formulário por meio do atributo [FromForm] são descritos com o tipo de conteúdo multipart/form-data.
  • Todos os outros parâmetros do corpo da solicitação são descritos com o tipo de conteúdo application/json.
  • O corpo da solicitação será tratado como opcional se for anulável ou se a propriedade AllowEmpty estiver definida no atributo FromBody.

Suporte ao controle de versão da API

As APIs mínimas dão suporte ao controle de versão da API por meio do pacote Asp.Versioning.Http. Exemplos de configuração de controle de versão com APIs mínimas podem ser encontrados no repositório de controle de versão da API.

Código-fonte do ASP.NET Core OpenAPI no GitHub

Recursos adicionais

Um aplicativo de API mínima pode descrever a especificação do OpenAPI para manipuladores de rota usando o Swashbuckle.

Para obter informações sobre o suporte para OpenAPI em APIs baseadas em controlador, consulte a versão .NET 9 deste artigo.

O código a seguir é um aplicativo do ASP.NET Core típico com suporte ao OpenAPI:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new() { Title = builder.Environment.ApplicationName,
                               Version = "v1" });
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger(); // UseSwaggerUI Protected by if (env.IsDevelopment())
    app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json",
                                    $"{builder.Environment.ApplicationName} v1"));
}

app.MapGet("/swag", () => "Hello Swagger!");

app.Run();

Excluir descrição do OpenAPI

No exemplo a seguir, o ponto de extremidade /skipme é excluído da geração de uma descrição do OpenAPI:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(); // UseSwaggerUI Protected by if (env.IsDevelopment())
}

app.MapGet("/swag", () => "Hello Swagger!");
app.MapGet("/skipme", () => "Skipping Swagger.")
                    .ExcludeFromDescription();

app.Run();

Descrever os tipos de resposta

O exemplo a seguir usa os tipos de resultados internos para personalizar a resposta:

app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
         await db.Todos.FindAsync(id) 
         is Todo todo
         ? Results.Ok(todo) 
         : Results.NotFound())
   .Produces<Todo>(StatusCodes.Status200OK)
   .Produces(StatusCodes.Status404NotFound);

Adicionar IDs de operação ao OpenAPI

app.MapGet("/todoitems2", async (TodoDb db) =>
    await db.Todos.ToListAsync())
    .WithName("GetToDoItems");

Adicionar marcas à descrição do OpenAPI

O código a seguir usa uma marca de agrupamento do OpenAPI:

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.ToListAsync())
    .WithTags("TodoGroup");