Executando verificação de segurança...
5

🟪 [All In One] REST API com ASP.NET Core

All in One REST API com ASP.NET

jwt_log

O ASP.NET hoje é um dos principais frameworks para criar REST API, conta com uma vasta variedade de recursos, incluindo suporte para autenticação, autorização, documentação de API com Swagger, roteamento, versionamento, além de uma grande comunidade de desenvolvedores para ajudar a solucionar problemas. Para aqueles que querem começar com o framework ou aqueles que querem rever algum conceito, este artigo vai servir todos público, este é um All in One (Tudo em Um), vou cobrir vários assuntos básicos e alguns poucos avançados de forma mais completa possível, mostrando como e porque fazer, além de explicar todos os códigos que serão mostrados aqui.

Para quem nunca usou o .NET talvez se sinta perdido, este é um artigo para quem quer começar com ASP.NET, portanto você já deve saber os conceitos básicos da plataforma-base que carrega o framework além da linguagem C#.

Para não ficar perdido ou se quiser ver apenas um conteúdo específico aqui está a tabela de conteúdo:

O que é ASP.NET

O ASP.NET é um framework para construir aplicação web e serviços na plataforma .NET usando C#, Ele totalmente gratuito e open-source e ao contrário do que muitas pessoas pensam ele é multiplataforma, funcionando no Windows, Linux e MacOS.

Ele não é usado somente para criar APIs REST, o ASP.NET é uma plataforma completa para criar suas aplicações, fornece recursos para construir desde a UI até APIs, ou seja, criar aplicações full-stack inteiras sozinhas, além de ser fácil de separa partes de sua aplicação para criar microsserviço, ele dá um dos melhores suporte para Docker que existe hoje, pois ele consegue construir sua aplicação direto em um container sem um Dockerfile.

O ASP.NET faz parte da plataforma .NET o que significa que todo ecossistema também pode ser usado para construir sua aplicação, como o Entity Framework, um ORM (Object Relational Mapper) que te ajuda a manipular banco de dados de vários provedores (neste artigo será abordado Entity Framework), SignalR para comunicação em tempo real, esta biblioteca usa o protocolo RPC (Remote Procedure Call) para atingir tal objetivo, mas esta não é a única forma de criar aplicações que se comunicam em tempo real, o ASP.NET também conta com um bom suporte a WebSockets, e para acompanhar o funcionamento de tudo isso temos o Serilog para criar logs e diagnósticos, exportando-os para diferentes fontes com sua funcionalidade de sinks que pode ser usado para escrever os logs aonde você quiser. Algumas dessas bibliotecas veremos aqui, pois são fundamentais para qualquer aplicação, mas que fique claro que não são as únicas que existem e tem várias outras que servem o mesmo propósito e outras que facilitam ainda mais seus dia a dia.

REST API

Primeiro, uma API (Application Programming Interface) pode ser dito como um conjunto de definições que usa protocolos para se comunicar entre diferentes partes que uma aplicação, como no seu nome especifica, API é uma interface, ou seja, define um contrato para comunicação, onde quem vai consumir da API também estará ciente de tais contratos para que não possa haver mal-entendido entre as partes. Por exemplo, uma API Web define interfaces para comunicação no contexto da web, onde se usa o protocolo HTTP para comunicação e as respostas são enviadas em formato JSON ou XML, sendo JSON o mais comum.

O REST (Representational State Transfer) define algumas restrições para as API Web para que os serviços sejam mais flexíveis, escaláveis e fáceis de manter, onde cada recurso é identificado por uma URI (Uniform Resource Identifier) e as operações são realizadas através dos métodos HTTP: GET, POST, PUT, PATCH e DELETE. As API que usam o REST também podem ser denominadas como RESTful.

Se quiser saber mais sobre o propósito de cada método acesse o MDN Docs.

Uma API RESTful deve respeitar as seguintes restrições:

  • Cliente-servidor: separação entre a interface do usuário e a lógica do servidor.
  • Sem estado: cada requisição deve conter todas as informações necessárias para a realizar a operação desejada, ou seja, nenhuma informação deve ser armazenada entre solicitações.
  • Cacheável: as respostas devem ser cacheáveis.
  • Interface uniforme: a API deve seguir um conjunto predefinido de recursos e verbos.
  • Sistema em camadas: a API pode ser escalada através da adição de camadas intermediárias.

Se quiser saber mais sobre APIs REST acesse o artigo da RedHat.

Com o ASP.NET, é possível criar uma API seguindo essas restrições, utilizando os recursos já disponíveis no framework.

Não é o foco deste artigo mostra como que faz uma requisição HTTP, pois isso depende do seu sistema operacional já que alguns veem com diferentes ferramentas para este propósito, neste artigo estarei utilizando o Postman para que não precise criar um cliente em uma outra linguagem para consumir da API, mas fica a seu critério usar a ferramenta do seu sistema, criar um programa ou usar qualquer outro cliente para consumir da API que vamos criar.

Criar um projeto

A partir daqui será abordado o conteúdo sobre a criação da API em si, mas antes de criar os projetos vamos definir o que será feito.

O que será feito?

Será criado uma Minimal API completa para cadastro de usuários e cliente usando os conceitos listados na tabela de conteúdo. Os usuários são os que usarão o sistema, eles podem:

  • Cadastrar clientes;
  • Ver as informações dos clientes cadastrados pelo usuário que está requisitando;
  • Remover cliente;
  • Atualizar informações dos clientes;

Tudo só será possível se caso o usuário esteja autenticado. Para exclarecer, mesmo que o cliente não tenha sido cadastrado pelo usuário que está requisitando a mudança ele ainda poderá ser removido e atualizado, mas não deverar ver as informações. Um sistema bem simples que será suficiente para o propósito do artigo.

Minimal API é um conceito introduzido no .NET 6 onde define apenas o essencial para que a API funcione e possa receber requisições, eliminando o código gigantesco que precisava antigamente para que a API funcionasse. As Minimal APIs se assemelhar com a forma de como o Express no NodeJS cria APIs, esta forma deixa o código muito mais limpo e simples, mas mantendo as funcionalidades que tornam o ASP.NET tão completo.

Esclarecimentos

Os conteúdos não serão tratados separadamente com explicações muito teoricas, todo os conceitos que vamos usar aqui serão explicados e aplicados, caso queira estudar a parte mais teórica dos assuntos veja as referências do artigo onde estará todas as fontes usadas, explicadas de forma mais aprofundada.

Estarei usando o .NET 7 para este projeto juntamente com o Rider como IDE, mas pode usar qualquer uma outra, já que irei usar o terminal tanto para cria a aplicação quanto para rodar a API.

Para criar o projeto é necessário que você já tenha o SDK do .NET instalado, não é necessário instalar nada relacionado ao ASP.NET em si, pois ele já vem com o SDK.

Para criar um projeto, no seu terminal execute:

dotnet new web -o AllInOneAspNet
  • dotnet - Define o programa que será usado.
  • new - Parâmetro para criar uma nova aplicação.
  • web - O template que será usado, neste caso será o template para criar uma aplicação web vazia.
  • -o AllInOneAspNet - output dos arquivos do projeto/onde o projeto será criado, neste casso será criado na pasta AllInOneAspNet.

A seguinte estrutura de pastas será criada:

.
├── obj
├── Properties
├── AllInOneAspNet.csproj
├── appsettings.Development.json
├── appsettings.json
└── Program.cs

O arquivo mais importante é o Program.cs onde contém código principal da nossa aplicação. Conforme formos prosseguindo no artigo será falado mais sobre os outros arquivos e diretórios.

Baseado no template que escolhemos para criar o projeto, o Program.cs deve estar inicialmente dessa forma:

var builder = WebApplication.CreateBuilder(args); // 1
var app = builder.Build(); // 2

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

app.Run(); // 4

./Program.cs

  1. Cria um construtor para os serviços da aplicação
  2. Cria uma aplicação que será usada para configurar as rotas HTTP usando o builder.
  3. Mapeia a rota / com o método GET que apenas retorna “Hello World”.
  4. Executa a API.

Para executar a API devemos executar o comando no diretório raiz do projeto:

dotnet watch
  • dotnet - Define o programa que será usado.
  • watch - Inicia a aplicação e observa por modificações.

Os logs exibidos devem ser parecidos com esse:

dotnet watch 🔥 Hot reload enabled. For a list of supported edits, see https://aka.ms/dotnet/hot-reload.
  💡 Press "Ctrl + R" to restart.
dotnet watch 🔧 Building...
  Determinando os projetos a serem restaurados...
  Todos os projetos estão atualizados para restauração.
  AllInOneAspNet -> ...\AllInOneAspNet\bin\Debug\net7.0\AllInOneAspNet.dll
dotnet watch 🚀 Started
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5111
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: ...\AllInOneAspNet

Provavelmente uma página foi aberta no seu navegador padrão na rota / da aplicação. Note que no meu caso, a aplicação está rodando em http://localhost:5111 mas a sua pode estar sendo executada em outra URI, portanto, verifique os logs.

Caso não queira que execute o navegador toda vez que executar a API, vá em ./Properties/launchSettings.json que é onde estão as configurações de execução do projeto, incluindo os perfis de execução, e altere a propriedade launchBrowser no perfil que estiver executando.

"profiles": {
    "http": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "applicationUrl": "http://localhost:5111",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
...

./Properties/launchSettings.json

Vamos fazer uma requisição GET para o endpoint /, isso deve retornar o “Hello World” como resposta:

Hello World

Criar as primeiras rotas

Vamos começar a criar nossas primeiras rotas mantendo as restrições do REST. Primeiro vamos definir os endpoints baseado no proposito do sistema:

EndpointDescriçãoCorpo da requisiçãoCabeçalhoReposta esperadaPossíveis códigos de resposta
POST user/signinCadastrar novo usuárioUserSigninRequestModel objetoNãoJWT201; 400; 409
POST user/loginAutentica um usuárioUserLoginRequestModel objetoNãoJWT200; 404; 400
GET client/Resgatar todos os clientes cadastrados por um usuário específicoNãoJWT de autenticação do usuárioLista de cliente cadastrados pelo usuário.200; 404
POST client/Cadastrar um novo clienteClientRegisterRequestModel objetoJWT de autenticação do usuárioInformações do cliente cadastrado201; 400; 404
PUT client/{id:int}Atualizar cliente com base no IDClientUpdateRequestModel objetoJWT de autenticação do usuárioID do cliente afetado200; 400; 404;
DELETE cliente/{id:int}Deletar cliente com base no IDNãoJWT de autenticação do usuárioID do cleinte afetado200; 404

Lembre-se que todas as respostas de todos os endpoints também retornaram um código de status. A autenticação e autorização com JWT será implementada depois, devemos primeiro mapear os endpoints, criar os modelos, controladores, etc.

Mapear um endpoint pode ser feito da seguinte maneira:

//...
app.MapPost("user/signin", (HttpContext context) =>
{
    return Results.Ok("Cadastrar usuário");
});
app.MapPost("user/login", (HttpContext context) =>
{
    return Results.Ok("Logar usuário");
});
app.MapGet("client", (HttpContext context) =>
{
    return Results.Ok("Buscar cliente");
});
app.MapPut("client/{id:int}", (HttpContext context, int id) =>
{
    return Results.Ok($"Atualizar cliente com ID {id}");
});
//...
app.Run();

./Program.cs; Exemplo

Os endpoints são sempre mapeados em app com os métodos Map e o método HTTP que será usado, onde neste exemplo usamos:

  • MapPost
  • MapGet
  • MapPut

Não vamos usar todos os métodos HTTP que existem, pois simplesmente não precisamos, mas caso queira saber qual quais são possíveis mapear com ASP.NET veja a referência para EndpointRouteBuilderExtensions. Note que na função anônima que estamos passando como parâmetro dos métodos tem um parâmetro HttpContext que será usado futuramente para recuperar as Claims do JWT, mas pode ser usada para recuperar qualquer outra informação sobre a requisição.

Note que ao invés de retornar a string crua no endpoint estamos usando Results para retornar algum resultado, esta classe é muito útil para retornar objetos com algum código HTTP específico.

Esta forma de mapear endpoints não é a única que temos e não é a que vamos usar, pois note que alguns endpoints possuem parte de sua rota em comum como user/signin e user/login que possuem user/ em comum, portanto vamos mapear nossas rotas usando MapGroup, que agrupa nossos endpoints com rotas em comum, que é algo perfeito para nosso caso.

Para mapear usando MapGroup podemos fazer da seguinte forma:

public static class UserEndpoints
{
    public static RouteGroupBuilder MapUserEndpoints(this RouteGroupBuilder group)
    {
        group.MapPost("signin", (HttpContext context) => 
						Results.Created("user/signin", null));
        group.MapPost("login", (HttpContext context) => 
            Results.Ok("Logar usuario"));

        return group;
    }
}

./Endpoints/UserEndpoints.cs

public static class ClientEndpoints
{
    public static RouteGroupBuilder MapClientEndpoints(this RouteGroupBuilder group)
    {
				group.MapGet("/", (HttpContext context) => 
            Results.Ok("Resgatar clientes"));
        group.MapPost("/", (HttpContext context) => 
            Results.Created("client/", null));
        group.MapPut("{id:int}", (HttpContext context, int id) => 
            Results.Ok($"Atualizar cliente com ID {id}"));
        group.MapDelete("{id:int}", (HttpContext context, int id) => 
            Results.Ok($"Deletar cliente com ID {id}"));
        
        return group;
    }
}

./Endpoints/UserEndpoints.cs

Primeiro criamos duas classes estáticas que terá um método de extensão para RouteGroupBuilder onde nesses métodos é que vamos mapear os endpoints em si, mas perceba que em UserEndpoints e ClientEndpoints estão faltando user/ e client/ respectivamente nas suas rotas, isso é porque vamos especificá-los agora que criarmos os grupos e usarmos os métodos de extensão para mapear os endpoints que definimos nas classes:

RouteGroupBuilder userGroup = app.MapGroup("user");
userGroup.MapUserEndpoints();

RouteGroupBuilder clientGroup = app.MapGroup("client");
clientGroup.MapClientEndpoints();

./Program.cs

A parte em comum que as rotas devem ser passadas por parâmetro para o método MapGroup e usando o método de extensão em userGroup e clientGroup para mapeá-los.

Para ter certeza está tudo funcionando, vamos fazer uma requisição para um endpoint de cada grupo:

POST http://localhost:5111/user/login:

"Logar usuario"

Status: 200 OK

DELETE http://localhost:5111/client/1:

"Deletar cliente com ID 1"

Status: 200 OK

Portanto nossa API está retornando os resultados que deveriam. Por hora não vamos mais mexer no nosso endpoints, vamos agora criar os controladores e modelos que usaremos na aplicação.

Continuação

Este projeto vai estar open-source no GitHub para quem quiser se localizar melhor pelas pastas ou arquivos, ver o resultado final etc. Junto com ele, no README, estará uma cópia deste artigo, sendo possível contribuir tanto para a API quanto para o artigo em si (caso tenha alguma sugestão ou encontrado algum problema).

O TabNews não permite posts com mais de 20000 caracteres, então para ver a continuação acesse o repositório no GitHub.

Espero realmente que todos tenham conseguido entender ou até aprendido algo novo, caso tenha alguma dúvida comentem aqui mesmo ou na aba de Discussão no GitHub.

GitHubDiscussions

Obrigado a todos que leram até aqui 💖.

Github-sponsors

Keep in touch:

LinkedIn GitHub

Peace✌️

Carregando publicação patrocinada...
1

Estou estudando .net por causa do trabalho, mas sei lá, parece muito burocrático em relação a um NodeJS da vida ... mas irei ler este post com calma novamente depois, quem sabe me faz mudar de ideia.