Como configurar uma WebAPI com Swagger e Swashbuckle

Agora que você já sabe como documentar uma API, chegou a hora de fazer isso de verdade, lá no código, aprendendo a como configurar uma WebAPI utilizando o Swashbuckle e o Swagger como UI! Antes que você me diga que “quando criamos uma webapi, o Swagger já está funcionando”, preciso te lembrar que só isso não é o suficiente para emitir os detalhes da sua documentação. Ela precisa, no mínimo, ser versionada – para acompanhar a evolução da sua API! Neste post vamos aprender como configurar uma WebAPI, com Swagger, utilizando a biblioteca padrão Swashbuckle.

Criando uma WebAPI como básico de documentação

Antes de tudo, vamos criar uma WebApi simples. Eu aconselho que você sempre utilize a última versão da .net sdk. Para este artigo, estou utilizando a versão .net 6. Você também pode utilizar a sua IDE preferida, mas neste artigo, estou utilizando o Rider da JetBrains e fazendo uso da linha de comando dentro do WSL.

Para criar uma nova WebApi, basta você digitar no terminal:

dotnet new webapi -n ApiRightWay -o ./src/ApiRightWay.Api

Eu gosto, também, de sempre criar projetos vinculados a uma solution. Para para isso, crie uma solution e adicione o projeto a ela.

dotnet new sln -n ApiRightWay
dotnet sln add ./src/ApiRightWay.Api/

As IDEs e mesmo a linha de comando ficam mais fáceis de serem utilizadas quando você utiliza uma solution. Um comando dotnet test na mesma pasta da solution, roda os testes de toda a solução. Agora o mesmo comando executado na pasta de um .csproj, executa os testes apenas daquele projeto (se for um projeto de testes).

E por falar no .csproj, se você abri-lo neste exato momento, deve ver algo parecido com:

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

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

  <ItemGroup>
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
  </ItemGroup>

</Project>

Confira se a versão da biblioteca Swashbuckle é a última. Você pode fazer isso através da IDE ou pesquisando a última versão no site https://www.nuget.org/. É importante sempre manter atualizadas essas bibliotecas, já que as novas versões podem corrigir bugs ou trazer novas funcionalidades presentes na OpenAPI. No momento em que eu escrevo este artigo, a lib está na versão 6.4.0. Para atualizar – já que estamos com o .csproj aberto – apenas mude o valor da propriedade Version de “6.2.3” para “6.4.0”. E não se esqueça de rodar o comando dotnet restore na raiz do projeto. E pronto, a biblioteca já está atualizada.

Neste momento, se você rodar a sua aplicação, já teremos uma página com a documentação mais básica possível.

dotnet run --project ./src/ApiRightWay.Api
Print mostrando como fica a renderização da documentação no formato Swagger

Ao acessar o endereço sugerido pela linha de comando, adicionando ao final o /swagger, o seu browser trará a página ilustrada na imagem acima. Nela já podemos conferir algumas configurações básicas, como a versão da API que está sendo mostrada, o controller (ou tag do ponto de vista da OpenAPI) e seu único endpoint. Contudo, se você abrir os detalhes da documentação deste endpoint (clicando na setinha que aponta para baixo), você vai ver que as informações agregam pouco valor. Claro, este é um projeto “template”, logo é fácil discernir o que cada coisa faz. Mas e em um projeto maior, você acha que essa documentação seria suficiente?

Configurando o Swashbuckle

Para ter uma documentação mais rica, é preciso algumas configurações para personalizar a geração da documentação.

Como habilitar o Swashbuckle a ler documentação à partir de comentários?

Abra o arquivo ApiRightWay.csproj, e na primeira ocorrência de PropertyGroup, adicione as seguintes tags:

<GenerateDocumentationFile>true</GenerateDocumentationFile>

Ao ligar essa propriedade, você fará com que a aplicação gere um arquivo XML com toda a documentação que você escreveu para as classes e interfaces públicas do seu sistema. Alguns lugares vão te recomendar a adicionar a propriedade <NoWarn>$(NoWarn);1591</NoWarn>. Essa recomendação é necessária, porque à partir do momento em que você gera a documentação, o compilador entende que todas as classes públicas deveriam ser documentadas.

E cá entre nós, gerar o warning de membro público não documentado, faz total sentido! Lembre-se que cada .csproj é uma biblioteca que poder ser reutilizada por outros sistemas. Sendo assim, você deveria tomar cuidado com o que é público e o que é interno apenas para a biblioteca. E evidentemente, tudo aquilo que é público DEVE ser documentado – já que será acessado por uma terceira pessoa. Por isso eu recomendo você a não desligar esse warning e utilizar a sua emissão como um ponto de reflexão.

Ao executar um build, o compilador irá gerar o Warning CS1591 de membro público não documentado

Agora que sua aplicação já gera documentação em XML, é o momento de informar ao Swashbuckle como ler esses arquivos.

Para não poluir a unit Program.cs, crie uma pasta na raiz do projeto (não da solution) com o nome Extensions. Nela você irá armazenar todos os Extension Methods que criar. Dentro desta pasta, crie uma classe estática chamada SwashbuckleConfigExtension com o método público ConfigSwashbuckle. Veja o código:

using Swashbuckle.AspNetCore.SwaggerGen;

namespace ApiRightWay.Extensions;

public static class SwashbuckleConfigExtension
{
    public static IServiceCollection ConfigSwashbuckle(this IServiceCollection services) =>
        services
            .AddEndpointsApiExplorer()
            .AddSwaggerGen(options => { options.LoadDocumentationFiles(); });

    private static void LoadDocumentationFiles(this SwaggerGenOptions options)
    {
        foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            var xmlDocumentationFile = $"{assembly.GetName().Name}.xml";
            var xmlDocumentationPath = Path.Combine(AppContext.BaseDirectory, xmlDocumentationFile);
            if (File.Exists(xmlDocumentationPath))
            {
                options.IncludeXmlComments(xmlDocumentationPath, includeControllerXmlComments:true);
            }
        }
    }
}

O método ConfigSwashbuckle adiciona uma extensão para a interface IServiceCollection. E basicamente o que ele faz (até o momento) é chamar um outro extension method – só que dessa vez, privado – que é capaz de ler e adicionar todos os XMLs de documentação presentes. Perceba que a chamada a options.IncludeXmlComments tem o seu último parâmetro como true. Isso faz com que ele adicione ainda mais informações a documentação gerada. Em breve mostro o que isso faz.

E para acessar esse código, faça a seguinte alteração na unit Program.cs:

using ApiRightWay.Extensions;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddControllers().Services
    .ConfigSwashbuckle();

var app = builder.Build();

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

app
    .UseHttpsRedirection()
    .UseAuthorization();
app.MapControllers();
app.Run();

Dentre as alterações, fiz uso da fluent api sempre que possível. Acho que essa notação deixa bem mais clara a ideia do pipeline. Também removi tudo o que é relativo a configuração da documentação da API para a extensão que acabamos de criar. E por fim, apenas chamei o método.

Você já deve estar se coçando para testar, certo? Para isso, vá até o método da controller WeatherForecastController e adicione a seguinte documentação:

/* Código omitido */

    /// <summary>
    /// Teste
    /// </summary>
    [HttpGet(Name = "GetWeatherForecast")]
    public IEnumerable<WeatherForecast> Get()
    {

/* Código omitido */

Quando rodar a aplicação e consultar a documentação do sistema, verá que foi adicionado o título a descrição do endpoint:

Sublinhado de vermelho está a palavra "Teste", que foi o que informamos na documentação do endpoint.
Sublinhado de vermelho está a palavra “Teste”, que foi o que informamos na documentação do endpoint.

Mais opções para o SwaggerGen

O método de extensão AddSwaggerGen, que nós configuramos na classe SwashbuckleConfigExtension é onde você vai conseguir configurar grande parte do processo de geração da documentação. A seguir, mostro algumas das opções que gosto de utilizar.

Um detalhe importantíssimo: Este método funciona como uma pipeline – da mesma forma que o IApplicationBuilder – o que quer dizer que a ordem com que as chamadas de método são feitas importa! Então muito cuidado em como você encadeia essas chamadas. A própria documentação do Swashbuckle recomenda que você siga a seguinte ordem: OperationFilter -> SchemaFilter -> DocumentFilter -> Security Definition. Caso algum comportamento da documentação fugir do esperado, considere alterar o método.

options.DescribeAllParametersInCamelCase();

Quando escrevemos código em C#, o code style nos dá algumas diretrizes diferente dos padrões comumente adotado para APIs REST. Por exemplo: Enquanto propriedades, no C#, são escritas em PascalCase, as propriedades em javascript são camelCase. E o mesmo vale para outros elementos de código. Como o Swashbuckle toma o próprio código como fonte de documentação, caso você deseje, essa instrução fará com que todos os parâmetros sejam escritos em camelCase.

options.OperationFilter<AddResponseHeadersFilter>();

Para habilitar essa opção, você precisará incluir a biblioteca Swashbuckle.AspNetCore.Filters ao projeto da API. Esta opção permite que você adicione na documentação alguma informação de Header que vai no retorno da chamada. Muito comum no caso de documentação chamadas que criam novos recursos. Como por exemplo:

options.CustomSchemaIds(type => type.FullName);

Mais um “gosto do freguês”, quando especifico um objeto de retorno na OpenAPI, gosto de dar um nome completo, dizendo até qual é o namespace ao qual ele pertence. O método te passa o tipo que está sendo avaliado. Por padrão, a documentação pega apenas o “ClassName”. Com essa instrução lambda, você pode utilizar .FullName ou qualquer outra estratégia de resolução de nomes.

options.OperationFilter<???>();

Também é importante salientar que você pode escrever os seus próprios filtros de documentação, adicionando valores padrão, customizando detalhes de da documentação e etc.

options.EnableAnnotation()

Para ter disponível esse método, primeiro é preciso adicionar a biblioteca Swashbuckle.AspNetCore.Annotations ao projeto da API. Esta opção habilita alguns atributos que podem ser utilizados em métodos e classes, enriquecendo a documentação. Para saber quais atributos estão disponíveis, acesse a página do Git Hub do projeto (link para a página com os atributos).

[SwaggerResponseHeader(StatusCodes.Status201Created, "Location", "string", "URL do cliente recém-criado")]

Como configurar a UI do Swagger?

Para enriquecer ainda mais a sua documentação, é interessante as pessoas saberem quem elas devem chamar (e não estou falando dos Ghostbusters). E se for uma API pública, um link para a licença de uso também pode ser uma boa ideia. Também é interessante saber quais APIs estão depreciadas para utilização e que podem ser removidas nas próximas versões.

Para configurar tudo isso, o próprio projeto de exemplo da Microsoft nos oferece exemplos. São os arquivos SwaggerDefaultValues.cs (clique aqui para abrir) que adiciona comportamentos e leitura padrão para a documentação. O próprio autor diz que ele será necessário apenas enquanto duas issues estiverem abertas. Depois disso, ele passa a ser opcional. O outro arquivo é o ConfigureSwaggerOptions.cs (clique aqui para abrir), cuja principal missão é configurar as informações de cabeçalho do Swagger para cada versão que você disponibilizar.

Minha recomendação é que você faça o download desses dois arquivos para o seu projeto. E após isso, altere as configurações conforme a sua necessidade. E também não se esqueça de adicioná-los no pipeline de configuração do seu projeto.

/* Código omitido */
        services
            .AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>()
/* Código omitido */
        services
            .AddSwaggerGen(options =>
            {
/* Código omitido */
                options.OperationFilter<SwaggerDefaultValues>();
            });

Configurando Swashbuckle para gerar documentação versionada

Neste exato ponto, se você executar sua API, terá uma exception. Isso porque a classe SwaggerGenOptions tem uma dependência para a interface IApiVersionDescriptionProvider. Resolver essa dependência vai exigir um pouco mais de código.

Antes, adicione ao seu projeto a biblioteca Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer ao projeto. Não se esqueça de fazer o dotnet restore da sua solução. Agora, na classe SwashbuckleConfigExtension, faça a seguinte alteração no método ConfigSwashbuckle:

    public static IServiceCollection ConfigSwashbuckle(this IServiceCollection services) =>
        services
            .AddEndpointsApiExplorer()
            .AddApiVersioning(options =>
            {
                options.ReportApiVersions = true;
                options.AssumeDefaultVersionWhenUnspecified = true;
                options.DefaultApiVersion = new ApiVersion(1, 0);
            })
            .AddVersionedApiExplorer(options =>
            {
                options.GroupNameFormat = "'version - v'VVV";
                options.SubstituteApiVersionInUrl = true;
            })
            .AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>()
            .AddSwaggerGen();

Os métodos AddApiVersioning(...) e AddVersionedApiExplorer(...) vão influenciar no comportamento tanto do roteamento da sua API quanto na geração da documentação. Na linha onde você lê options.GroupNameFormat, por exemplo, você está especificando um padrão para nomeação das versões da API. Aqueles 3 “V” é exatamente onde vai o número da versão. O que está entre aspas simples é texto puro.

Agora que estamos utilizando personalizando as configurações de geração do Swagger pela classe ConfigureSwaggerOptions, podemos tornar o método LoadDocumentationFiles como public e lê-lo à partir do método Configure da classe.

        public void Configure(SwaggerGenOptions options)
        {
            options.LoadDocumentationFiles();
            options.OperationFilter<SwaggerDefaultValues>();
            // add a swagger document for each discovered API version
            // note: you might choose to skip or document deprecated API versions differently
            foreach (var description in provider.ApiVersionDescriptions)
            {
                options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
            }
        } 

Também é necessário que você faça mais uma alteração, dessa vez no método UseSwaggerUI(...), indicando quais e onde estão os arquivos gerados. Importante! Eles são gerados em tempo de execução. Para isso, eu criei mais um extension method, só que dessa vez, para IApplicationBuilder.

using Microsoft.AspNetCore.Mvc.ApiExplorer;

namespace ApiRightWay.Extensions;

internal static class ApiSwashbuckleConfig
{
    public static IApplicationBuilder ConfigureSwagger(this IApplicationBuilder app) =>
        app
            .UseSwagger()
            .UseSwaggerUI(options =>
            {
                var provider = app.ApplicationServices.GetRequiredService<IApiVersionDescriptionProvider>();
                // Geração de um endpoint do Swagger para cada versão descoberta
                foreach (var description in provider.ApiVersionDescriptions)
                {
                    options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json",
                        description.GroupName.ToUpperInvariant());
                }
            });
}

O resultado dessa configuração pode ser percebido no caixa de seleção da versão da API e no caminho do arquivo com as definições:

Com a configuração tanto o path quando o nome da versão sofrem influência.
Com a configuração tanto o path quando o nome da versão sofrem influência.

Últimas alterações no Program.cs

Você vai perceber que as mudanças foram bem pequenas em relação a versão original do Program.cs. Apenas adicionamos as chamadas aos métodos de extensão e agrupamos o código em comum. O resultado final fica assim:

using ApiRightWay.Extensions;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddControllers().Services
    .ConfigSwashbuckle();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app
        .UseDeveloperExceptionPage()
        .ConfigureSwagger();
}

app
    .UseHttpsRedirection()
    .UseAuthorization();
app.MapControllers();
app.Run();

Quase pronto!

Grande parte do que diz respeito a configuração para geração da documentação já está pronto. Mas se olharmos para o nosso swagger nesse momento, ainda não temos lá muito informação útil. Calma! No próximo post nós vamos começar a efetivamente documentar a nossa API. Te espero lá!

Você pode verificar o código de exemplo acessando o meu GitHub: https://github.com/ftathiago/blogdoft-toycode/tree/feature/como-configurar-webapi-com-swagger

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.