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:
- .NET Core 2.0 + JWT: consumindo uma API que utiliza tokens
- ASP.NET Core 2.0: JWT + Identity Core na autenticação de APIs
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.