Suporte ao OpenAPI em aplicativos de APIs mínimas

A especificação do OpenAPI é um padrão independente de linguagem de programação para documentar APIs HTTP. Esse padrão tem suporte em APIs mínimas por meio de uma combinação de APIs internas e bibliotecas de software livre. Há três aspectos principais para a integração do OpenAPI em um aplicativo:

  • Gerando informações sobre os pontos de extremidade no aplicativo.
  • Coletando as informações em um formato que corresponde ao esquema do OpenAPI.
  • Expondo o esquema do OpenAPI gerado por meio de uma interface do usuário visual ou um arquivo serializado.

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.

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. Requer o pacote nuget Swashbuckle.AspNetCore.
  • O UseSwaggerUI habilita uma versão inserida da ferramenta de interface do usuário do Swagger no modo de desenvolvimento.
  • 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 usuário 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 ou TypedResults.Problem pode ser usado para adicionar a anotação apropriada aos metadados do ponto de extremidade.

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

A especificação do OpenAPI é um padrão independente de linguagem de programação para documentar APIs HTTP. Esse padrão tem suporte em APIs mínimas por meio de uma combinação de APIs internas e bibliotecas de software livre. Há três aspectos principais para a integração do OpenAPI em um aplicativo:

  • Gerando informações sobre os pontos de extremidade no aplicativo.
  • Coletando as informações em um formato que corresponde ao esquema do OpenAPI.
  • Expondo o esquema do OpenAPI gerado por meio de uma interface do usuário visual ou um arquivo serializado.

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.

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 usuário 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 ou TypedResults.Problem pode ser usado para adicionar a anotação apropriada aos metadados do ponto de extremidade.

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 pode descrever a especificação do OpenAPI para manipuladores de rota usando o Swashbuckle.

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");