DEV Community

Silvair L. Soares
Silvair L. Soares

Posted on

Criando Web APIs modernas, autônomas e rastreáveis com .Net Core utilizando arquitetura de microsserviços – Parte 2

Após a conclusão da etapa 1, onde criamos todos os projetos e configuramos a execução do sistema, as funcionalidades básicas de nossas APIs estão todas funcionando perfeitamente.

Ao pressionar a tecla F5 para executar os projetos da nossa solução, veremos as seguintes janelas serem abertas no browser:

Embora os serviços prometidos estejam funcionando, as informações destas páginas não são nada expressivas para o consumidor de nossas APIs.

O que faremos a seguir é habilitar a documentação automática utilizando o NSwag.

Para isto, vamos executar os seguintes passos:

1. Instalando o NSwag e criando o configurador do Swagger

Acesse o menu Tools ⇒ Nuget Package Manager ⇒ Package Manager Console;
Na janela do console, no campo Default Project, selecione o projeto Erp.Shared;
Digite o comando Install-Package NSwag.AspNetCore, pressione Enter e aguarde a instalação.

Como o projeto Api.Erp.Shared está referenciado em todos os demais projetos, instalaremos o NSwag apenas neste projeto.

A seguir, criaremos dois métodos de extensão para configurar o swagger.

Crie o seguinte arquivo no diretório Extensions do projeto Api.Erp.Shared:

…Api.Erp\Api.Erp.Shared\Extensions\SwaggerConfig.cs

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

namespace Api.Erp.Shared.Extensions
{
    public static class SwaggerConfig
    {
        public static void ConfigureSwaggerDoc(this IServiceCollection services, string description)
        {
            services.AddOpenApiDocument(config =>
            {
                config.Version = "v1";
                config.AllowReferencesWithProperties = true; //Habilita a geração de exemplos para propriedades aninhadas
                config.Title = "Documentação da API: " + Assembly.GetEntryAssembly().GetName().Name;
                config.Description = description;
            });
        }

        public static void ConfigureSwaggerUI(this IApplicationBuilder app)
        {
            // Ativa o middleware para veicular o Swagger gerado como um terminal JSON.
            app.UseOpenApi();

            // Registra o gerador Swagger e os middlewares Swagger UI
            app.UseSwaggerUi3(config => config.TransformToExternalPath = (internalUiRoute, request) =>
            {
                config.Path = "/swagger";
                if (internalUiRoute.StartsWith("/") == true && internalUiRoute.StartsWith(request.PathBase) == false)
                {
                    return request.PathBase + internalUiRoute;
                }
                else
                {
                    return internalUiRoute;
                }
            });
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Habilitando o Swagger no projeto Api.Erp.Clientes

Acesse o arquivo Startup.cs do projeto Api.Erp.Clientes e faça as seguintes alterações:

using Api.Erp.Shared.Extensions;
    public class Startup
    {

        public void ConfigureServices(IServiceCollection services)
        {

            //Habilita o Swagger
            string description = "**API para gerenciamento dos clientes do sistema Api.Erp**";
            services.ConfigureSwaggerDoc(description);
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {

            // Ativa a Swagger-ui
            app.ConfigureSwaggerUI();

        }
    }
Enter fullscreen mode Exit fullscreen mode

3. Habilitando o Swagger no projeto Api.Erp.Comercial

Acesse o arquivo Startup.cs do projeto Api.Erp.Comercial e faça as seguintes alterações:

using Api.Erp.Shared.Extensions;
    public class Startup
    {

        public void ConfigureServices(IServiceCollection services)
        {

            //Habilita o Swagger
            string description = "**API para gerenciamento das vendas do sistema Api.Erp**";
            services.ConfigureSwaggerDoc(description);
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {

            // Ativa a Swagger-ui
            app.ConfigureSwaggerUI();

        }
    }
Enter fullscreen mode Exit fullscreen mode

4. Habilitando o Swagger no projeto Api.Erp.Fiscal

Acesse o arquivo Startup.cs do projeto Api.Erp.Fiscal e faça as seguintes alterações:

using Api.Erp.Shared.Extensions;
    public class Startup
    {

        public void ConfigureServices(IServiceCollection services)
        {

            //Habilita o Swagger
            string description = "**API para gerenciamento das notas fiscais do sistema Api.Erp**";
            services.ConfigureSwaggerDoc(description);
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {

            // Ativa a Swagger-ui
            app.ConfigureSwaggerUI();

        }
    }
Enter fullscreen mode Exit fullscreen mode

A partir deste momento, ao executarmos o nossas APIs (F5), caso acessemos a rota \swagger, as páginas de documentação já serão exibidas.

Nestas páginas, é possível testar todos os endpoints, clicando no botão Try it out e depois no botão Execute.

  1. Abrindo a rota do Swagger automaticamente

Você deve ter notado, que ao executar os projetos, ainda está sendo necessário digitar a rota que exibe a documentação do Swagger (\swagger), este processo pode ser automatizado.

Esta ação, também poderia ser feito via configuração nas propriedades do projeto. Porém, faremos de uma forma um pouco mais simples e não baseada em configuração. Siga os seguintes passos:

Crie um arquivo chamado DocsController.cs, na pasta Controller de cada um dos projetos, com os respectivos conteúdos:

…\Api.Erp\Api.Erp.Clientes\Controllers\DocsController.cs

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Api.Erp.Clientes.Controllers
{
    /// <summary>
    /// Controller para exibir a documentação da API de Clientes
    /// </summary>
    [Route("")]
    [ApiExplorerSettings(IgnoreApi = true)]
    public class DocsController : Controller
    {
        [Route(""), HttpGet]
        [AllowAnonymous]
        public IActionResult Swagger()
        {
            return Redirect("~/swagger");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

…\Api.Erp\Api.Erp.Comercial\Controllers\DocsController.cs

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Api.Erp.Comercial.Controllers
{
    /// <summary>
    /// Controller para exibir a documentação da API Comercial
    /// </summary>
    [Route("")]
    [ApiExplorerSettings(IgnoreApi = true)]
    public class DocsController : Controller
    {
        [Route(""), HttpGet]
        [AllowAnonymous]
        public IActionResult Swagger()
        {
            return Redirect("~/swagger");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

…\Api.Erp\Api.Erp.Fiscal\Controllers\DocsController.cs

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Api.Erp.Fiscal.Controllers
{
    /// <summary>
    /// Controller para exibir a documentação da API Fiscal
    /// </summary>
    [Route("")]
    [ApiExplorerSettings(IgnoreApi = true)]
    public class DocsController : Controller
    {
        [Route(""), HttpGet]
        [AllowAnonymous]
        public IActionResult Swagger()
        {
            return Redirect("~/swagger");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Agora, ao executar a solution (F5), as páginas do swagger serão carregadas automaticamente para todas as APIs, sem a necessidade de acionar a rota \swagger.

6. Melhorando os exemplos de dados gerados do Swagger

Ao examinarmos a documentação de cada endpoint, podemos verificar no campo “Example Value”, um exemplo de dados que poderiam ser submetidos para a API, porém os valor atribuídos à cada propriedade, não são sempre sugestivos para o usuário.

Veja por exemplo o JSON atribuído ao método POST da API que gerencia as notas fiscais:

Podemos melhorar estes exemplos, com a anotação JsonSchemaExtensionData da classe NJsonSchema.Annotations.JsonSchemaExtensionDataAttribute.

Vamos alterar as nossas classes, incluindo exemplos de dados mais eficientes, incluindo também a tag com um texto explicativo para o usuário (internos ou externos) de nossas APIs.

Faça as seguintes alterações nos respectivos arquivos:

…Api.Erp\Api.Erp.Shared\ViewModels\ClienteVM.cs

using NJsonSchema.Annotations;
using System;

namespace Api.Erp.Shared.ViewModels
{
    /// <summary>
    /// Informações para o cadastro do cliente
    /// </summary>
    public class ClienteVM
    {
        /// <summary>
        /// Id do cliente no Banco de Dados
        /// </summary>
        [JsonSchemaExtensionData("example", "15")]
        public string id { get; set; }

        /// <summary>
        /// Nome do cliente
        /// </summary>
        [JsonSchemaExtensionData("example", "Nome do cliente")]
        public string nome { get; set; }

        /// <summary>
        /// Email do cliente
        /// </summary>
        [JsonSchemaExtensionData("example", "emaildocliente@provedor.com.br")]
        public string email { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

…Api.Erp\Api.Erp.Shared\ViewModels\NotaFiscalVmInput.cs

using NJsonSchema.Annotations;

namespace Api.Erp.Shared.ViewModels
{    
    /// <summary>
    /// Informações para geração de uma nova nota fiscal
    /// </summary>
    public class NotaFiscalVmInput
    {
        /// <summary>
        /// Id na Nota Fiscal no Banco de Dados
        /// </summary>
        [JsonSchemaExtensionData("example", "31")]
        public string id { get; set; }

        /// <summary>
        /// Id da venda que será a origem da Nota Fiscal
        /// </summary>
        [JsonSchemaExtensionData("example", "2")]
        public string idVenda { get; set; }

        /// <summary>
        /// Valor total do ICMS. Formato 00000.00
        /// </summary>
        [JsonSchemaExtensionData("example", 1500.90)]
        public decimal valorTotalICMS { get; set; }

        /// <summary>
        /// Valor total da nota fiscal
        /// </summary>
        [JsonSchemaExtensionData("example", 1500.00)]
        public decimal valorTotalNotaFiscal { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

…Api.Erp\Api.Erp.Shared\ViewModels\NotaFiscalVmOutput.cs

namespace Api.Erp.Shared.ViewModels
{
    /// <summary>
    /// Informações da nota fiscal
    /// </summary>
    public class NotaFiscalVmOutput
    {
        /// <summary>
        /// Id na Nota Fiscal no Banco de Dados
        /// </summary>
        public string id { get; set; }

        /// <summary>
        /// Referência para a venda que originou a nota fiscal
        /// </summary>
        public VendaVmOutput Venda { get; set; }

        /// <summary>
        /// Valor total do ICMS
        /// </summary>
        public decimal valorTotalICMS { get; set; }

        /// <summary>
        /// Valor total da nota fiscal
        /// </summary>
        public decimal valorTotalNotaFiscal { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

…Api.Erp\Api.Erp.Shared\ViewModels\VendaVmInput.cs

using NJsonSchema.Annotations;

namespace Api.Erp.Shared.ViewModels
{
    /// <summary>
    /// Informações para geração de uma nova venda
    /// </summary>
    public class VendaVmInput
    {
        /// <summary>
        /// Id na venda no banco de dados
        /// </summary>
        [JsonSchemaExtensionData("example", "3")]
        public string id { get; set; }

        /// <summary>
        /// Id do cliente da venda
        /// </summary>
        [JsonSchemaExtensionData("example", "10")]
        public string idCliente { get; set; }

        /// <summary>
        /// Valor total da venda. Formato 00000.00
        /// </summary>
        [JsonSchemaExtensionData("example", 890.50)]
        public decimal valorTotal { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

…Api.Erp\Api.Erp.Shared\ViewModels\VendaVmOutput.cs

namespace Api.Erp.Shared.ViewModels
{
    /// <summary>
    /// Informações da venda
    /// </summary>
    public class VendaVmOutput
    {
        /// <summary>
        /// Id na venda no banco de dados
        /// </summary>
        public string id { get; set; }

        /// <summary>
        /// Referência para o cliente, para o qual foi executada a venda
        /// </summary>
        public ClienteVM cliente { get; set; }

        /// <summary>
        /// Valor total da venda
        /// </summary>
        public decimal valorTotal { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

As informações incluídas na anotação [JsonSchemaExtensionData("example", "value")] serão utilizadas para a geração dos exemplos de dados na interface do Swagger, porém, para aproveitar as informações incluídas na tag summary, precisamos configurar os nossos projetos para gerar o arquivo XML da documentação (XML Documentation file). Uma maneira muito simples de fazer isso, é editar os arquivos de nossos projetos (extensão .csproj) e incluir as instruções:

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
       <DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
       <DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
  </PropertyGroup>
Enter fullscreen mode Exit fullscreen mode

Portanto, dê um clique duplo em cada um dos arquivos de cada projeto (.csproj) e deixe-os da seguinte forma:

…Api.Erp\Api.Erp.Clientes\Api.Erp.Clientes.csproj

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

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
    <DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\Api.Erp.Shared\Api.Erp.Shared.csproj" />
  </ItemGroup>

</Project>
Enter fullscreen mode Exit fullscreen mode

…Api.Erp\Api.Erp.Comercial\Api.Erp.Comercial.csproj

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

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
    <DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\Api.Erp.Shared\Api.Erp.Shared.csproj" />
  </ItemGroup>

</Project>
Enter fullscreen mode Exit fullscreen mode

…Api.Erp\Api.Erp.Fiscal\Api.Erp.Fiscal.csproj

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

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
    <DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\Api.Erp.Shared\Api.Erp.Shared.csproj" />
  </ItemGroup>

</Project>
Enter fullscreen mode Exit fullscreen mode

…Api.Erp\Api.Erp.Shared\Api.Erp.Shared.csproj

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

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
    <DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="NSwag.AspNetCore" Version="13.3.0" />
  </ItemGroup>

</Project>
Enter fullscreen mode Exit fullscreen mode

Ao executarmos nossas APIs, agora teremos uma riqueza muito maior de detalhes, com instruções precisas para os usuários.

Veja novamente o exemplo do endpoit utilizado para criar uma nova nota fiscal, após as modificações:

Com isto concluímos a parte 2 desta série de artigos.

Obtenha o código completo da solução até o presente momento neste repositório do GitHub.

Links para a série completa:

Criando Web APIs modernas, autônomas e rastreáveis com .Net Core utilizando arquitetura de microsserviços – (Parte 1) Construindo o projeto

Criando Web APIs modernas, autônomas e rastreáveis com .Net Core utilizando arquitetura de microsserviços – (Parte 2) Incluindo documentação com o Swagger

Criando Web APIs modernas, autônomas e rastreáveis com .Net Core utilizando arquitetura de microsserviços (Parte 3) – Implementando rastreamento distribuído

AWS GenAI LIVE image

Real challenges. Real solutions. Real talk.

From technical discussions to philosophical debates, AWS and AWS Partners examine the impact and evolution of gen AI.

Learn more

Top comments (0)

Qodo Takeover

Introducing Qodo Gen 1.0: Transform Your Workflow with Agentic AI

Rather than just generating snippets, our agents understand your entire project context, can make decisions, use tools, and carry out tasks autonomously.

Read full post

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay