Escolar Documentos
Profissional Documentos
Cultura Documentos
Arquitetura do projeto
Para criar projetos do tipo ASP.NET Core Web App (MVC): dotnet new mvc
Para criar projetos do tipo ASP.NET Core Empty: dotnet new web
using System;
using Microsoft.AspNetCore.Mvc;
namespace asp3.Controllers
{
public class HomeController : Controller
{
public IActionResult index(){
return View();
}
}
}
assim como especificado lá, o HomeController foi criado com o método Index .
Lembrando que o HomeController deve ser herdado de Controller de
Microsoft.AspNetCore.Mvc .
No retorno de View() se não houver nenhuma especificação dentro, ele vai retornar
a View contida na pasta Views/Home um arquivo com o mesmo nome do método.
5. Finalmente, assim como citado acima, devemos criar a pasta Home e o arquivo
index.cshtml
@{
Layout = null;
}
<h2>MENU INICIAL</h2>
6. Para concluir, devemos criar o arquivo _ViewImports.cshtml dentro da pasta Views. Para
usarmos as sintax razor dentro dos arquivos cshtml, devemos especificar algumas coisas
dentro deste arquivo:
@using asp3
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Após uma requisição via Http o controller assume toda a responsabilidade de entregar a view
desejada (response).
using System;
using Microsoft.AspNetCore.Mvc;
using asp1.Models;
namespace asp1.Controllers
{
public class HomeController : Controller
{
public ViewResult Index()
{
//Não precisa especificar o index, por padrão, ele procura na pasta
View>Home um view de nome index.
return View("index");
}
}
}
index.cshtml
@{
ViewData["Title"] = "Minha página";
}
2. Content("Uma string simples!") : Retorna uma string simples
using System;
using Microsoft.AspNetCore.Mvc;
namespace Proj02_IActionResult.Controllers
{
public class HomeController : Controller
{
public IActionResult Index(){
var pessoa = new { ID = 1234, nome = "Ellison"};
return new ObjectResult(pessoa);
}
}
}
Página:
using System;
using Microsoft.AspNetCore.Mvc;
namespace Proj02_IActionResult.Controllers
{
public class HomeController : Controller
{
public IActionResult Index(){
return View();
}
Ao acessar a action:
Middlewares
Os middlewares fazem parte do projeto ASP.NET. Eles são responsáveis pela configuração de
todo o projeto. O middleware controla como nosso aplicativo responde a solicitação HTTP.
Pipeline de Middlewares
Implementar aplicações ASP.NET Core envolve a configuração via código de quais middlewares
serão utilizados pela app.
UseStaticFiles
UseMvc
UseMvcWithDefaultRoute
UseIndentify
etc.
Toda vez que usamos um middleware ele é configurado para que nosso projeto se adeque ao
padrão pedido.
Middlewares de Exceções
Esse tópico precisa ser expandido. Para completá-lo é necessário o estudo específico de cada
método e sobre as alterações das variáveis de ambiente, a ASPNETCORE_ENVIRONMENT.
{
"Mensagem": "Macoratti .net - Quase tudo de .NET",
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
Adicionamos a string "Macoratti .net - Quase tudo de .NET" com identificação Mensagem. A partir
disso, iremos no Startup.cs .
Dentro da classe Startup devemos criar uma propriedade do tipo IConfiguration , que nesse
caso demos o nome de _config . Além disso, criamos um construtor para a Startup
configurando o diretório do appsettings.json .
using System.IO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace Proj01_Configuration
{
public class Startup
{
//PROPRIEDADE CRIADA QUE IRÁ OBTER A CONFIGURAÇÃO DO JSON
public IConfiguration _config {get; set;}
public Startup(){
/*
Configuramos através da builder o diretório que contém o
appsettings.json.
Criamos o builder com o tipo CofnigurationBuilder() setando o
diretório com .SetBasePath() onde o Directory.GetCurrentDirectory() retorna o
diretório atual, depois adiciona o Json com .AddJsonFile("appsettings.json).
*/
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
});
}
}
}
Resultado:
_ViewImports
A ViewImports é usado para importar bibliotecas ou classes que são utilizadas em todas as
Views. Para usar as TagHelpers nas views, é necessário importar a:
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Nas views, temos muitas das vezes utilizar um ViewModel e declaramos o modelo da seguinte
forma:
index.cshtml
@model Projeto.Models.Modelo
Para que não precise escrever todo o caminho até o modelo em todas as views, colocamos na
ViewImports onde contém a pasta de modelos, usando na _ViewImports.cshtml :
@using Projeto.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
A partir desse momento, só precisamos usar o nome do modelo ao declara nas views. Por
exemplo:
@model Modelo
Projeto 01
Esse projeto consiste em confirmar a sua presença em um aniversário ou não. Inicialmente na
index terá um link para a página de formulario na qual há a necessidade de preencher nome,
email, telefone e se vai estar presente ou não. Ao preencher e enviar aparecerá uma página
agradecendo pelo registro e mais um link para ver a lista de convidados(ListaConvidados).
Começando pelos Models, foram criados duas classes. Uma chamada RespostaConvidado.cs :
using System;
namespace app2.Models{
public class RespostaConvidado{
public string Nome {get; set;}
public string Email {get; set;}
public string Telefone {get; set;}
public bool? Presente {get; set;}
}
}
e Repositorio.cs :
using System;
using System.Collections.Generic;
namespace app2.Models
{
public static class Repositorio{
private static List<RespostaConvidado> LISTA = new
List<RespostaConvidado>();
Onde vão ser armazenadas todas as respostas de convidados. Consiste numa classe estática
com uma lista de RespostaConvidado, um método para adicionar e outro para retornar a lista
completa.
Inicio.cshtml :
@{
ViewData["Title"] = "Inicio";
}
<h2>FESTA DE ANIVERSÁRIO</h2>
<hr/>
<p>O meu aniversário vai ser na próxima semana.</p>
<p>Agradecia que indicasse se vai estar presente ou não.</p>
<!--
A tag asp-action especifica que tem uma ação no HomeController que ao clicar
nesse link vai ser executada. Que nesse caso, é o formulário.
-->
<p>Para isso, clique <a asp-action="Formulario">AQUI</a>.</p>
Formulario.cshtml :
@{
ViewData["Title"] = "Formulário";
}
<h1>Formulário</h1>
<hr />
<div>
<div>
<label asp-for="Email">O seu email: </label>
<input asp-for="Email" />
</div>
<div>
<label asp-for="Telefone">O seu telefone: </label>
<input asp-for="Telefone" />
</div>
<div>
<label>Vou estar presente? </label>
<select asp-for="Presente">
<option value="">Escolha uma opção</option>
<option value="true">Sim</option>
<option value="false">Não</option>
</select>
</div>
<div>
<!-- Ao clicar em enviar, o mesmo método Formulario é chamado,
porém, o que tem uma tag [HttpPost]-->
<button type="submit">Enviar</button>
</div>
</form>
</div>
Assim que o Formulario armazena o dado, o HomeController retorna uma view agradecendo e
com a opção de visualizar quem vai comparecer a festa.
Obrigado.cshtml :
<!-- O modelo é passado pois quando chamada essa view em Formulario, ela passa
um RespostaConvidado por parâmetro -->
@model app2.Models.RespostaConvidado
@{
ViewData["Title"] = "Obrigado";
}
<p>
Obrigado pelo seu registro, @Model.Nome .
</p>
<div>
Clique <a asp-action="ListaConvidados">AQUI</a> para ver a lista de
convidados.
</div>
e ListaFinal.cshtml :
<!-- Nesse caso, como é uma lista. O modelo deve ser um objeto iterável, no
caso, um IEnumerable do tipo RespostaConvidado-->
@model IEnumerable<app2.Models.RespostaConvidado>
@{
ViewData["Title"] = "ListaFinal";
}
<h2>ListaFinal</h2>
<hr />
<table>
<thead>
<tr>
<th>Nome</th>
<th>Email</th>
<th>Telefone</th>
</tr>
</thead>
<tbody>
<!-- Percorrendo Model com sintax Razor -->
@foreach(RespostaConvidado item in Model){
<tr>
<td>@item.Nome</td>
<td>@item.Email</td>
<td>@item.Telefone</td>
</tr>
}
</tbody>
</table>
using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using app2.Models;
namespace app2.Controllers
{
public class HomeController : Controller{
public ViewResult Index(){
return View("inicio");
}
[HttpGet]
public ViewResult Formulario(){
return View("Formulario");
}
[HttpPost]
public ViewResult Formulario(RespostaConvidado resposta){
Repositorio.AdicionarResposta(resposta);
return View("Obrigado", resposta);
}
Podemos observar que existem dois métodos Formulário. O primeiro, onde tem a especificação
[HttpGet] é chamado no momento em que há um request da página. E tanto que ela retorna o
View Formulario.cs .
Já a que contém o [HttpPost] ele é chamado assim que pressionado o botão submit, podemos
observar que ele é diferenciado pelo argumento, que recebe um RespostaConvidado do
formulário. Ele armazena a resposta na Lista e retorna a view Obrigado.cs com o argumento
resposta, onde esse argumento é usado na página Obrigado.cs .
O método ListaConvidados é para retornar o View ListaFinal.cs . Além disso, ela passa a lista
de todos os que vão comparecer a festa fazendo um Linq.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace asp11
{
public class Startup
{
public void ConfigureServices(IServiceCollection services){
services.AddMvc();
}
app.UseStaticFiles();
app.UseMvc(route => {
route.MapRoute("default", "
{controller=home}/{action=index}/{id?}");
});
}
}
}
após isso, devemos criar a página wwwroot, e dentro dela colocar o conteúdo estático, no nosso
caso, criamos duas pastas, uma com o nome css e outra images. Dentro do css colocamos o css
do bootstrap, e dentro de images colocamos uma imagem. A estrutura ficou como a seguir:
Para podermos acessar de uma View, vamos utilizar como exemplo um index.cshtml :
<!DOCTYPE html>
<html>
<head>
<title>Index</title>
<link href="~/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>
<div class="container">
<h1>Titulo 1</h1>
<h2>Titulo 2</h2>
<h3>Titulo 3</h3>
<h4>Titulo 4</h4>
<h5>Titulo 5</h5>
<h6>Titulo 6</h6>
<img src="~/images/csharp.png"/>
</div>
</body>
</html>
Podemos acessar a pasta(no src e href), voltando a pasta origem com ~/ assim como no linux. E
depois acessamos a pasta, como se a pasta de origem fosse o wwwroot.
Para demostrar, no controller a seguir declaramos dois parâmetro, nome e idade, e vamos
passar dados via URL.
using System;
using Microsoft.AspNetCore.Mvc;
namespace Proj02_IActionResult.Controllers
{
public class HomeController : Controller
{
public IActionResult Index(){
return Content("Index");
}
if(String.IsNullOrEmpty(nome)){
nome = "Nao tem";
}
if(!idade.HasValue){
idade = -1;
}
return Content($"Nome: {nome}\nIdade: {idade}");
}
}
}
Podemos observar que a action Parametro possui dois argumentos, nome e idade, e assim
como foi dito o framework irá procurar se na requisição (URL) tem argumentos com o mesmo
nome e atribui.
Não é obrigatório os parâmetros, podemos tornar obrigatórios com o sistema de routes, que é o
conteúdo abaixo.
Sistema de Routes
Até então, estavamos usando o sistema de routeamento padrão. Iamos no Startup.cs e
configuravamos no método Configure o app.UseMvcWithDefaultRoute() .
Usamos o UseMvc() com uma função lambda, onde a função lambda tem o . MapRoute (Mapa
de roteamento), definimos default e no segundo argumento mudarmos o template, onde o
controller inicial é o InicioController.cs e a ação o método Pag1 .
Ela segue essa estrutura {}/{}/{} , onde o primeiro é o controller, o segundo o action e o
terceiro o id, que iremos ver a seguir.
Attribute Routing ID
Até agora vimos o id contido no roteamento, agora vamos entender o motivo de
funcionamento.
using System;
using Microsoft.AspNetCore.Mvc;
namespace asp8.Controllers
{
public class InicioController : Controller
{
public string Index(int id){
return "Index\nID: " + id;
}
}
}
O Attribute Routing são atributos que são passados para os métodos através do link do
navegador, vamos usar o seguinte link: https://localhost:5001/inicio/index/123456 :
Podemos observar que o 123456 foi passado para o int id por parâmetro.
using System;
using Microsoft.AspNetCore.Mvc;
namespace asp9.Controllers
{
public class HomeController : Controller
{
[Route("")]
[Route("home")]
[Route("home/index")]
[Route("home/aaa")]
public string Index(){
return "Eu sou a index.";
}
[Route("home/pag")]
public string pag12345(){
return "Eu sou a pag12345.";
}
[Route("home/pag/{id?}")]
public string pagID(int id){
return $"Eu sou a pagID: {id}";
}
[Route("home/arg/{nome?}/{sobrenome?}")]
public string pagArg(string nome, string sobrenome){
return $"Meu nome é {nome} {sobrenome}";
}
}
}
No método Index especificamos quatro route. O três primeiros é exatamente oque o index já
fazia, que era acessar a partir do https://localhost:5001 , https://localhost:5001/home e
https://localhost:5001/home/index . Porém, no quarto fizemos um quarto modo de
visualizar, que é acessando https://localhost:5001/home/aaa .
Obs: A partir do momento que você predefine um Route, a forma de acessar anterior não
funciona mais.
Nesse caso da pag123456 se tentar acessar https://localhost:5001/home/pag12345
não vai ser mais possível
Já o ultimo método, o pagArg demostra que podemos passar mais de um argumento. Além
disso, podemos melhorar especificando os limites recebidos.
Ex:
O controller a seguir não faz parte do anterior, é somente para demostrar que podemos
especificar os parâmetros passsados. No caso do idade, ele possui :range(1,55) , isso significa
que o número passado pode somente estar entre 1 e 55. Além disso, podemos colocar :int pra
dizer que deve ser do tipo inteiro.
using System;
using Microsoft.AspNetCore.Mvc;
namespace Proj03_Route.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return Content("Index");
}
[Route("home/metodo/{nome}/{idade:range(1, 55)}")]
public IActionResult Metodo(string nome, int idade){
return Content($"Nome: {nome}\nIdade: {idade}");
}
}
}
using System;
using Microsoft.AspNetCore.Mvc;
using asp1.Models;
namespace asp1.Controllers
{
public class HomeController : Controller
{
public ViewResult Index()
{
ViewBag.Nome = "João";
ViewBag.Apelido = "Ribeiro";
ViewBag.Idade = 21;
return View("Index");
}
}
}
@{
ViewData["Title"] = "Minha página";
}
<div>
<h5>A minha view</h5>
<hr/>
<p>Nome: @ViewBag.Nome @ViewBag.Apelido</p>
<p>Idade: @ViewBag.Idade anos de idade.</p>
</div>
Usando ViewData
O ViewData é usado de duas formas. Ele se parece mais com um dicionário.
HomeController.cs :
using System;
using Microsoft.AspNetCore.Mvc;
namespace asp3.Controllers
{
public class HomeController : Controller
{
[ViewData]
public double cpf{get; set;}
return View();
}
No método pag2, pode observar a primeira maneira. É basicamente um dicionário que já está
declarado, você só cria novos atributos. E para acessar da View:
pag2.cshtml
@{
Layout = null;
}
<h2>Pagina 2</h2>
<p>@ViewData["frase1"]</p>
<p>@ViewData["frase2"]</p>
<a asp-action="index">Voltar!</a>
Já o segundo método, que está no método pag3, na verdade ele é criado como um atributo. Há a
declaração da propriedade com o [ViewData]. E dentro da função foi atribuida um valor. Assim,
podendo acessar da mesma forma que a anterior:
pag2.cshtml
@{
Layout = null;
}
<h2>Pagina 3</h2>
<p>CPF: @ViewData["cpf"]</p>
<a asp-action="index">Voltar!</a>
ViewModel
O ViewModel é a forma mais correta de se passar dados para as views. A diferença dele, é que ele
realmente é passado como parâmetro no retorno das View. Consiste em passar um modelo.
Na pasta do projeto, foi criada uma pasta chamada ViewModel(Convenção) para armazenar todos
os modelos para as views. Nela, declaramos a Cliente.cs :
using System;
namespace asp4.ViewModels
{
public class Cliente
{
public string nome { get; set; }
public string apelido { get; set; }
public string telefone { get; set; }
}
}
Do HomeController.cs devemos declarar um objeto do tipo cliente e passar para a View por
parâmetro:
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using asp4.ViewModels;
namespace asp4.Controllers
{
public class HomeController : Controller
{
public IActionResult Index(){
//Criando objeto
Cliente cliente = new Cliente(){
nome = "Joao",
apelido = "Ribeiro",
telefone="12345678"
};
//Passando objeto para a view por parametro.
return View(cliente);
}
}
}
@model asp4.ViewModels.Cliente
@{
Layout = null;
}
<h1>Index</h1>
<p>Nome: @Model.nome</p>
<p>Apelido: @Model.apelido</p>
<p>Telefone: @Model.telefone</p>
Na primeira linha, deve-se especificar qual o tipo do model. A partir disso, usamos sintax razor
@Model.atributo .
Passando mais de um model para a view
Só podemos passar somente um model para a view. Como alternativa de passar mais de um
model, é necessário criar uma classe que armazene todos os tipos que você precisa passar
(chamamos de bag).
Por exemplo, vamos criar uma view que precise receber um model Humano e outro Telefone .
Para isso, iremos criar uma classe BAG com atributos Humano e Telefone e passar esse BAG
através do model.
Vamos primeiramente, criar uma ViewModels, com as classes Humano , Telefone e BAG que é
justamente para passar os valores para a View. Você pode observer que o BAG tem atributos do
mesmo tipo das classes acima:
ViewModels/Modelos.cs :
using System;
using System.Collections.Generic;
namespace asp7.ViewModels
{
public class Humano{
public string nome { get; set; }
public int idade { get; set; }
}
Controller/HomeController.cs :
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using asp7.ViewModels;
namespace asp7.Controllers{
public class HomeController : Controller{
public IActionResult Index(){
Humano humano = new Humano(){
nome = "Ellison",
idade = 21
};
Telefone telefone = new Telefone(){
ddd = 73,
telefone = "12345678"
};
return View(bag);
}
}
}
@model asp7.ViewModels.BAG
<h1>Index</h1>
<h3>Humano</h3>
<p>Nome: @Model.humano.nome</p>
<p>Idade: @Model.humano.idade</p>
<h3>Telefone</h3>
<p>(@Model.telefone.ddd) @Model.telefone.telefone</p>
Partial Views
Ou views parciais, nada mais nada menos que inserir conteúdo que está em um cshtml em outro.
Para isso, vamos criar duas views. A primeira _parte1.cshtml :
E agora, vamos criar a página que vai conter o conteúdo acima, de forma indireta. Para isso na
index.cshtml :
@{
Layout = null;
}
Resultado:
index.cshtml
@{
Layout = null;
}
<h1>Inicio</h1>
<p>Num: @ViewData["num"]</p>
@{
ViewData["num"] = 200;
}
<p>Num: @ViewData["num"]</p>
<partial name="_parte1"/>
<h2>Depois da Partial</h2>
<p>Num: @ViewData["num"]</p>
Na página inicial, mostramos na tela o @ViewData["num"] , logo após, atribuimos um novo valor
(200) e imprimimos novamente. Após isso, entramos na PartialView.
_parte1.cshtml
<h2>Dentro da _parte1</h2>
<p>Num: @ViewData["num"]</p>
@{
ViewData["num"] = 300;
}
<p>Num: @ViewData["num"]</p>
Dentro da PartialView mostramos novamente o valor de num . Depois atribuimos um novo valor
(300) e novamente mostramos na tela.
E o resultado foi:
Podemos observar que quando voltou da PartialView, o valor que foi atribuido lá não foi alterado
na View principal.
Layouts
No mundo do desenvolvimento web, fazemos o usos de páginas, onde contém o mesmo menu
de navegação, rodapé ou corpo. Para não ter a necessidade de copiar e colar código, foram
criados os layouts.
Para usarmos os Layouts, devemos criar na pasta Views uma pasta de nome Shared. Nela vai
conter os Layouts. Por convenção o layout chama-se _Layout.cshtml .
Nesse exemplo, iremos fazer 2 páginas, essas, com o mesmo Layout, mudando somente o corpo.
Para isso, devemos fazer o HomeController.cs com as duas páginas:
using System;
using Microsoft.AspNetCore.Mvc;
namespace asp12.Controllers
{
public class HomeController : Controller
{
public IActionResult Index(){
return View();
}
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
</head>
<body>
<div>
<h4>Navegação</h4>
<ul>
<li><a asp-action="index">Index</a></li>
<li><a asp-action="pag2">Pag2</a></li>
</ul>
</div>
<div>
@RenderBody()
</div>
<div>
<h5>Todos os direitos reservados .</h5>
</div>
</body>
</html>
Precisamos inicialmente passar o título e o corpo da página index e pag2 pro layout. O título
iremos passar através de @ViewBag.title e para renderizar o restante do conteúdo presente
em index.cshtml e pag2.cshtml , iremos utilizar o @RenderBody() dentro de uma div ao meio
de outras duas div, ou seja, o contéudo de cada página vai ficar entre essa div, logo, as páginas
não-layouts não precisam de corpo.
index.cshtml :
@{
Layout = "_Layout";
ViewBag.title = "Página inicial";
}
<h3>Index</h3>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer quam purus,
consectetur et pharetra eget, sagittis non enim. Mauris eget massa pharetra,
gravida augue sed, ullamcorper metus. Quisque ac tristique dolor. Suspendisse
eget ante ex. Phasellus at purus sem. Sed blandit tellus in ante euismod tempus.
Praesent rutrum sagittis nibh sit amet luctus.
</p>
pag2.cshtml :
@{
Layout = "_Layout";
ViewBag.title = "Pagina 2";
}
<h3>Pagina 2.</h3>
<p>Estamos na página 2!!!!!!!!!!!!!!!!!!</p>
Podemos observar que precisamos setar Layout o nome do layout. Que pela convenção
colocamos o nome de "_Layout" e o título da página.
index.cshtml pag2.cshtml
Podemos através desse, verificar que o Layout se repetiu nas páginas setadas.
Section
Vimos que utilizamos o @RenderBody() para renderizar o corpo da página. Porém, e se
quisermos outras partes dessa mesma página em um local diferente?! Aí vem a Section.
A section são blocos de códigos criado numa página que vai receber um Layout, e que no Layout
você pode renderizar ela onde quiser.
Na index.cshtml :
<h2>Página inicial</h2>
<p>Paragrafo 1...</p>
<p>Paragrafo 2...</p>
@section Erros{
<div style="padding:20px;margin:40px;background:yellow;border:1 px black;">
<p>Seção renderizada pelo bloco section no Layout.</p>
</div>
}
Na página ela é delimitada pelo escopo @section Nome{} e dentro da chaves todo o código que
aparecerá em um lugar específico no Layout. Agora, vamos ao _Layout.cshtml :
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
</head>
<body>
Resultado:
Ainda temos o IsSectionDefined("Nome") que retorna true se existir e false se não existir.
Veja o mesmo exemplo:
_Layout.cshtml :
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
</head>
<body>
HttpVerbs
Foi discutido no Projeto 01, o [HttpGet] e [HttpPost] . Porém, com elas você pode ditar o
caminho como no Attribute Routing. Para exemplificar, vamos criar um pequeno projeto:
Views/Inicio/index.cshtml :
@{
Layout = null;
}
<h2>Página inicial</h2>
<p>Clique <a asp-action="form">AQUI</a> para cadastrar.</p>
<h3>Formulário</h3>
Onde, também, irá observar que executa o form . Logo, no controller deve conter [HttpGet] e
[HttpPost] .
Controllers/InicioController.cs :
using System;
using Microsoft.AspNetCore.Mvc;
namespace asp10.Controllers
{
public class InicioController : Controller
{
public ViewResult Index(){
return View();
}
[HttpGet("inicio/form")]
public ViewResult FormGet(){
return View("Formulario");
}
[HttpPost("inicio/form")]
public string FormPost(){
return "Salvo com sucesso";
}
}
}
Podemos observar que o nome dos métodos são totalmente diferente dos nomes chamados nos
arquivos .cshtml . A utilização do HttpVerbs facilitou.
Além disso, observamos através do asp-route-* que iremos passar no primeiro link o atributo id
e no segundo link o atributo nome.
HomeController.cs :
using System;
using Microsoft.AspNetCore.Mvc;
namespace asp17.Controllers
{
public class HomeController : Controller
{
public IActionResult Index(){
return View();
}
[Route("home/metodo2/{nome}")]
public string Metodo2(string nome){
return "Estou no método 2 e o meu nome é: " + nome;
}
}
}
Como dito, o Metodo1 recebe um id, e o Metodo2 recebe um nome. Podemos verificar que o
Metodo dois utiliza o sistema de Routes.
Caso não houvesse o sistema de routes, teriamos como se fosse um get, da seguinte forma:
/Metodo2?nome=Ellison .
@model List<asp15.Models.Pessoa>
<h1>Index</h1>
<a asp-action="Formulario">Adicionar</a>
<table>
<thead>
<tr>
<th>Nome</th>
<th>Sobrenome</th>
<th>Propriedades</th>
</tr>
</thead>
<tbody>
@foreach(var item in Model){
<tr>
<td>@item.Nome</td>
<td>@item.Sobrenome</td>
<td>@Html.ActionLink("Excluir", "Delete", new{id = item.Id} )
</td>
</tr>
}
</tbody>
</table>
<p>Total: @Model.Count</p>
Essa página recebe um List<Pessoa> por parâmetro, e o foreach percorre a lista imprimindo
na tabela os dados organizados. Há um link Adicionar que redireciona para uma página que há
um formulário.
A cereja do bolo é o @Html.ActionLink() . Com ele, você pode atribuir um link que executa um
método e passa como parâmetro algum atributo. Nesse caso, criamos um link Excluir que
executa o método Delete e passa o argumento item.Id por parâmetro. Ou seja, assim que
clicamos em Excluir o método Delete vai ter o id do item a ser excluído.
Além dessa página, temos o formulario.cshtml que é responsável por cadastrar uma nova
Pessoa:
@model asp15.Models.Pessoa
<div>
<button type="submit">Salvar</button>
</div>
</form>
E após cadastrar, a página obrigado.cshtml retorna uma mensagem dizendo que foi
cadastrado com sucesso e dá a alternativa de voltar para a página inicial:
@model asp15.Models.Pessoa
<h1>Obrigado</h1>
<p>@Model.Nome foi adicionado!</p>
<hr/>
<a asp-action="index">Voltar</a>
Após apresentar todas as páginas, iremos apresentar a composição dos dados. Criamos um
model Pessoa.cs :
using System;
namespace asp15.Models
{
public class Pessoa
{
public int Id { get; set; }
public string Nome { get; set; }
public string Sobrenome { get; set; }
}
}
using System;
using System.Collections.Generic;
namespace asp15.Models
{
public static class BAGDATA
{
static public List<Pessoa> pessoas = new List<Pessoa>(){
new Pessoa(){ Id=0, Nome = "Ellison", Sobrenome = "William"},
new Pessoa(){ Id=1, Nome = "Rebeca", Sobrenome = "Dias"}
};
}
}
using System.Linq;
using System;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using asp15.Models;
namespace asp15.Controllers
{
public class HomeController : Controller
{
// Como na página inicial é mostrado uma tabela com todos os dados
(Pessoa), é necessário mandar por parâmetro a lista de Pessoa.
public ViewResult Index(){
return View(BAGDATA.pessoas);
}
Resultado:
Página inicial:
Adicionando uma Pessoa:
Após remover:
Pessoa.cs
namespace Projeto02.Models
{
public class Pessoa
{
public int Id { get; set; }
public string Nome { get; set; }
public string Sobrenome { get; set; }
}
}
ARMAZEN.cs
using System;
using System.Collections.Generic;
using System.Linq;
namespace Projeto02.Models
{
public static class ARMAZEN
{
private static List<Pessoa> PESSOAS = new List<Pessoa>();
No Views podemos observar que temos um _ViewStart.cshtml onde é setado um layout, que
está dentro da pasta Shared, e todas as páginas seguem esse layout.
_Layout.cshtml
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<link href="~/css/style.css" rel="stylesheet"/>
</head>
<body class="body">
<div class="head">
<a asp-controller="Home" asp-action="Index"><img src="~/img/logo.png"/>
</a>
</div>
<div class="content">
@RenderBody()
</div>
</body>
</html>
Contendo conteúdo estático dentro da pasta wwwroot .
A página inicial:
@{
ViewBag.title = "Página inicial";
}
<h2>Gestão de Pessoas</h2>
<p>Aqui você pode remover, alterar e modificar registro de pessoas.</p>
<div class="buttons">
<div class="button-list">
<a class="button-list-link" asp-controller="Home" asp-
action="Lista">Lista de pessoas</a>
</div>
<div class="button-register">
<a class="button-register-link" asp-controller="Home" asp-
action="Cadastro">Fazer Cadastro</a>
</div>
</div>
Essa página há somente dois butões um que vai para a página Home/Lista como mensionado
no asp-controller e asp-action , e outro que vai para o Home/Cadastro que é o responsável
pelo formulário de cadastro.
A página de cadastro:
@model Projeto02.Models.Pessoa
@{
ViewBag.title = "Cadastro";
}
<h2>Cadastro</h2>
<p>Aqui você pode cadastrar uma pessoa.</p>
<div>
<form asp-controller="Home" asp-action="Cadastro" method="post" class="form-
cadastro">
<table>
<tr>
<td><label asp-for="Nome">Nome: </label></td>
<td><input asp-for="Nome" /></td>
</tr>
<tr>
<td><label asp-for="Sobrenome">Sobrenome: </label></td>
<td><input asp-for="Sobrenome" /></td>
</tr>
</table>
<br/>
<button type="submit">Adicionar</button>
</form>
</div>
A página da lista
@model List<Projeto02.Models.Pessoa>
@{
ViewBag.title = "Lista de pessoas";
}
<h2>Lista de pessoas</h2>
<p>Aqui você pode remover, alterar e modificar registro de pessoas.</p>
<br/>
<div>
<div style="text-align:right;">
<a class="button-link" asp-controller="Home" asp-action="Cadastro">Fazer
Cadastro</a>
</div>
<div>
@if(@Model.Count() > 0){
<table class="table-list">
<thead>
<tr>
<th>Id</th>
<th>Nome</th>
<th>Sobrenome</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
}else{
<h2>Não há nenhum registro!</h2>
}
</div>
</div>
Que é composta por uma tabela que só é mostrada quando a lista de dados for maior que 0, ou
seja, @if(@Model.Count() > 0) . Tendo elementos, é mostrado uma tabela com nome,
sobrenome e ações como remover e editar.
A âncora possui o asp-controller e asp-action como numa ancora normal. Porém, existe o asp-
route-* onde o * pode ser um nome de um atributo recebido pelo método no HomeController ,
que nesse caso, é o id, tornando-o asp-route-id . Exemplificando melhor, no método Edit
dentro de HomeController receberá um atributo id , e dentro desse método poderemos
manipular o dado com o id dele.
Página Edit
Que ao clicar no pencil, abre um formulário com os dados já preenchidos e pode ser alterado.
@model Projeto02.Models.Pessoa
@{
ViewBag.title = "Editar";
}
<h2>Editar</h2>
<div>
<form asp-controller="Home" asp-action="Edit" method="post" class="form-
cadastro">
<table>
<tr>
<td><label asp-for="Nome">Nome: </label></td>
<td><input asp-for="Nome" /></td>
</tr>
<tr>
<td><label asp-for="Sobrenome">Sobrenome: </label></td>
<td><input asp-for="Sobrenome" /></td>
</tr>
</table>
<br/>
<button type="submit">Editar</button>
</form>
</div>
E, finalmente o HomeController.cs :
using System;
using Microsoft.AspNetCore.Mvc;
using Projeto02.Models;
namespace Projeto02.Controllers
{
public class HomeController : Controller
{
public IActionResult Index(){
return View();
}
[HttpGet]
public ViewResult Cadastro(){
return View();
}
[HttpPost]
public IActionResult Cadastro(Pessoa pessoa){
ARMAZEN.adicionarPessoa(pessoa);
return RedirectToAction("index");
}
[HttpGet]
public IActionResult Edit(int id){
return View(ARMAZEN.procurarPessoa(id));
}
[HttpPost]
public IActionResult Edit(Pessoa pessoa){
ARMAZEN.editarPessoa(pessoa);
return RedirectToAction("Lista");
}
}
}
Temos o método Cadastro tendo o [HttpGet] e [HttpPost] . A Lista que é a página que
mostra todos os dados.
Agora temos a ação Delete que remove um cadastro quando é clicado na ação de hiperlink
asp-route-* . Podemos observar que é um asp-route-id pois recebe um int id . Remove o
dado pelo id passado e redireciona pra view Lista .
E temos a Edit que é composta por [HttpGet] que recebe um int id assim como no método
Delete , retorna um model para a view edit.cshtml . E o [HttpPost] que recebe uma pessoa
pelo view e edita conforme id dentro da classe ARMAZEN.cs .
Sintax Razor
O razor trabalha junto com o html nas views .cshtml . É caracterizada pelo simbolo @. Ela incluí
dentro do html blocos de códigos de C#.
<!DOCTYPE html>
<html>
<head>
<title>Index</title>
</head>
<body>
<!-- Acessando o valor de nome com o @ -->
<p> @nome </p>
<!-- O intelicense identifica que é um email e não um sintax razor -->
<a href="mailto:joao@gmail.com">joao@gmail.com</a>
<!-- Com o @@ (um arroba seguido do outro) escreve somente um @. Assim como
ocorre no barra invertida no python (\\)
Nesse caso, oque é impresso é:
O valor da variável @nome é Ellison
-->
<p>O valor da variável @@nome é @nome</p>
<!-- Ele executa a operação matemática e mostra 30. -->
<p> @(10+20) </p>
<!-- Consegue acessar o DateTime dessa forma -->
<p> @DateTime.Now.Year </p>
<!-- Pode fazer o uso de sintaxe razor o momento que quiser -->
@{
string valor = "Um numero qualquer";
}
<p>@valor</p>
@{ valor = "Outra fase"; } <p>@valor</p>
</body>
</html>
Declarando listas
@using asp20.Classes
<!-- Bloco de código -->
@{
Pessoa eu = new Pessoa(){
nome = "Ellison",
apelido = "William"
};
List<Pessoa> lista_pessoas = new List<Pessoa>();
lista_pessoas.Add(eu);
lista_pessoas.Add(new Pessoa(){ nome = "Rebeca", apelido = "Dias"});
lista_pessoas.Add(new Pessoa(){ nome = "Rose", apelido = "Medrado"});
}
<!DOCTYPE html>
<html>
<head>
<title>Index</title>
</head>
<body>
@if(lista_pessoas.Count() > 0){
@foreach(var pessoa in lista_pessoas){
<p>@pessoa.nome @pessoa.apelido</p>
}
}else{
<p> Não há pessoas. </p>
}
</body>
</html>
Html Helpers
HTML Helper é um método que retorna uma string. Ele gera como resultado a renderização de
marcas HTML.
Por exemplo, o Html Helper @Html.TextBox("nome") renderiza em html o <input id="nome"
name="nome" type="text" value=""/> .
Os Html Helpers fortemeente tipados são criados a partir de views fortemente tipadas
baseadas nas propriedades do model.
Quando a página é um formulário de cadastro, onde não há dados, utiliza-se os não tipados. Já
quando é uma página que exibe esses dados ou edita os dados, é utilizado as fortemente
tipadas.
Para comparar a renderização, iremos ver a renderização de dois HtmlHelpers iguais, um tipado
e outro não:
Não tipado:
@Html.TextBox("nome")
<input id="nome" name="nome" type="text" value=""/>
Fortemente tipado:
Html.BeginForm()
Gera o HTML para o elemento Form configurado para postar para o método Action(POST).
É necessário usar a instrução using assegurando que o objeto será liberado quando sair do
escopo.
Exemplo:
@using (Html.BeginForm()){
<!-- ..conteúdo do form.. -->
}
<!--
<form action="/home/index" method="post">
..conteúdo do form..
</form>
-->
Html.ActionLink()
Cria um link para uma Action (não para uma view).
Exemplo:
<!--
Que é renderizado como:
<a href="/home/index">Retornar</a>
-->
Exemplo
Faremos um exemplo onde um formulário do tipo cadastro de cliente é mostrado, e logo após,
será exibido os dados em outra view. A index.cshtml irá conter o cadastro:
@model Proj04_HtmlHelper.Models.Cliente
<h1>Cadastro de cliente</h1>
@using (Html.BeginForm("Index", "Home", FormMethod.Post)){
@Html.Label("Id");
@Html.TextBox("Id");
<br/>
@Html.Label("Nome");
@Html.TextBox("Nome");
<br/>
@Html.Label("Email");
@Html.TextBox("Email");
<br/>
<button type="submit">Salvar</button>
}
E a exibirdados.cshtml :
@model Proj04_HtmlHelper.Models.Cliente
<h1>Dados do cliente</h1>
<hr/>
e controller HomeController.cs :
using System;
using Microsoft.AspNetCore.Mvc;
using Proj04_HtmlHelper.Models;
namespace Proj04_HtmlHelper.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public IActionResult Index(){
//new Cliente(){Nome = "Ellison", Email="e@e.com"}
return View();
}
[HttpPost]
public IActionResult Index(Cliente cliente){
return View("exibirdados", cliente);
}
}
}
Resultado:
Tag Helpers
Já vimos diversas vezes as Tags nos exemplos acima. Ela é uma alternativa ao HtmlHelp, porém,
menos limitadas. Estamos falando dos asp-controller, asp-action e asp-route-* que falamos
em todos os tópicos.
Há diversas TagHelpers, podem ser encontrados os tipos no site da Microsoft clicando aqui.
ASP-FOR
A asp-for , passando o nome do atributo, ela identifica qual o tipo do atributo e renderiza um
campo do tipo desejado.
Podemos observar que o há tipo inteiro , string e bool . Com o asp-for terá que renderizar
componentes equivalentes.
index.cshtml :
@model Cliente
<h1>Cadastro cliente</h1>
<hr/>
<label asp-for="Id"></label>
<input asp-for="Id"/>
<br/>
<label asp-for="Nome"></label>
<input asp-for="Nome"/>
<br/>
<label asp-for="Fiel"></label>
<input asp-for="Fiel"/>
<br/><br/>
<button type="submit">Salvar</button>
</form>
result.cshtml :
@model Cliente
<h1>Cliente Page</h1>
<hr/>
<p>@Model.Id</p>
<p>@Model.Nome</p>
<p>@Model.Fiel</p>
<br/><hr/>
<a asp-action="index">Retornar</a>
e o HomeController.cs :
using Microsoft.AspNetCore.Mvc;
using Proj05_TagHelper.Models;
namespace Proj05_TagHelper.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public IActionResult Index()
{
return View();
}
[HttpPost]
public IActionResult Index(Cliente cliente){
return View("result", cliente);
}
}
}
Resultado:
Podemos observar que foi renderizado um campo numérico( type="number" ) no Id por ser um
int , no campo Nome foi renderizado um type="text" e no campo Fiel foi renderizado um
type="checkbox" . Podemos ver isso na imagem abaixo:
Validação
Data Annottations
Serve para validar o que foi digitado pelo usuário de acordo com as regras de negócio da
aplicação. Data Annottations são declarados no modelo, com uma tag acima dos atributos.
Tag Observação
[DisplayFormat(ApplyFormatInEditMode
Exibe o que texto no formato que você quiser.
= true, DataFormatString = "
Nesse caso, formatando um campo de data.
{0:dd/MM/yyyy}")]
Quase todas elas possuem o atributo ErrorMessage que recebe uma string que contém a
mensagem caso o erro acontecer.
using System;
using System.ComponentModel.DataAnnotations;
namespace Validacao.Models
{
public class Pessoa
{
[Key]
public int Id { get; set; }
Tipo Descrição
Currency Dinheiro
Date Data
EmailAddress Email
Password Senha
O campo Nome tem limite máximo de 50 caracteres, você n consegue escrever mais que isso. Se
ao informar uma idade que não esteja dentro do range, ou, digitar um email inválido:
E finalmente, testando a tag compare, digitaremos um email que não confere com o anterior:
Validação Remota
É muito útil para validar informações no lado do cliente em relação aos dados no servidor,
permite por exemplo validar se um usuário, CPF, email, etc já existe na base de dados.
[Remote("UsuarioExisteAsync", "Pessoas")]
public string Nome { get; set; }
Para exemplificar, iremos utilizar o projeto anterior, porém, vamos adicionar uma validação
remota ao campo email, para verificar se já existe esse email no banco de dados. Para isso,
iremos adicionar primeiramente o remote:
using System;
using System.ComponentModel.DataAnnotations;
namespace Validacao.Models
{
public class Pessoa
{
[Key]
public int Id { get; set; }
Validação personalizada
A validação personalizada, se parece com a remota, porém, independe do controlador e pode ser
chamada a qualquer momento.
As validações padrões são herdadas de ValidationAttribute, então para personalizar, deve-se criar
uma classe herdando de ValidationAttribute. A validação de dados deve ser feita sobrescrevendo
o método isValid() . Iremos criar uma pasta no projeto chamada Validations e nela iremos
adicionar as classes de validação personalizada.
Para demostrar o uso, iremos novamente utilizar o exemplo anterior. Dessa vez iremos adicionar
a validação ao campo idade, verificando se ele é maior de idade. Pessoa.cs :
using System;
using System.ComponentModel.DataAnnotations;
namespace Validacao.Models
{
public class Pessoa
{
[Key]
public int Id { get; set; }
Agora, devemos criar a classe de validação. Nesse caso, a classe de validação irá se chamar
AdultoAttribute.cs (Sempre deve conter o nome Attribute no final) e deve estar dentro da
pasta Validations.
AdultoAttribute.cs :
using System.ComponentModel.DataAnnotations;
using Validacao.Models;
namespace Validacao.Validations
{
public class AdultoAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
//Pegamos através do contexto a instância do objeto.
Pessoa pessoa = (Pessoa)validationContext.ObjectInstance;
//Fazemos a verificação se a idade é menor que 18.
if(pessoa.Idade < 18)
{
//Se for iremos retornar um erro dizendo que apenas adultos
podem se cadastrar
return new ValidationResult("Apenas adultos podem se
cadastrar");
}
//Se não retornaremos success, para continuar.
return ValidationResult.Success;
}
}
}
Campo de pesquisa
Faremos aqui um campo de pesquisa. Para isso, iremos utilizar um controlador qualquer criado a
partir do scaffouding com suas respectivas views de index, create e etc. Mais especificamente,
iremos adicionar na view index que é utilizada para listar os dados.
É feita através de um formulário que contém um campo para o usuário digitar e o botão de
procurar. Esse campo será interceptado pelo método no controller e passado para o controller
para que possa ser feita uma consulta ao banco de dados.
Index.cshtml :
@model IEnumerable<Relacionamento.Models.Pessoa>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Nome)
</th>
<th>
@Html.DisplayNameFor(model => model.Nascimento)
</th>
<th>
@Html.DisplayNameFor(model => model.Endereco)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Nome)
</td>
<td>
@Html.DisplayFor(modelItem => item.Nascimento)
</td>
<td>
@Html.DisplayFor(modelItem => item.Endereco.Id)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using Relacionamento.Data;
using Relacionamento.Models;
namespace Relacionamento.Controllers
{
public class PessoasController : Controller
{
private readonly ApplicationContext _context;
[HttpGet]
public async Task<IActionResult> Index()
{
var applicationContext = _context.Pessoas.Include(p => p.Endereco);
return View(await applicationContext.ToListAsync());
}
ViewComponents
Permitem criar funcionalidades semelhantes a uma Partial View. Dá jus ao nome, é um view que
permite mostrar um componente de um fluxo.
Para criar um componente, primeiro você precisa criar a classe de chamada que estará em uma
pasta de nome Components que está no diretório principal do projeto.
A classe de chamada deve ter ViewComponents no final, por exemplo, um componente chamado
Lista deve ser ListaViewComponents.cs . Para a classe ser um ViewComponent, ela deve derivar
do tipo:
Components/ListaViewComponent.cs
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace Proj07_ViewComponent.Components
{
//Deve derivar de ViewComponent
public class ListaViewComponent : ViewComponent
{
//A classe de retorno é obrigatória ter o nome InvokeAsync, com esse
mesmo tipo de retorno (Task<IViewComponentResult>).
public async Task<IViewComponentResult> InvokeAsync(string sequencia){
//Nesse caso, passamos um parâmetro chamado sequencia, nessa linha
abaixo, tratamos ela para retornar uma lista de substring separadas por ,
(virgula).
var resultado = await Task.FromResult(sequencia.Split(new char[]
{','}));
//Retorna a View "Lista" passando o resultado(lista) por parâmetro.
return View("Lista", resultado);
}
}
}
A nossa Lista.cshtml exibe uma lista simples dos argumentos passados por parâmetro:
@model IEnumerable<string>
<style type="text/css">
li{font-size:20px;}
</style>
@if(ViewData["Titulo"] != null){
<h2>@ViewData["Titulo"]</h2>
}
<ul>
@foreach(var item in Model){
<li>@item</li>
}
</ul>
<h1>Index</h1>
<!--
Usando taghelpers
É necessário adicionar ao _ViewImports.cshtml a linha:
@addTagHelper *, NomeProjeto
-->
@{
ViewData["Titulo"] = "Times";
}
<vc:lista sequencia="Santos"></vc:lista>
using Microsoft.AspNetCore.Mvc;
namespace Proj07_ViewComponent.Controllers
{
public class HomeController : Controller
{
public IActionResult Index(){
return View();
}
1. Usando InvokeAsync()
2. Usando tag helper
3. Usando direto no controller
View Injection
Antigamente, para recuperar os dados em uma view, previsavamos passar através do
controlador com ViewBag, ViewData ou propriedades do ViewModel. No ASP.NET Core ficou bem
mais simples usando a diretiva @inject , que permite injetar as dependências diretamente nas
views e recuperar os dados.
Pode ser útil para serviços específicos de uma view, como por exemplo, dados para preencher
elementos da view.
Sintax:
Injetando um serviço
1. Criar o serviço
Para criar um serviço, deveremos criar uma pasta Services na pasta do projeto. E dentro dela
haverá os serviços, com o nome com a sintax NomeService.cs , onde deverá ter a palavra
Service no final.
No nosso caso, o serviço será uma classe que retornará uma lista de estados.
Services/EstadosService.cs :
using System;
using System.Collections.Generic;
using Proj08Inject.Models;
namespace Proj08Inject.Services
{
public class EstadosService
{
public List<Estado> GetEstados(){
return new List<Estado>(){
new Estado("São Paulo", "SP"),
new Estado("Bahia", "BA"),
new Estado("Pará", "PA"),
new Estado("Goias", "GO"),
};
}
}
}
Models/Estado.cs :
namespace Proj08Inject.Models
{
public class Estado
{
public string Nome { get; set; }
public string Sigla { get; set; }
2. Registrar o serviço
Modos Descrição
Transient Um objeto do serviço é criado toda vez que um objeto for requisitado.
Injetamos na Startup.cs :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Proj08Inject.Services;
namespace Proj08Inject
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
//ADICIONAMOS
services.AddTransient<EstadosService>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment
env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc(route => {
route.MapRoute("default",
"controller=Home}/{action=Index}/{id?}");
});
}
}
}
3. Injetar o serviço na View
<ul>
@foreach(var item in estados.GetEstados()){
<li>@item.Nome - @item.Sigla</li>
}
</ul>
Area
A estrutura padrão MVC que possui Models, Views e Controllers, atende a maior parte das
aplicações. No entanto, algumas aplicações pode ter um grande número de controladores e cada
controlador associado a vários modos de exibição. Para acomodar grandes projetos, a AspNet
Core permite que particione as aplicações em unidades menores que são denominadas Areas.
Uma área pode ser definida como unidades funcionais menores em um projeto ASP.NET Core
MVC com seu próprio conjunto de controladores, views e modelos.
Quando usar?
Se a sua aplicação for composta de múltiplos componentes funcionais de alto nível que
devem ser separadas logicamente
Se você deseja particionar seu projeto MVC para que cada área funcional possa ser
trabalhada de forma independente.
Características
Para usar Area, primeiramente, você deve criar uma pasta denominada Areas na raiz do projeto,
e dentro dela terá cada área específica, e dentro dessas áreas terão os controllers,
views( ViewStart.cshtml ou _ViewImports.cshtml ) e models. Segue a estrutura de uma área:
Como dito, a pasta Areas comporta toda área do projeto. Nesse caso, temos a área Blog e a área
Loja. E cada área comporta Controllers, Views, Models e etc.
Após definir a hierarquia de pastas, você precisa dizer ao MVC que cada controlador está
associado a uma área decorando o nome do controlador com o atributo [Area("NomeDaArea")]
.
using System;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using Proj09Area.Areas.Blog.Models;
namespace Proj09Area.Areas.Blog.Controllers
{
[Area("Blog")]
public class BlogController : Controller
{
public IActionResult Index(){
List<Post> postagens = new List<Post>(){
new Post(1, "Megasena acumula", "A megasena foi
acumulada em 3mi de reais. O próximo..."),
new Post(2, "Ponte desaba em Itabuna", "Uma ponte que
liga o centro da cidade ao..."),
new Post(3, "Estudante de medicina ganha prêmio
nobel", "Uma estudante de medicina do sul da bahia gan..."),
};
return View(postagens);
}
}
}
Como pode ser visto, há o decorador Area("Blog") no BlogController.cs . Em todas os
controladores de determinada área, deve conter esse decorador.
Deve-se também criar uma rota para trabalhar com a área criada. Você pode usar um route
genérico para todas, ou específico para cada view.
GERAL
app.UseMvc(route => {
route.MapRoute("areaRoute",
"area:exists}/{controller=Home}/{action=Index}/{id?}");
route.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
}
Ele vai criar a rota com base no nome da área. Tendo como padrão HomeController e Action index.
ESPECÍFICO
Cria uma rota para cada área em específico, sendo que, quando não existe um route para uma
área, não será possível obter o acesso.
app.UseMvc(route => {
route.MapAreaRoute(
name: "AreaBlog",
areaName:"Blog",
template: "Blog/{controller=Blog}/{action=Index}/{id?}");
route.MapAreaRoute(
name: "AreaLoja",
areaName:"Loja",
template: "Loja/{controller=Loja}/{action=Index}/{id?}");
route.MapRoute("default",
"{controller=Home}/{action=Index}/{id?}");
});
}
Criamos as áreas e a rota para acessar as áreas. Finalmente, veremos como usar a âncora <a>
para acessar. Assim como temos a taghelper asp-controller para ditar o controller, e a asp-
action para ditar a action, temos o asp-area para ditar a área. A âncora segue a sintax:
Views/Shared/_Layout.cshtml :
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
</head>
<body>
<div id="nav">
<a asp-area="" asp-controller="Home" asp-action="Index">Home</a>
<a asp-area="Blog" asp-controller="Blog" asp-action="Index">Blog</a>
<a asp-area="Loja" asp-controller="Loja" asp-action="Index">Loja</a>
</div>
<div>
@RenderBody()
</div>
<div id="footer">
<hr/>
<p>2019 ©. Todos os direitos reservados.</p>
</div>
</body>
</html>
Quando uma área não possui layout, ou, não possui _ViewStart , ele por padrão fica sem
layout. Porém, se criar uma _ViewStart e utilizar do @{Layout = "__Layout"} , se a área
não tiver um _Layout próprio na pasta Shared da área, ela busca da pasta Shared da pasta
raiz.
Ele elimina a necessidade da maior parte do código de acesso a dados que os desenvolvedores
normalmente precisam escrever.
O EntityFramework fornece uma classe principal chamada DBContext onde ela é o canal de
comunicação com o banco de dados, representa a sessão. Ela vai administrar as entidades em
tempo de execução.
Cada DBContext contém uma ou mais DBSet, que representa as tabelas no banco de dados.
Database First: Partimos de um banco de dados e das tabelas existentes e usa o EFCore
para gerar as classes de domínio.
Code First: Cria as classes de domínio e usa o EFCore para gerar o banco de dados e as
tabelas.
A abordagem Code First há um aumento de produtividade, pois definir classes é mais fácil
que criar tabelas, e também há o controle de versionamento (Migrations). Mais fácil de
testar e integrar.
A melhor opção é utilizar a mesma versão do ASP.NET Core. A versão utilizada é a 2.2.
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace EFCore.Models
{
[Table("Alunos")]
public class Aluno
{
[Key]
public int Id{ get; set; }
[Required]
[StringLength(50)]
public string Nome { get; set; }
[Required]
[StringLength(50)]
public string Sexo { get; set; }
[Required]
[StringLength(150)]
public string Email { get; set; }
Notação Descrição
Terceiro passo: Definindo uma DbContext e DbSet
para a tabela Aluno.
Devemos definir uma classe simples, herdade de DbContext da
Microsoft.EntityFrameworkCore .
using Microsoft.EntityFrameworkCore;
namespace EFCore.Models
{
public class AlunoContext : DbContext{
public DbSet<Aluno> Alunos { get; set; }
{
"ConnectionStrings": {
"conexaoSQLServer": "Data Source=DESKTOP-88QLLPP;Initial
Catalog=EscolaDemo;Integrated Security=True;"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
public voi.......
services.AddMvc();
}
Para gerar o código de configuração, deve-se usar o migrations no Package Manager Console:
add-migration Que gera o código para atualizar o banco de dados de acordo com
nome as alterações feitas no modelo de entidades.
Add-Migration
Para gerar o código com as alterações do banco de dados, devemos utilizar o Add-Migration
nome , onde o nome é utilizado apenas para identificar.
Neste momento, com toda configuração dos quatro passos acima, vamos gerar o arquivo de
configuração digitando Add-Migration Inicial .
Se tudo ocorrer bem, uma pasta chamada Migrations será criada, e nela há o versionamento do
banco de dados. A cada migration, irá ser criado um arquivo de sintax
20190518054044_Inicio.cs onde o número inicial é a data aplicada e depois do _ o nome
dado para a migration no comando.
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace EFCore.Migrations
{
public partial class Inicio : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Alunos",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy",
SqlServerValueGenerationStrategy.IdentityColumn),
Nome = table.Column<string>(maxLength: 50, nullable: false),
Sexo = table.Column<string>(maxLength: 50, nullable: false),
Email = table.Column<string>(maxLength: 150, nullable:
false),
Nascimento = table.Column<DateTime>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Alunos", x => x.Id);
});
}
Podemos fazer uma breve análise que o código implementa tudo o que configuramos nas
entidades/modelos (MaxLenght, nullable, primarykey, etc..).
Remove-Migration
Caso tenha gerado o migration e queira remover, utiliza-se o Remove-Migration . Porém, se já
estivar aplicado ao banco de dados com Update-database , não poderá remover o migration
aplicado. Para isso, será necessário apagar os migrations de alteração manualmente e aplicar
novamente.
Update-database
Agora que geramos o arquivo de configuração com Add-Migration , precisamos aplicar ao banco
de dados. Para isso, utilizamos o Update-database -Verbose , onde o -Verbose mostra passo a
passo da configuração.
Com o Done. conseguimos aplicar ao banco de dados. Podemos observar em todo o código
gerado que contém as informações especificadas na entidade.
Podemos observar que foi gerada o Schema com o nome EscolaDemo como foi especificada no
appsettings.json . E que o nome da tabela é Alunos que também foi especificada na entidade,
assim como as características da tabela.
using Microsoft.EntityFrameworkCore.Migrations;
namespace EFCore.Migrations
{
public partial class SeedDatabase : Migration
{
/*Usando o comando .Sql para adicionar elementos a tabela*/
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("INSERT INTO Alunos(nome, sexo, email,
nascimento) VALUES('Ellison', 'Masculino', 'ellison@gmail.com', '17/01/1998')");
migrationBuilder.Sql("INSERT INTO Alunos(nome, sexo, email,
nascimento) VALUES('Rebeca', 'Feminino', 'Rebekinha@gmail.com', '07/05/1998')");
migrationBuilder.Sql("INSERT INTO Alunos(nome, sexo, email,
nascimento) VALUES('Rosinei', 'Feminino', 'Rosestar@gmail.com', '13/09/1973')");
}
e ao dar um Update-database -Verbose verifica-se no banco de dados que foi inserido os dados:
CRUD
A partir desse momento sabemos como criar as entidades e configurar o banco, e até mesmo,
alimentar dados ao banco através do migration. Agora, vamos desenvolver o nosso próprio
CRUD com página web, ou seja, acessar o banco através dos Controllers.
using EFCore.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace EFCore.Controllers
{
public class HomeController : Controller
{
//CRIA UM ATRIBUTO DO TIPO AlunoContext
private AlunoContext _context;
A partir do momento que você tem uma instância local, você pode utilizar essa instancia local
para lista, adicionar, editar ou remover os dados do banco de dados.
Listar
Vamos listar na View Index.cshtml .
@model IEnumerable<EFCore.Models.Aluno>
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Nome)
</th>
<th>
@Html.DisplayNameFor(model => model.Sexo)
</th>
<th>
@Html.DisplayNameFor(model => model.Email)
</th>
<th>
@Html.DisplayNameFor(model => model.Nascimento)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Nome)
</td>
<td>
@Html.DisplayFor(modelItem => item.Sexo)
</td>
<td>
@Html.DisplayFor(modelItem => item.Email)
</td>
<td>
@Html.DisplayFor(modelItem => item.Nascimento)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
</body>
</html>
No método action Index, usamos _context.Alunos.ToList() que retorna uma lista com todos
os elementos do banco de dados, e essa lista é passada para ser exibida na view.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EFCore.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace EFCore.Controllers
{
public class HomeController : Controller
{
private AlunoContext _context;
Adicionar
Para adicionar, iremos criar uma página de formulário [HttpGet] chamada Create.cshtml .
[HttpGet]
public IActionResult Create()
{
return View();
}
Create.cshtml :
@model EFCore.Models.Aluno
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Create</title>
</head>
<body>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
</body>
</html>
Além disso, é necessário utilizar uma Bind para o objeto recebido. A bind é responsável para
garantir que só foram preenchidos os campos passado por parâmetro, é obrigatória para a
segurança.
[HttpPost]
[ValidateAntiForgeryToken]
//Podemos observar que são colocados no BIND o nome dos atributos de Aluno. É
exatamente assim que deve estar, com todos os atributos.
public IActionResult Create([Bind("Id, Nome, Sexo, Email, Nascimento")]Aluno
aluno){
return RedirectToAction(nameof(Index));
}
return View(aluno);
}
Após verificar que a validação do modelo é true, podemos salvar no banco de dados usando
.Add(entidade) e finalmente salvar as alterações (persistir no banco de dados) com
.SaveChanges() .
Editar
Para editar, primeiramente, ao clicarmos devemos retornar um formulário com as informações
atuais. Para isso, criamos um HttpGet retornando uma view com os dados preenchidos:
[HttpGet]
public IActionResult Edit(int? id)
{
if(id == null){
return NotFound();
}
if(aluno == null){
return NotFound();
}
return View(aluno);
}
@model EFCore.Models.Aluno
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Edit</title>
</head>
<body>
<h4>Editar aluno</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Nome" class="control-label"></label>
<input asp-for="Nome" class="form-control" />
<span asp-validation-for="Nome" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Sexo" class="control-label"></label>
<input asp-for="Sexo" class="form-control" />
<span asp-validation-for="Sexo" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Email" class="control-label"></label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Nascimento" class="control-label"></label>
<input asp-for="Nascimento" class="form-control" />
<span asp-validation-for="Nascimento" class="text-danger">
</span>
</div>
<div class="form-group">
<input type="submit" value="Salvar" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Voltar para a lista</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
</body>
</html>
Após isso, devemos criar o método [HttpPost] , que não só recebe um objeto por parâmetro,
mas também o id. E utiliza também o [ValidateAntiForgeryToken] e a Bind. E que também
verifica ModelState.
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id,
[Bind("Id, Nome, Sexo, Email, Nascimento")]Aluno
aluno)
{
//Verifica se o id passado é o mesmo que o Id do objeto.
if(id != aluno.Id)
{
return NotFound();
}
Deletar
A utilização do [HttpPost] e do [ValidateAntiForgeryToken] é somente usado quando a
view exibe em um formulário para depois excluir. Nesse caso, somente temos um botão de
excluir na index e faz isso diretamente.
return RedirectToAction(nameof(Index));
}
É verificado se o id passado não é nulo e a partir disso procura o aluno no contexto com LINQ
através do SingleOrDefault procurando pelo id. Após obter, utiliza-se
_context.Alunos.Remove(aluno) e persiste os dados com .SaveChanges() .
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace EFCore.Models
{
[Table("TipoSocios")]
public class TipoSocio
{
[Key]
public int Id { get; set; }
[Required]
public int DuracaoEmMeses { get; set; }
[Required]
public int TaxaDesconto { get; set; }
}
}
AlunoContext.cs
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace EFCore.Models
{
public class AlunoContext : DbContext
{
public DbSet<Aluno> Alunos { get; set; }
//Adicionado o DbSet<TipoSocio>
public DbSet<TipoSocio> TipoSocios { get; set; }
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace EFCore.Models
{
[Table("Alunos")]
public class Aluno
{
[Key]
public int Id{ get; set; }
[Required]
[StringLength(50)]
public string Nome { get; set; }
[Required]
[StringLength(50)]
public string Sexo { get; set; }
[Required]
[StringLength(150)]
public string Email { get; set; }
[Required]
[StringLength(150)]
public string Foto { get; set; }
[Required]
[StringLength(150)]
public string Texto { get; set; }
//RELACIONAMENTO UM PRA UM
public TipoSocio TipoSocio { get; set; }
}
}
Para o relacionamento, deve-se ser criado uma propriedade com o mesmo nome do tipo, que
nesse caso, TipoSocio.
Ao subir para o banco de dados, verificamos que foi criado uma nova tabela de nome TipoSocios
(Id, TaxaDesconto, DuracaoEmMeses) e na tabela Aluno foi criado um novo campo chamado
TipoSocioId que é do tipo inteiro e que esse inteiro representa o Id do TipoSocios referente ao
aluno.
Temos uma forma melhor de atribuir relacionamentos que é usando Fluent API.
Para demostrar como mostramos os dados, iremos criar um novo controller chamado
AlunoTipoSocioController.cs :
using EFCore.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace EFCore.Controllers
{
public class AlunoTipoSocioController : Controller
{
private AlunoContext _context { get; set; }
para podermos acessarmos a lista de alunos, não devemos acessar como anteriormente,
utilizando _context.Alunos.ToList() . Precisamos incluir no carregamento a tabela
TipoSocio , e só podemos incluir com o método .Include , passando uma função lambda
dizendo que é o TipoSocio necessário para carregar.
Se tentar usar a forma antiga de acessar, os dados não serão mostrados na View.
@model IEnumerable<Aluno>
@if (!Model.Any())
{
<p>Não existem dados.</p>
}
else
{
<table>
<thead>
<tr>Nome</tr>
<tr>Taxa de Desconto (%)</tr>
</thead>
<tbody>
@foreach (var aluno in Model)
{
<tr>
<td>@Html.DisplayFor(m => aluno.Nome)</td>
<td>@Html.DisplayFor(m => aluno.TipoSocio.TaxaDesconto)</td>
</tr>
}
</tbody>
</table>
}
FLUENT API
Até agora, para mapear as entidades, usamos os Datas Annotations. Ele utiliza atributos para
realizar o mapeamento e configuração no modelo de domínio. Porém, temos uma outra
alternativa que possui mais recursos e que mantém a camada de domínio sem referências
externas, e essa alternativa é o FLUENTE API.
Cliente.cs
using System.Collections.Generic;
namespace FluentAPI.Models
{
public class Cliente
{
public int Id { get; set; }
public string Nome { get; set; }
public string Telefone { get; set; }
Pedido.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace FluentAPI.Models
{
public class Pedido
{
public int Id { get; set; }
public string Item { get; set; }
public int Preco { get; set; }
public int Quantidade { get; set; }
Endereco.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace FluentAPI.Models
{
public class Endereco
{
public int Id { get; set; }
public string Rua { get; set; }
public string Bairro { get; set;}
public string Cidade { get; set; }
public string Estado { get; set; }
Podemos verificar que Cliente tem dois relacionamentos, ele tem um endereco e tem vários
pedido . Sendo que endereco e pedido só podem ter um Cliente . Todos os relacionamentos
vão ser configurados com Fluente Api.
Configurando o OnModelCreating
Assim como citado, o FLUENT API define o mapeamento e a configuração via código no método
override OnModelCreating do DbContext. Assim, criamos um ApplicationContext.cs :
using FluentAPI.Models;
using Microsoft.EntityFrameworkCore;
namespace FluentAPI.Data
{
public class ApplicationContext : DbContext
{
//DECLARAMOS TODOS OS DBSET DE TODOS OS MODELOS
public DbSet<Cliente> Clientes { get; set; }
public DbSet<Pedido> Pedidos { get; set; }
public DbSet<Endereco> Enderecos { get; set; }
}
}
}
Dentro do OnModelCreating, usamos o ModelBuilder para configurar as entidades. Com a
sintax:
modelBuilder.Entity<Endereco>()
.Property(e => e.Rua)
.HasMaxLength(80)
.IsRequired();
Ao configurar relacionamentos com Fluent, vocâ vai usar o padrão Has / With. O método
HasOne e o método WithOne são usados para propriedades de navegação de referência e os
métodos HasMany e WithMany usados para propriedades de navegação de coleção.
Um para um
modelBuilder.Entity<Cliente>()
.HasOne(c => c.Endereco) //Cliente tem um Endereco
.WithOne(e => e.Cliente) //Endereco tem um Cliente
.HasForeignKey<Endereco>(e => e.ClienteId); //Chave estrangeira
Caso queira que que a chave estrangeira fique na tabela Cliente, deve seguir o passo abaixo:
modelBuilder.Entity<Endereco>()
.HasOne(e => e.Cliente)
.WithOne(c => c.Endereco)
.HasForeignKey<Cliente>(c => c.EnderecoId);
Relacionamento 0..1
Nesse relacionamento anterior, o Cliente é obrigado a ter um Endereço ligado a ele. Para
ter a opção de ter nenhum endereço, o atributo ClienteId em Endereco deve conter o ?
que faz o atributo receber null. Exemplo:
using System;
namespace FluentAPI.Models
{
public class Endereco
{
public int Id { get; set; }
public string Rua { get; set; }
public string Bairro { get; set;}
public string Cidade { get; set; }
public string Estado { get; set; }
Um para muitos
modelBuilder.Entity<Pedido>()
.HasOne(p => p.Cliente)
.WithMany(c => c.Pedidos)
.OnDelete(DeleteBehavior.Restrict);
Nesse caso, a chave estrangeira sempre vai ficar na entidade One (Que não contém a lista).
Mesmo se tentar fazer invertido:
modelBuilder.Entity<Cliente>()
.HasMany(c => c.Pedidos)
.WithOne(p => p.Cliente)
.OnDelete(DeleteBehavior.Restrict);
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Design
Microsoft.EntityFrameworkCore.Tools.DotNet
Microsoft.VisualStudio.Web.CodeGeneration.Tools
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Design"
Version="2.2.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer"
Version="2.2.4" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools"
Version="2.0.4" />
</ItemGroup>
</Project>
Após essas configurações, todos os recursos estarão disponíveis para o projeto, tanto o EF Core
quanto os recursos do Migrations.
Já que no Visual Studio Code não temos o Gerenciador como no Visual Studio, devemos utilizar
os comandos do EF Migration através do CLI.
Lembrando que deve-se estar na pasta do projeto ao aplicar os comandos. Assim como
todos os comandos do dotnet.
Lazy Loading
Quando temos relacionamentos no nosso modelo de dados, por exemplo, um Cliente com
Pedidos, o EF nos permite que, ao ler o cliente, os pedidos possam ser trazidos também (Com o
include utilizado na parte do CRUD para listar).
Mas isto pode deixar tudo muito lento, pois podemos ter vários clientes, com vários pedidos, e
os pedidos também tem outros relacionamentos, como produtos, vendedores, etc.
Para a carga dos dados ficar mais rápida, o Lazy Loading, ou carregamento preguiçoso/lento, é
empregado e os dados relacionados são trazidos somente se eles forem consultados, ou
acionados.
O lazy loading só foi implementado a partir do EF Core 2.1. Além de melhorar o desempenho,
pode ser mais fácil ainda de ser implementado. Só é necessário algumas configurações.
Fazer a configuração na Startup.cs para que o projeto utilize o LL: Adicionar ao serviço do
DbContext o método .UseLazyLoadingProxies() .
services.AddDbContext<ApplicationDbContext>(options =>
options.UseLazyLoadingProxies()
.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
A partir dessas configurações, o LazyLoading é aplicado nativamente ao projeto e não
precisamos utilizar o .Include para adicionar as tabelas de relacionamento quando realizarmos
as consultas.
Identity
Criando projeto
Para criar um projeto do Identity no Visual Studio, basta criar um projeto Asp Net Core Mvc com a
autenticação individual.
Ao clicar em Ok, o projeto estará pronto com os pacotes do Entity Framework Sql Server e do
Identity.
Logo após, lembre-se de alterar a string de conexão do banco de dados para uma string
válida.
Estrutura do projeto
O projeto MVC com autenticação, fica da seguinte maneira:
A pasta Identity fica os arquivos do sistema. O Controllers fica somente o HomeController.cs
com nenhum alteração.
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace IdentityMacoratti.Data
{
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext>
options)
: base(options)
{
}
}
}
Observe que não há DbSet e que a classe também não é filha da DbContext. Ela é filha da
IdentityDbContext, ou seja, a classe mãe já tem as configurações do banco de dados das entidades
do Identity. Essa classe é usada pra aplicar outros DbSet necessário, é uma extensão do
DbContext.
Também temos as Views referente ao Layout criado pelo projeto MVC, onde há alteração da
barra de navegação, onde o canto superior direito aparece um botão pra login e register quando
não está logado e logout e o painel de controle quando está logado:
E finalmente o arquivo de Startup.cs , que contém algumas informações a mais que estará
escrita no código:
using IdentityMacoratti.Data;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace IdentityMacoratti
{
public class Startup
{
//ASSIM COMO NO MÉTODO MVC, É CRIADO UM ARQUIVO DE CONFIGURAÇÃO
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
//UTILIZAÇÃO DO COOKIES
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
//DIZER QUAL O DBCONTEXT A SER UTILIZADO, ALÉM DE PASSAR A STRING DE
CONEXÃO DO BANCO DE DADOS.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
/*CONFIGURAR O SERVIÇO DO Identity
Observer que é passado o IdentityUser que é o Modelo que contém
as informações do banco de dados do Usuario.
*/
services.AddDefaultIdentity<IdentityUser>()
.AddDefaultUI(UIFramework.Bootstrap4)
//Aqui é passado o nome do DbContext que o Entity vai utilizar
pra guardar e acessar dados do sistema
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure
the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change
this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
Finalmente, devemos alterar a string de conexão para uma válida. Trocando o nome do Server e
um nome para a base de dados (Database).
appsettings.json :
{
"ConnectionStrings": {
"DefaultConnection": "Server=DESKTOP-
88QLLPP;Database=EntityDb;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
Exemplo:
ContatoController :
using IdentityMacoratti.Data;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
namespace IdentityMacoratti.Controllers
{
public class ContatoController : Controller
{
private ApplicationDbContext _context;
Nesse caso, ele só deixa visualizar a página com o conteúdo da tabela Contatos do banco de
dados se estiver logado, se não, ele volta pra view index em outro controlador.
Além dessa forma, existe outra usando decoradores [Authorize] e [AllowAnonymous] . Vamos
modificar o controlador acima para utilizar o decorador [Authorize] :
ContatoController :
using IdentityMacoratti.Data;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
namespace IdentityMacoratti.Controllers
{
public class ContatoController : Controller
{
private ApplicationDbContext _context;
[Authorize]
public IActionResult Index()
{
var contatos = _context.Contatos.ToList();
return View(contatos);
}
}
}
Com o decorator, só é possível acessar a View se estiver logado. Caso tente acessar sem login, ele
te leva para a página de realização de login.
ContatoController :
using IdentityMacoratti.Data;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
namespace IdentityMacoratti.Controllers
{
[Authorize]
public class ContatoController : Controller
{
private ApplicationDbContext _context;
Agora, vamos supor que entre todos os métodos de um controlador com o decorador
[Authorize] tenha um que não é necessário estar logado, é usado o decorador
[AllowAnonymous] acima dessa action para permitir que seja visualizada sem login:
ContatoController :
using IdentityMacoratti.Data;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
namespace IdentityMacoratti.Controllers
{
[Authorize]
public class ContatoController : Controller
{
private ApplicationDbContext _context;
Políticas de senha
Para a senha, o Identity coloca por padrão a obrigatoriedade de ter letra maiúscula e minúscula,
ter letras e números e ainda simbolos (@&..). Podemos fragilizar a senha retirando alguma delas:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
Bloqueio de usuário
Podemos definir o tempo em que o usuário vai ficar bloqueado, número máximo de tentativas
sem sucesso a efetuar o login e bloquear um novo usuário entre outras configurações.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
Configuração de login
Se quizermos que o usuário seja obrigado a ter um endereço de email ou telefone confirmado
para iniciar a sessão.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
options.User.AllowedUserNameCharacters = "abcdefg123456789"
significa que o usuário só pode ser preenchido com algum desses caracteres informados.
O Identity já cria uma tabela para armazenar os Roles (AspNetRoles) e as conexões de User com
Roles (AspNetUserRoles).
Podemos observar que a AspNetRoles tem o papel Admin. E na tabela AspNetUserRoles já
cadastramos um dois usuários setando o Id da Role no AspNetRoles. Em UML fica:
[Authorize(Roles="Admin")]
public class FinancasController : Controller
{
....
}
assim, só é possível acessar esse controlador se for um usuário com o papel de Admin.
Além disso, como vimos na imagem acima, um usuário pode ter mais de um papel. O
atributo/modificador [Authorize] permite multiplos Roles:
[Authorize(Roles="Gerente, Financeiro")]
public class FinancasController : Controller
{
....
}
[Authorize(Roles="Gerente, Financeiro")]
public class FinancasController : Controller
{
public IActionResult Criar(){
...
}
[Authorize(Roles="Admin")]
public IActionResult Deleter(){
...
}
}
Significa que todos os métodos do controlador FinancasController podem ser acessados pelo
perfil Gerente e Financeiro, exceto a action Deletar() , que só pode ser acessado por Admin.
services.AddDefaultIdentity<IdentityUser>()
.AddRoles<IdentityRole>() //Configurando Service Role
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
using System.ComponentModel.DataAnnotations;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace IdentityMacoratti.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class RegisterModel : PageModel
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender;
//Adicionar uma variável local da instância
private readonly RoleManager<IdentityRole> _roleManager;
public RegisterModel(
UserManager<IdentityUser> userManager,
RoleManager<IdentityRole> roleManager, //Receber o serviço
SignInManager<IdentityUser> signInManager,
ILogger<RegisterModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
_emailSender = emailSender;
//Atribuir o serviço a variável local
_roleManager = roleManager;
}
....
....
....
}
_logger.LogInformation
("User created a new account with password.");
var code =
await
_userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = user.Id, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email,
"Confirm your email",
$"Please confirm your account by <a
href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
A partir desse momento, todos os usuários que serão cadastrados com o Role Cliente.
Podemos observar então, que o atributo _roleManager do tipo
RoleManager<IdentityRole> faz o controle das Roles no Identity. Com o:
Além disso, podemos identificar que o _userManager do tipo UserManager<IdentityUser>
e é responsável pela tabela de usuário no banco de dados. Nesse caso, utilizamos:
Atribuindo na Startup.cs
Esse método consiste em criar um novo usuário e atribuir um perfil a ele. Para isso, utiliza-se o
Startup.cs . É um método muito bom quando você quer iniciar uma conta Admin.
Task<IdentityResult> roleResult;
string email = "admin@admin.com"; //EMAIL
if (!hasAdminRole.Result)
{
roleResult = roleManager.CreateAsync(new IdentityRole("Admin"));
roleResult.Wait();
}
//Verifica se tem algum User com o mesmo email, se não ele cria uma nova
conta
Task<IdentityUser> testUser = userManager.FindByEmailAsync(email);
testUser.Wait();
O método basicamente acessa os serviços de Role e de User, verifica se já existe o Role Admin e
se não existe, cria. Depois verifica se o usuário já existe, e se não existir, cria o usuário e atribui a
Role a ele.
Precisamos agora chamar o método criado no método Configure . Para isso, iremos chamar no
final do método Configure :
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
//CHAMANDO O MÉTODO CREATEROLES
CreateRoles(serviceProvider);
}
Para isso, iremos criar um novo tipo chamado ApplicationUser.cs na pasta Models. Além
disso, estendemos a tabela com um novo atributo do tipo string chamado Endereco_completo.
using Microsoft.AspNetCore.Identity;
namespace WebIdentityAspNetCore.Models
{
public class ApplicationUser : IdentityUser
{
public string Endereco_completo { get; set; }
}
}
Além disso, devemos configurá-lo no contexto dos dados. Como visto anteriormente, a classe de
contexto não é derivada da DbContext e sim da IdentityDbContext, porém, a classe de contexto por
padrão utiliza o IdentityUser e precisamos altera-lo passando o ApplicationUser através do generic.
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using WebIdentityAspNetCore.Models;
namespace WebIdentityAspNetCore.Data
{
//Herdado do IdentityDbContext<ApplicationUser>
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext
(DbContextOptions<ApplicationDbContext> options) : base(options)
{ }
Por padrão o tipo da tabela User no Startup.cs é o IdentityUser e também precisamos alterar
para ApplicationUser no ConfigureServices() :
services.AddDefaultIdentity<ApplicationUser>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
E finalmente, uma última configuração: Após o ASPNET Core 2.1, os arquivos relacionados as
views de login e gerenciamento são gerados pelo scaffouding e não estão disponíveis na pasta
de controlador e de views. Dos elementos, há somente a partial view de ancoras para acessar o
login/register no Shared/_LoginPartial.cshtml e nesse arquivo é necessário a alteração
também do IdentityUser:
@using Microsoft.AspNetCore.Identity
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-
page="/Account/Manage/Index" title="Manage">Hello @User.Identity.Name!</a>
</li>
<li class="nav-item">
<form class="form-inline" asp-area="Identity" asp-
page="/Account/Logout" asp-route-returnUrl="@Url.Action("Index", "Home", new {
area = "" })">
<button type="submit" class="nav-link btn btn-link text-
dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-
page="/Account/Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-
page="/Account/Login">Login</a>
</li>
}
</ul>
OBSERVAÇÃO IMPORTANTE
Caso tenha criado outras views do Identity pelo Scaffolding antes de configurar, é
necessário a alteração em todas as Views.
A partir do momento que a configuração acima é feita, todas as views geradas pelo
scaffolding vem com o tipo ApplicationUser . Antes disso, foram geradas com
IdentityUser .
No arquivo .cs :
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using WebIdentityAspNetCore.Models;
namespace WebIdentityAspNetCore.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class RegisterModel : PageModel
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender;
public RegisterModel(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
ILogger<RegisterModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
_emailSender = emailSender;
}
[BindProperty]
public InputModel Input { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2}
and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and
confirmation password do not match.")]
public string ConfirmPassword { get; set; }
@page
@model RegisterModel
@{
ViewData["Title"] = "Register";
}
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<form asp-route-returnUrl="@Model.ReturnUrl" method="post">
<h4>Create a new account.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password"
class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword"
class="form-control" />
<span asp-validation-for="Input.ConfirmPassword"
class="text-danger"></span>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Finalmente, iremos fazer um pequeno teste. Iremos criar uma view Endereco.cshtml no
controlador HomeController.cs que exibirá o endereço se o usuário estiver logado.
HomeController.cs :
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using WebIdentityAspNetCore.Models;
namespace WebIdentityAspNetCore.Controllers
{
public class HomeController : Controller
{
public readonly UserManager<ApplicationUser> _userManager;
Endereco.cshtml :
@model ApplicationUser
@{
ViewData["Title"] = "Endereco";
}
<h1>Endereco</h1>
<p>@Model.Endereco_completo</p>
namespace ParaExcluir.Models
{
public class Contato
{
public int Id { get; set; }
public string Nome { get; set; }
//Referência do Usuário
public virtual ApplicationUser ApplicationUser { get; set; }
}
}
E teremos a classe ApplicationUser.cs que é derivada de IdentityUser e irá ser nossa tabela
AspNetUsers no banco de dados:
using Microsoft.AspNetCore.Identity;
using System.Collections.Generic;
namespace ParaExcluir.Models
{
public class ApplicationUser : IdentityUser
{
/*A extensão do banco de dados: A tabela IdentityUser terá uma lista de
contatos. */
public virtual List<Contato> Contatos { get; set; }
}
}
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using ParaExcluir.Models;
namespace ParaExcluir.Data
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
//ADICIONANDO O DBSET
public DbSet<Contato> Contatos { get; set; }
public ApplicationDbContext(DbContextOptions<ApplicationDbContext>
options) : base(options)
{ }
Após configurarmos os Models e o DbContext. Iremos criar views para listar, criar e excluir
contatos. Através do método ContatoController.cs acessamos os dados no bancos e
aplicamos as views:
Nesse exemplo focamos na view de listagem, a Index . Acessamos os dados através das
duas formas de carregamento, com e sem Lazy Loading.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ParaExcluir.Data;
using ParaExcluir.Models;
using System.Linq;
using System.Threading.Tasks;
namespace ParaExcluir.Controllers
{
public class ContatosController : Controller
{
private readonly ApplicationDbContext _context;
private readonly UserManager<ApplicationUser> _userManager;
public ContatosController(
ApplicationDbContext context,
UserManager<ApplicationUser> userManager)
{
_context = context;
_userManager = userManager;
}
[Authorize]
public async Task<IActionResult> Index()
{
/*Acessamos o usuário através do UserManager utilizando o método
.GetUserAsync onde passamos por parâmetro o User do HttpContext*/
var _user = await _userManager.GetUserAsync(HttpContext.User);
/*
COM LAZY LOADING: Utilizando a instância do próprio usuário com
UserManager
*/
var lista = _user.Contatos.ToList();
/*
COM LAZY LOADING: Utilizando a instância do ApplicationDbContext
para
acessar o usuário procurando pela instância do
UserManager.Id
*/
//var user = _context.Users.FirstOrDefault(u => u.Id == _user.Id);
//var lista = user.Contatos.ToList();
/*
SEM LAZY LOADING: A mesma coisa da anterior, só que como não é
LazyLoading, é necessário o .Include(u => u.Contatos) para carregar
também a tabela Contatos .
*/
//var user = _context.Users
// .Include(u => u.Contatos)
// .FirstOrDefault(u => u.Id == _user.Id);
//var lista = user.Contatos.ToList();
return View(lista);
}
[HttpGet]
public IActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult>
Create([Bind("Id,Nome")] Contato contato)
{
if (ModelState.IsValid)
{
contato.ApplicationUser =
await _userManager.GetUserAsync(HttpContext.User);
_context.Add(contato);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(contato);
}
return View(contato);
}
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var contato = await _context.Contatos.FindAsync(id);
_context.Contatos.Remove(contato);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
}
}
Produtos/Index.cshtml :
Tabela AspNetUsers: