Dev (Back & Front)ARTIGO

ASP.NET Core 2.0: CRUD em API utilizando JWT, EF Core InMemory e Docker

No seguinte artigo, o autor apresenta um exemplo de implementação de um CRUD em uma API REST baseada no ASP.NET Core 2.0.

Neste artigo, trago um exemplo de implementação de um CRUD em uma API REST baseada no ASP.NET Core 2.0. O projeto em questão é muito simples, permitindo a inclusão, alteração, exclusão e consulta a dados de produtos (descrição + códigos de barras).

Além do .NET Core 2.0/ASP.NET Core 2.0, foram utilizadas na construção desta aplicação as seguintes tecnologias:

  • Entity Framework Core InMemory, que permitirá o uso das funcionalidades típicas do Entity Framework com armazenamento em memória, dispensando a necessidade de criação física de uma base de dados relacional;
  • JWT (JSON Web Tokens) com a utilização de tokens para o acesso seguro à API;
  • Docker, para a criação de uma imagem pública da API de produtos e disponibilização da mesma para uso geral.

O código do fonte do projeto está no seguinte repositório do GitHub:

Um vídeo descrevendo a estrutura utilizada para a API foi publicado também no canal Coding Night:

Nas próximas seções você poderá encontrar maiores detalhes e referências úteis sobre os recursos empregados na API REST de produtos.

Utilizando o Entity Framework Core InMemory

Para habilitar o uso da versão em memória do Entity Framework Core foi adicionado ao projeto o package Microsoft.EntityFrameworkCore.InMemory:

No caso específico da API de produtos duas entidades serão consideradas: Produto e Usuario.

A classe Produto conterá o código de barras, a descrição/nome de um item e seu respectivo preço:

namespace APIProdutos.Models
{
    public class Produto
    {
        private string _codigoBarras;
        public string CodigoBarras
        {
            get => _codigoBarras;
            set => _codigoBarras = value?.Trim().ToUpper();
        }

        private string _nome;
        public string Nome
        {
            get => _nome;
            set => _nome = value?.Trim();
        }

        public double Preco { get; set; }
    }
}

Já na classe Usuario, estão o ID do mesmo e sua chave de acesso:

A classe de contexto ApplicationDbContext referencia as entidades Produto e Usuario. Embora a API aqui descrita faça uso do Entity Framework Core InMemory, este tipo (ApplicationDbContext) em nada difere de outra implementação que empregue uma base relacional:

A classe ProdutoService fará uso de ApplicationDbContext, contendo as diversas operações de CRUD relativas ao cadastro de produtos:

O tipo ProdutoService também referencia a classe Resultado, na qual estarão informações indicando o sucesso ou não de uma operação de CRUD:

using System.Collections.Generic;

namespace APIProdutos
{
    public class Resultado
    {
        public string Acao { get; set; }

        public bool Sucesso
        {
            get { return _Inconsistencias == null || Inconsistencias.Count == 0; }
        }

        private List<string> _Inconsistencias = new List<string>();
        public List<string> Inconsistencias
        {
            get { return _Inconsistencias; }
        }
    }
}

Já a classe UsuarioService será utilizada na validação de credenciais de um usuário, além de permitir a inclusão de tais dados:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using APIProdutos.Data;
using APIProdutos.Models;

namespace APIProdutos.Business
{
    public class UsuarioService
    {
        private ApplicationDbContext _context;

        public UsuarioService(ApplicationDbContext context)
        {
            _context = context;
        }

        public Usuario Obter(string id)
        {
            return _context.Usuarios.Where(
                u => u.ID == id).FirstOrDefault();
        }

        public void Incluir(Usuario dadosUsuario)
        {
            _context.Usuarios.Add(dadosUsuario);
            _context.SaveChanges();
        }
    }

As dependências com os tipos ApplicationDbContext, ProdutoService e UsuarioService serão configuradas através do método ConfigureServices da classe Startup:

  • Quanto a ApplicationDbContext, nota-se uma chamada ao método UseInMemoryDatabase a fim de possibilitar o armazenamento de dados em memória a partir do Entity Framework Core;
  • As chamadas ao método AddScoped farão com que dependências de ProdutoService e UsuarioService utilizem uma única instância para cada um desses tipos durante o processamento de uma solicitação HTTP;
  • Já o método Configure receberá como parâmetro uma instância de UsuarioService (resolvida via injeção de dependências), com este objeto sendo empregado no cadastramento de 2 credenciais de acesso à API (via operação Incluir).
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.EntityFrameworkCore;
using APIProdutos.Data;
using APIProdutos.Models;
using APIProdutos.Business;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication.JwtBearer;

namespace APIProdutos
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseInMemoryDatabase("InMemoryDatabase"));
            services.AddScoped<ProdutoService>();
            services.AddScoped<UsuarioService>();

            // Outras implementações envolvendo o uso de JWT...
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env,
            UsuarioService usrService)
        {
            usrService.Incluir(
                new Usuario() { ID = "usuario01", ChaveAcesso = "94be650011cf412ca906fc335f615cdc" });
            usrService.Incluir(
                new Usuario() { ID = "usuario02", ChaveAcesso = "531fd5b19d58438da0fd9afface43b3c" });

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMvc();

Caso queira saber mais sobre injeção de dependências no ASP.NET Core, acesse o vídeo a seguir do Canal .NET:

Habilitando o uso de JWT

Os ajustes necessários para habilitar o uso de JWT (JSON Web Tokens) envolvem alterações na classe Startup, além da implementação de um Controller chamado LoginController. Já abordei em detalhes este processo no seguinte artigo:

Por questões de simplificação, não apresentarei tais instruções neste artigo, até porque o projeto/repositório indicado no início do artigo já contém todas as modificações esperadas para viabilizar o uso de tokens.

Os próximos artigos contêm referências adicionais sobre o uso de JWT com o .NET Core:

Implementando o Controller para CRUD

A classe ProdutosController será responsável pelas operações de CRUD envolvendo o cadastro de produtos:

  • Este tipo foi marcado com o atributo Authorize, no qual foi indicado ainda o uso de Bearer Authentication (autenticação via tokens);
  • As diversas operações de CRUD foram implementadas através das Actions Get, Post, Put e Delete, com as mesmas acionando métodos definidos na classe ProdutoService.
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using APIProdutos.Business;
using APIProdutos.Models;
using Microsoft.AspNetCore.Authorization;

namespace APIProdutos.Controllers
{
    [Authorize("Bearer")]
    [Route("api/[controller]")]
    public class ProdutosController : Controller
    {
        private ProdutoService _service;

        public ProdutosController(ProdutoService service)
        {
            _service = service;
        }

        [HttpGet]
        public IEnumerable<Produto> Get()
        {
            return _service.ListarTodos();
        }

        [HttpGet("{codigoBarras}")]
        public IActionResult Get(string codigoBarras)
        {
            var produto = _service.Obter(codigoBarras);
            if (produto != null)
                return new ObjectResult(produto);
            else
                return NotFound();
        }

        [HttpPost]
        public Resultado Post([FromBody]Produto produto)
        {
            return _service.Incluir(produto);
        }

        [HttpPut]
        public Resultado Put([FromBody]Produto produto)
        {
            return _service.Atualizar(produto);
        }

        [HttpDelete("{codigoBarras}")]
        public Resultado Delete(string codigoBarras)
        {
            return _service.Excluir(codigoBarras);
        }
    }
}

Gerando e publicando uma imagem Docker

Os procedimentos necessários para a publicação da imagem pública desta API na Docker Store/Docker Hub são os mesmos descritos no seguinte artigo:

Tal imagem poderá ser utilizada por Single-page Applications (SPAs) ou outros tipos de aplicações sem a preocupação com ferramentas necessárias para a execução da API descrita neste artigo. O único pré-requisito neste caso é a instalação prévia do Docker.

Importante ressaltar ainda que, por envolver o uso de REST, qualquer solução que suporte este padrão poderá interagir com a API, independentemente da tecnologia empregada em sua construção.

A imagem da API de produtos já se encontra no Docker Hub/Docker Store e está identificada como renatogroffe/apiprodutos:

Execução e testes da API a partir de um container Docker

A imagem renatogroffe/apiprodutos poderá ser baixada via PowerShell através do comando docker pull renatogroffe/apiprodutos:

Já para a criação de um container na porta 12345, será executado o comando:

docker run -p 12345:80 — name testeapiprodutos -d renatogroffe/apiprodutos

O comando docker ps mostrará que o container em questão já está ativo e executando na porta 12345:

As credenciais de usuários definidas via código (mais precisamente no método Configure da classe Startup) foram as seguintes:

  • ID = usuario01, ChaveAcesso = 94be650011cf412ca906fc335f615cdc
  • ID = usuario02, ChaveAcesso = 531fd5b19d58438da0fd9afface43b3c

Na imagem a seguir, é possível observar a obtenção de um token (válido por 1 hora) via Postman e empregando uma destas credenciais:

Uma requisição do tipo POST e direcionada a ProdutosController será então gerada, tendo por objetivo cadastrar um primeiro produto:

  • O token obtido no início dos testes será utilizado para o acesso à API de produtos;
  • No corpo da solicitação estarão os dados do produto a ser armazenado em memória, com a API devolvendo um conjunto de dados indicando o sucesso nesta operação.

Referências

Atua como consultor em atividades voltadas ao desenvolvimento de sistemas há mais de 15 anos. Microsoft MVP (Most Valuable Professional) e participante do programa MTAC (Multi-Plataform Technical Audience Contributor). Bacharel em Sistemas de Informação, com Especialização em Engenharia de Software e MBA em Business Intelligence. Também é palestrante e autor técnico em portais e revistas especializadas, com foco em tecnologias Microsoft e boas práticas na área de software.

Ver perfil