ASP.NET Core 3.1 - CRUD : Web API com EF Core e Tokens JWT


Hoje teremos um roteiro básico para criar Web APIs para realizar o CRUD com ASP .NET Core 3.1 e EF Core usando Tokens JWT.

Hoje temos um roteiro básico que explica o desenvolvimento de APIs REST usando a ASP.NET Core 3.1, fazendo a conexão com banco de dados existentes via Entity Framework, e criando um token JWT para proteção da API.

Vamos criar uma aplicação ASP.NET Core Web API bem simples para realizar o CRUD básico usando o SQL Server e as tabelas Produtos e Usuarios que iremos criar. Portanto vamos usar uma abordagem DataBaseFirst para variar um pouco.

Os recursos usados foram:

A seguir o mapa do nosso roteiro:

  1. O que é uma API
  2. O que é um token JWT
  3. Criar uma Web API REST com ASP.NET Core
  4. Instalar os pacotes NuGet
  5. Gerar as entidades e o contexto a partir do banco de dados
  6. Criar a API REST
  7. Executar e Testar a API usando o Postman
  8. Criar um token JWT
  9. Fazer a segundaça dos endpoints da API

Agora ao trabalho...

1- O que é uma API Rest ?

Devido ao crescente número nas diferentes variedades de clientes (aplicativos móveis, SPAs baseados em navegador, aplicativos desktop, aplicativos IOT etc.), precisamos transferir dados  dos servidores para os clientes de uma forma melhor e que seja independente da tecnologia e dos servidores.

As APIs REST resolvem esse problema de uma forma simples e direta.

REST significa - Representational State Transfer - ou Transferência de Estado Representacional e as APIs REST podem ser baseadas no protocolo HTTP (mas isso não é obrigatório) e fornecem aos aplicativos a capacidade de se comunicar usando o formato JSON, sendo executadas em servidores web.

O REST não é uma linguagem nem um framework, é um estilo de arquitetura para fornecer padrões entre sistemas de computadores na Web, com o objetivo de facilitar a comunicação entre os sistemas.

Nota: O termo RESTful é usado para indicar capacidade de determinado sistema aplicar os princípios de REST.

O estilo arquitetural REST que vamos considerar é representado pelas seguintes entidades:

Resource  : Os recursos são entidades identificáveis de forma única ​​(por exemplo: dados de um banco de dados, imagens ou qualquer dado). Qualquer informação que pode ser nomeada pode ser um recurso.

Endpoint : É um recurso pode ser acessado através de um identificador de URL;

Resource Method : Embora não seja obrigatório, vamos considerar o método HTTP como sendo o tipo de solicitação que um cliente envia para um servidor web. Os principais métodos HTTP usados nas APIs REST criadas na plataforma .NET são:  GET, POST PUT e DELETE. (Lembre-se que REST não é HTTP)

Header HTTP: Um cabeçalho HTTP é um par de chave-valor usado para compartilhar informações adicionais entre um cliente e servidor, como:

2- O que é um Token JWT

Vejamos agora o que é um JWT bearer token, que vamos usar para proteger a nossa WEB API REST.

JWT significa JSON Web Token e, é um padrão aberto que define uma maneira melhor de transferir dados com segurança entre duas entidades (cliente e servidor).

Um token JWT é assinado digitalmente usando uma chave secreta por um provedor de token ou servidor de autenticação. Um JWT ajuda o servidor de recursos a verificar os dados do token usando a mesma chave secreta, para que você possa confiar nos dados.

O JWT consiste nas três partes a seguir:

  1. Header:  São dados codificados do tipo token e o algoritmo usado para assinar os dados;
  2. PayLoad: São dados codificados de claims ou reivindicações destinadas a compartilhar;
  3. Signature: É criada por assinatura (cabeçalho codificado + carga útil codificada) usando uma chave secreta;

O token JWT final será assim: Header.PayLoad.Signature

Exemplo de token codificado:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4g
RG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Existe um fluxo para criação do token JWT que veremos mais adiante.

3 - Criar uma Web API REST com ASP .NET Core 3.1

Abra o Visual Studio 2019 Community versão 16.5 ou superior e faça o seguinte:

Etapa 1: Vá para File->New Project (ou estando na Start Window clique em New Project);

Etapa 2: Selecione o template ASP .NET Core Web Application e clique em Next;

Etapa 4: Digite o nome do projeto - InventarioNet  -  e sua localização e clique em Create;

Etapa 5: Selecione o template .NET Core, ASP.NET Core 3.1 e API (destacado a seguir);

Ao clicar no botão Create o projeto será criado com a estrutura abaixo exibida na janela Solution Explorer:

Vamos remover a API WeatherForecastController e a respectiva classe WeatherForecastcs  que são criados por padrão.

4. Instalar os pacotes Nuget

Agora vamos adicionar os pacotes Nuget ao projeto e podemos fazer isso via menu Tools-> Manage Nuget Packages for Solution ou via menu Tools-> Package Manager Console.

Ao instalar você deve marcar todos os projetos e sempre instalar as últimas versões estáveis de cada pacote.

Vamos usar a última opção e na janela de comandos digitar : Package-Install<nome-pacote> -Version xxx

1- Install-package Microsoft.VisualStudio.Web.CodeGeneration.Design -Version 3.1.2

Este pacote ajuda a gerar controladores e views e contém o comando dotnet-aspnet-codegenerator.

2- Install-Package Microsoft.EntityFrameworkCore.Tools -Version 3.1.4

Este pacote ajuda a criar contexto de banco de dados e classes de modelo a partir do banco de dados.

3- Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 3.1.4

Este pacote fornece o provedor de banco de dados permite que o Entity Framework Core trabalhe com o SQL Server.

4- Install-Package Microsoft.AspNetCore.Authentication.JwtBearer -Version 3.1.4

Este pacote fornece suporte para criar e validar um token JWT.

5- Install-Package System.IdentityModel.Tokens.Jwt -Version 6.5.1

Este é o middleware que permite que um aplicativo ASP.NET Core receber um token bearer no pipeline do request.

Após concluir a instalação destes pacotes o seu projeto não pode apresentar nenhum erro ou conflito entre pacotes. O arquivo de projeto InventarioNet.csproj deve estar assim:



5. Gerar as entidades e o contexto a partir do banco de dados

Para variar um pouco vamos usar a abordagem DatabaseFirst e partir de um banco de dados e tabelas que já existem.

Temos um banco de dados chamado InventarioDB no SQL Server e as seguintes tabelas:

1- Produtos - Script SQL

USE [InventarioDB]
GO
CREATE TABLE [dbo].[Produtos](
   [ProdutoId] [int] IDENTITY(1,1) NOT NULL,
   [Nome] [nvarchar](100) NOT NULL,
   [Preco] [decimal](18, 2) NOT NULL,
   [Estoque] [int] NOT NULL,
   [Imagem] [nvarchar](250) NULL,
CONSTRAINT [PK_Produtos] PRIMARY KEY CLUSTERED(
[ProdutoId] ASC
);
 

Vou incluir 3 produtos na tabela Produtos para realizar os testes:

2- Usuarios - Script SQL

USE [InventarioDB]
GO
CREATE TABLE [dbo].[Usuarios](
   [UsuarioId] [int] IDENTITY(1,1) NOT NULL,
   [Nome] [nvarchar](100) NOT NULL,
   [Login] [nvarchar](80) NOT NULL,
   [Senha] [nvarchar](80) NOT NULL,
   [Ativo] [bit] NOT NULL,
   [Email] [nvarchar](250) NULL,
CONSTRAINT [PK_Usuarios] PRIMARY KEY CLUSTERED
(
[UsuarioId] ASC
);     
 

Vou incluir 1 usuário na tabela Usuarios para testar:

Assim vamos gerar as entidades de domínio que serão obtidas a partir destas tabelas usando o comando Scaffold DbContext para fazer a engenharia reversa.

O comando usado para gerar o modelo de entidades a partir do banco de dados é  :

dotnet ef dbcontext scaffold <string de conexão>  
   Provider -o Models -f -c arquivoContexto

Onde :

dotnet ef dbcontext - comando
<string de conexão> - a string de conexão do banco de dados usado
Provider -  o provedor do banco de dados
-o Models - a pasta de sáida das classes geradas
-f - sobrescreve um código anteriormente gerado
-c arquivoContexto - o nome do DbContext usado na aplicação

O banco de dados InventarioDB.mdf possui a seguinte string de conexão (obtida a partir do Server Explorer):

'Data Source=Macoratti;Initial Catalog=InventarioDB;Integrated Security=True'

O comando para gerar as entidades deverá ficar assim:


dotnet ef dbcontext scaffold
"Data Source=Macoratti;Initial Catalog=InventarioDB;Integrated Security=True" Microsoft.EntityFrameworkCore.SqlServer -o Models -f -c AppDbContext        
 

NotaA versão da ferramenta Scaffold deve ser a mesma que a versão do pacote Microsoft.EntityFrameworkCore.Tools. No meu ambiente tive que atualizar a versão usando o comando abaixo:

Agora podemos executar comando Scafold DbContext na janela de comandos do Prompt de Comandos:

Obs: Você deve estar na pasta do projeto.

Ao final teremos as entidades Produtos e Usuarios e o arquivo AppDbContext gerados na pasta Models:

Eu vou alterar o nome das entidades para o singular: Produto e Usuario.

Abaixo temos o código da classe de contexto AppDbContext:

using Microsoft.EntityFrameworkCore;
namespace InventarioNET.Models
{
    public partial class AppDbContext : DbContext
    {
        public AppDbContext()
        {}
        public AppDbContext(DbContextOptions<AppDbContext> options)
            : base(options)
        { }
        public virtual DbSet<Produto> Produtos { get; set; }
        public virtual DbSet<Usuario> Usuarios { get; set; }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
              optionsBuilder.UseSqlServer
                ("Data Source=Macoratti;Initial Catalog=InventarioDB;Integrated Security=True");
            }
        }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Produto>(entity =>
            {
                entity.HasKey(e => e.ProdutoId);
                entity.Property(e => e.Imagem).HasMaxLength(250);
                entity.Property(e => e.Nome)
                    .IsRequired()
                    .HasMaxLength(100);
                entity.Property(e => e.Preco).HasColumnType("decimal(18, 2)");
            });
            modelBuilder.Entity<Usuario>(entity =>
            {
                entity.HasKey(e => e.UsuarioId);
                entity.Property(e => e.Email).HasMaxLength(250);
                entity.Property(e => e.Login)
                    .IsRequired()
                    .HasMaxLength(80);
                entity.Property(e => e.Nome)
                    .IsRequired()
                    .HasMaxLength(100);
                entity.Property(e => e.Senha)
                    .IsRequired()
                    .HasMaxLength(80);
            });
            OnModelCreatingPartial(modelBuilder);
        }
        partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
    }
}

Vamos remover a string de conexão desta classe para o arquivo appsettings.json abaixo:

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=Macoratti;Initial Catalog=InventarioDB;Integrated Security=True"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

E a seguir vamos registrar o contexto no método ConfigureServices da classe Startup:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<AppDbContext>(options =>
    {
       options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
    });
    services.AddControllers();
}

Dessa forma poderemos injetar este serviço nos controladores.

Na próxima parte do artigo vamos continuar criando a nossa Web API no projeto.

"Muitas são, Senhor meu Deus, as maravilhas que tens operado para conosco, e os teus pensamentos não se podem contar diante de ti; se eu os quisera anunciar, e deles falar, são mais do que se podem contar."
Salmos 40:5

Referências:


José Carlos Macoratti