- Visual Studio Code
- Banco de Dados funcionando - DDLs, DMLs e DQLs
- .NET Core SDK 3.0
Criamos nosso projeto de API com:
dotnet new webapi
Instalar o gerenciador de pacotes EF na máquina:
dotnet tool install --global dotnet-ef
Baixar Pacote SQL Server:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
Baixar pacote de escrita de códigos do EF:
dotnet add package Microsoft.EntityFrameworkCore.Design
Dar um restore na aplicação para ler e aplicar os pacotes instalados:
dotnet restore
Testar se o EF está ok
dotnet ef
Criar os Models à partir da sua base de Dados :point_right: -o = criar o diretorio caso não exista :point_right: -d = Incluir as DataNotations do banco
dotnet ef dbcontext scaffold "Server=DESKTOP-XVGT587\SQLEXPRESS;Database=Gufos;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -o Models -d
Apagamos o controller que já vem com a base...
Criamos nosso primeiro Controller: CategoriaController
Herdamos nosso novo controller de ControllerBase
Definimos a "rota" da API logo em cima do nome da classe, utilizando:
[Route("api/[controller]")]
Logo abaixo dizemos que é um controller de API, utilizando:
[ApiController]
Damos CTRL + . para incluir:
using Microsoft.AspNetCore.Mvc;
Instanciamos nosso contexto da nossa Base de Dados:
GufosContext _contexto = new GufosContext();
Damos CTRL + . para incluir nossos models:
using GUFOS_BackEnd.Models;
Criamos nosso método GET:
// GET: api/Categoria/
[HttpGet]
public async Task<ActionResult<List<Categoria>>> Get()
{
var categorias = await _context.Categoria.ToListAsync();
if (categorias == null)
{
return NotFound();
}
return categorias;
}
Importamos com CTRL + . as dependências:
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
using System.Collections.Generic;
Testamos o método GET de nosso controller no Postman:
dotnet run
https://localhost:5001/api/categoria
Deve ser retornado:
[
{
"categoriaId": 1,
"titulo": "Desenvolvimento",
"evento": []
},
{
"categoriaId": 2,
"titulo": "HTML + CSS",
"evento": []
},
{
"categoriaId": 3,
"titulo": "Marketing",
"evento": []
}
]
Criamos nossa sobrecarga de método GET, desta vez passando como parâmetro o ID:
// GET: api/Categoria/5
[HttpGet("{id}")]
public async Task<ActionResult<Categoria>> Get(int id)
{
var categoria = await _context.Categoria.FindAsync(id);
if (categoria == null)
{
return NotFound();
}
return categoria;
}
Testamos no Postman: https://localhost:5001/api/categoria/1
Criamos nosso método POST para inserir uma nova categoria:
// POST: api/Categoria/
[HttpPost]
public async Task<ActionResult<Categoria>> Post(Categoria categoria)
{
try
{
await _context.AddAsync(categoria);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
throw;
}
return categoria;
}
Testamos no Postman, passando em RAW , do tipo JSON:
{
"titulo": "Teste"
}
Criamos nosso método PUT para atualizar os dados:
// PUT: api/Categoria/5
[HttpPut("{id}")]
public async Task<IActionResult> Put(long id, Categoria categoria)
{
if (id != categoria.CategoriaId)
{
return BadRequest();
}
_context.Entry(categoria).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
var categoria_valido = await _context.Categoria.FindAsync(id);
if (categoria_valido == null)
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
Testamos no Postman, no método PUT, pela URL https://localhost:5001/api/categoria/4 passando:
{
"categoriaId": 4,
"titulo": "Design Gráfico"
}
Por último, incluímos nosso método DELETE , para excluir uma determinada Categoria:
// DELETE: api/Categoria/5
[HttpDelete("{id}")]
public async Task<ActionResult<Categoria>> Delete(int id)
{
var categoria = await _context.Categoria.FindAsync(id);
if (categoria == null)
{
return NotFound();
}
_context.Categoria.Remove(categoria);
await _context.SaveChangesAsync();
return categoria;
}
Testamos pelo Postman, pelo mérodo DELETE, e com a URL: https://localhost:5001/api/categoria/4
Deve-se retornar o objeto excluído:
{
"categoriaId": 4,
"titulo": "Design Gráfico",
"evento": []
}
Copiar ControllerCategoria e alterar com CTRL + F
Testar os métodos REST
Copiar ControllerCategoria e alterar com CTRL + F
Testar os métodos REST
Notamos que no método GET não retorna a árvore de objetos Categoria e Localizacao
Para incluirmos é necessário adicionar em nosso projeto o seguinte pacote:
dotnet add package Microsoft.AspNetCore.Mvc.NewtonsoftJson
Depois em nossa Startup.cs, dentro de ConfigureServices, no lugar de services.AddControllers() :
services.AddControllersWithViews().AddNewtonsoftJson(opt => opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);
Damos CTRL + . para incluir a dependência:
using Newtonsoft.Json;
Após isso precisamos mudar nosso controller para receber os atributos, no método GET ficará assim:
var eventos = await _context.Evento.Include(c => c.Categoria).Include(l => l.Localizacao).ToListAsync();
No método GET com parâmetro ficará assim:
var evento = await _context.Evento.Include(c => c.Categoria).Include(l => l.Localizacao).FirstOrDefaultAsync(e => e.EventoId == id);
Adicionar os Controllers restantes
Instalar o Swagger:
dotnet add Gufos_BackEnd.csproj package Swashbuckle.AspNetCore -v 5.0.0-rc4
Registramos o gerador do Swagger dentro de ConfigureServices, definindo 1 ou mais documentos do Swagger:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" });
// Mostrar o caminho dos comentários dos métodos Swagger JSON and UI.
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
Colocar na Startup com CTRL + .
using Microsoft.OpenApi.Models;
using System.Reflection;
using System.IO;
Colocar dentro do csproj para gerar a documentação com base nos comentários dos métodos:
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
Em Startup.cs , no método Configure, habilite o middleware para atender ao documento JSON gerado e à interface do usuário do Swagger:
// Habilitamos efetivamente o Swagger em nossa aplicação.
app.UseSwagger();
// Especificamos o endpoint da documentação
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "API V1");
});
Rodar a aplicação e testar em: https://localhost:5001/swagger/
Instalar pacote JWT
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer --version 3.0.0
Adicionar a configuração do nosso Serviço de autenticação:
// JWT
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
Importar com CTRL + . as dependências:
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using System.Text;
Adicionamos em nosso appsettings.json :
{
"Jwt": {
"Key": "GufosSecretKey",
"Issuer": "gufos.com"
},
}
Em Startup.cs , no método Configure , usamos efetivamente a autenticação:
app.UseAuthentication();
Criamos o Controller LoginController e herdamos da ControllerBase
Colocamos a rota da API e dizemos que é um controller de API :
[Route("api/[controller]")]
[ApiController]
public class LoginController : ControllerBase
{
}
Criamos nossos métodos:
// Chamamos nosso contexto do banco
GufosContext _context = new GufosContext();
// Definimos uma variável para percorrer nossos métodos com as configurações obtidas no appsettings.json
private IConfiguration _config;
// Definimos um método construtor para poder passar essas configs
public LoginController(IConfiguration config)
{
_config = config;
}
// Chamamos nosso método para validar nosso usuário da aplicação
private Usuario AuthenticateUser(Usuario login)
{
var usuario = _context.Usuario.FirstOrDefault(u => u.Email == login.Email && u.Senha == login.Senha);
if (usuario != null)
{
usuario = login;
}
return usuario;
}
// Criamos nosso método que vai gerar nosso Token
private string GenerateJSONWebToken(Usuario userInfo)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
// Definimos nossas Claims (dados da sessão) para poderem ser capturadas
// a qualquer momento enquanto o Token for ativo
var claims = new[] {
new Claim(JwtRegisteredClaimNames.NameId, userInfo.Nome),
new Claim(JwtRegisteredClaimNames.Email, userInfo.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
// Configuramos nosso Token e seu tempo de vida
var token = new JwtSecurityToken(_config["Jwt:Issuer"],
_config["Jwt:Issuer"],
claims,
expires: DateTime.Now.AddMinutes(120),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
// Usamos essa anotação para ignorar a autenticação neste método, já que é ele quem fará isso
[AllowAnonymous]
[HttpPost]
public IActionResult Login([FromBody]Usuario login)
{
IActionResult response = Unauthorized();
var user = AuthenticateUser(login);
if (user != null)
{
var tokenString = GenerateJSONWebToken(user);
response = Ok(new { token = tokenString });
}
return response;
}
Importamos as dependências:
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
using GUFOS_BackEnd.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
Testamos se está sendo gerado nosso Token pelo Postman, no método POST
Pela URL : https://localhost:5001/api/login
E com os seguintes parâmetros pela RAW :
{
"nome": "Administrador",
"email": "adm@adm.com",
"senha": "123",
}
O retorno deve ser algo do tipo:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiJQYXVsbyIsImVtYWlsIjoiYWRtQGFkbS5jb20iLCJqdGkiOiIwYjNmMGM3ZC1mMDhjLTQ4NDQtOTI1Mi04ZDI1ZTZmY2MxYmYiLCJleHAiOjE1NzExNTcyMjUsImlzcyI6Imd1Zm9zLmNvbSIsImF1ZCI6Imd1Zm9zLmNvbSJ9.bk_cvQJgVpq7TXa8Nhh1XzWAEUnTXHc2lP5vvqIVhJs"
}
Após confirmar, vamos até https://jwt.io/
Colamos nosso Token lá e em Payload devemos ter os seguintes dados:
{
"nameid": "Administrador",
"email": "adm@adm.com",
"jti": "d1e13b73-5f8f-423c-97e2-835f55bbfb0e",
"exp": 1571157573,
"iss": "gufos.com",
"aud": "gufos.com"
}
Pronto! Agora é só utilizar a anotação [Authorize] em baixo da anotação REST de cada método que desejar colocar autenticação!
No Postman devemos gerar um token pela rota de login e nos demais endpoints devemos adicionar o token gerado na aba Authorization escolhendo a opção Baerer Token
Apagamos a pasta Models e fazemos o scaffold novamente, com a nomenclatura Domains no lugar de Models
Antes disso recortamos nossos Controllers para outro diretório para não dar erro de Build
dotnet ef dbcontext scaffold "Server=DESKTOP-XVGT587\SQLEXPRESS;Database=Gufos;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -o Domains -d
Depois de gerados os domínios(models) recolocamos os controllers dentro do projeto
Substituímos todos os "Usings" de nossos Controllers que estão como "Models" para "Domains"
Criamos nosso diretório "Interfaces" Dentro deste diretório criamos nossa primeira interface: "ICategoria.cs"
Dentro de ICategoria criamos nossos "Contratos"(métodos que serão obrigatórios):
Task<List<Categoria>> Listar();
Task<Categoria> BuscarPorID(int id);
Task<Categoria> Salvar(Categoria categoria);
Task<Categoria> Alterar(Categoria categoria);
Task<Categoria> Excluir(Categoria categoria);
Criamos nosso diretório "Repositories" Dentro deste diretório criamos nosso primeiro repositório: "CategoriaRepository.cs" Herdamos esta classe de "Icategoria":
using GUFOS_BackEnd.Interfaces;
namespace GUFOS_BackEnd.Repositories
{
public class CategoriaRepository : ICategoria
{
}
}
Damos CTRL + . para implementar a interface em nosso repositório, e colocando "async" nos métodos, ficando assim:
public async Task<IActionResult> Alterar(long id, Categoria categoria)
{
throw new System.NotImplementedException();
}
public async Task<ActionResult<Categoria>> BuscarPorID()
{
throw new System.NotImplementedException();
}
public async Task<ActionResult<Categoria>> Excluir(int id)
{
throw new System.NotImplementedException();
}
public async Task<ActionResult<List<Categoria>>> Listar()
{
throw new System.NotImplementedException();
}
public async Task<ActionResult<Categoria>> Salvar(Categoria categoria)
{
throw new System.NotImplementedException();
}
Dentro do método Listar() colocamos nosso contexto utilizando using (que será responsável por abrir e fechar a conexão com o banco):
using(GufosContext _context = new GufosContext()){
}
Dentro do Using, já retornamos nossa "query" :
using(GufosContext _context = new GufosContext()){
return await _context.Categoria.ToListAsync();
}
Tiramos a instância do nosso Context do Controller e substituímos pela nossa "CategoriaRepository":
//GufosContext _context = new GufosContext();
CategoriaRepository repositorio = new CategoriaRepository();
Importamos nosso diretório de repositórios:
using GUFOS_BackEnd.Repositories;
Deixamos nossas interfaces e respositorios sem o AcionResult:
public async Task<Categoria> Alterar(Categoria categoria)
{
using(GufosContext _context = new GufosContext()){
_context.Entry(categoria).State = EntityState.Modified;
await _context.SaveChangesAsync();
}
return categoria;
}
public async Task<Categoria> BuscarPorID(int id)
{
using(GufosContext _context = new GufosContext()){
return await _context.Categoria.FindAsync(id);
}
}
public async Task<Categoria> Excluir(Categoria categoria)
{
using(GufosContext _context = new GufosContext()){
_context.Categoria.Remove(categoria);
await _context.SaveChangesAsync();
return categoria;
}
}
public async Task<List<Categoria>> Listar()
{
using(GufosContext _context = new GufosContext()){
return await _context.Categoria.ToListAsync();
}
}
public async Task<Categoria> Salvar(Categoria categoria)
{
using(GufosContext _context = new GufosContext()){
await _context.AddAsync(categoria);
await _context.SaveChangesAsync();
return categoria;
}
}
}
Task<List<Categoria>> Listar();
Task<Categoria> BuscarPorID(int id);
Task<Categoria> Salvar(Categoria categoria);
Task<Categoria> Alterar(Categoria categoria);
Task<Categoria> Excluir(Categoria categoria);
Criamos um diretorio chamado ViewModel Dentro dele criamos nossa classe LoginViewModel Dentro da classe colocamos somente os atributos que serão necessários para fazer o login e suas devidas DataAnotations:
// Data Annotations
[Required]
public string Email { get; set; }
// definimos o tamanho do campo
[StringLength(255, MinimumLength = 5)]
public string Senha { get; set; }
Dentro de Login Controller deixamos de receber as informações do objeto Usuario(que precisa passar os dados que estão como obrigatórios) e passamos a receber de nosso LoginViewModel:
// Chamamos nosso método para validar nosso usuário da aplicação
private Usuario AuthenticateUser(LoginViewModel login)
{
var usuario = _context.Usuario.FirstOrDefault(u => u.Email == login.Email && u.Senha == login.Senha);
return usuario;
}
// Usamos essa anotação para ignorar a autenticação neste método, já que é ele quem fará isso
[AllowAnonymous]
[HttpPost]
public IActionResult Login([FromBody]LoginViewModel login)
{
IActionResult response = Unauthorized();
var user = AuthenticateUser(login);
if (user != null)
{
var tokenString = GenerateJSONWebToken(user);
response = Ok(new { token = tokenString });
}
return response;
}