Você está na página 1de 137

Asp Net Core 2

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

Para a criação do projeto existem diversas possibilidades. No momento só trabalharemos com


padrões MVC, porém, além de criar o projeto ASP.NET Core Web App (MVC) onde toda a
estrutura MVC já está pronta, iremos também ensinar como configurar um projeto MVC em um
projeto do tipo ASP.NET Core Empty.

Estrutura de ambos as arquiteturas:

ASP.NET Core Empty ASP.NET Core Web App (MVC)

Para tornar um projeto Empty em um projeto MVC, deve-se:

1. Adicionar ao método ConfigureServices no arquivo Startup.cs o código


services.AddMvc() onde configura dizendo que o tipo do projeto vai ser MVC.

2. No final do método Configure do mesmo arquivo, acrescentar o middleware de


roteamento app.UseMvcWithDefaultRoute(); . Ele tem a mesma funcionalidade do
app.UseMvc contido no Startup.cs do projeto MVC:
app.UseMvc(routes =>
  {
   routes.MapRoute(
   name: "default",
   template: "{controller=Home}/{action=Index}/{id?}");
  });

Só que usando o método UseMvc, é necessário especificar o controller e o método do


controle referente ao index. Com o app.UseMvcWithDefaultRoute() já informa que o
controlador vai ser Home e o método o Index.

3. Finalmente criar as 3 pastas: Models, Views, Controllers.


4. Na controllers, é necessário criar o controlador Home como especificado na parte 2:

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

O @using asp3 é pra especificar o projeto.

A parte de @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers especifica a


utilização de TagHelpers (razor sintax) no projeto.
 

Entendendo Controllers e Views


O padrão MVC é muito utilizado para criação de aplicações web. Em aspnet há o seguinte fluxo:

Após uma requisição via Http o controller assume toda a responsabilidade de entregar a view
desejada (response).

Ao ditar na Startup.cs que o controller base é "{controller=Home}/{action=Index}/{id?}" ,


logo irá dentro do Controllers procurar por HomeController.cs e dentro o método(action)
Index :

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");
      }
  }
}

No método, retorna a view index.cshtml contida em view/Home.

index.cshtml

@{
  ViewData["Title"] = "Minha página";
}

<h5>A minha view Index</h5>

ViewData["Title"] = "Minha página"; é o título da página.

 
 

Tipos de retorno de Action: IActionResult


Os métodos Controller, são responsáveis por receber a requisição e retornar uma view. Cada
action(método) é um link(página). Os métodos são do tipo IActionResult , ViewResult , entre
outros. O mais utilizado é o IActionResult , pois é uma interface que suporta todos os outros
tipos de views.

1. RedirectToAction("index", "Home", new{pagina = 1, ordem="nome"}); : Redireciona


para uma controller/action diferente e ainda há a passagem de argumentos.

Ao acessarmos a página do método, ela redireciona passando argumentos como get.

 
2. Content("Uma string simples!") : Retorna uma string simples

3. Content("CaminhoArquivoPdf", "application/pdf"); : Abre uma guia do tipo PDF.

4. File("images/react.svg", "image/svg+xml"); : Exibe uma imagem SVG.


 

5. return new ObjectResult(new { ID = 1234, nome = "Ellison"}); : Retorna um objeto,


que nesse caso, é anônimo.
HomeController.cs :

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:
 

Além desses, existem:

ContentResult Returna uma string

FileContentResult Retorna conteúdo de um arquivo

FilePathResult Retorna conteúdo de um arquivo

FileStreamResult Retorna conteúdo de um arquivo

EmptyResult Retorna nothing

JavaScriptResult Retorna um script para executação

JsonResult Retorna dados JSON formatado

RedirectToResult Redireciona para a url especificada.

HttpUnauthorizedResult Retorna para uma action/controller diferente

RedirectToRouteResult Retorna para uma action/controller diferente

ViewResult Recebe a resposta da view engine. Exibe a View.

PartialViewResult Recebe a resposta da view engine. Exibe a View.

View Recebe a resposta da view engine. Exibe a View.

Acesso ao HttpContext do Controller


O HttpContext contém toda a informação HTTP. Todo controller tem herança de Controller ,
que disponibiliza uma instancia de HttpContext , ou seja, o acesso é direito.

using System;
using Microsoft.AspNetCore.Mvc;
namespace Proj02_IActionResult.Controllers
{
   public class HomeController : Controller
  {
       public IActionResult Index(){
          return View();
      }

//Podemos observar que o HttpContext é acessado diretamente.


       public IActionResult HttpContextPage(){

           var https = HttpContext.Request.IsHttps;


           var caminho = HttpContext.Request.Path;
           var status = HttpContext.Response.StatusCode;
           var conexao = HttpContext.Connection.ToString();

           return Content($"https: {https}\ncaminho: {caminho}\nstatus:


{status}\nconexao: {conexao}");
      }
  }
}

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
 

Cada um é um objeto e tem um papel específico, único e limitado.


Eles são executados na ordem definida no código

Implementar aplicações ASP.NET Core envolve a configuração via código de quais middlewares
serão utilizados pela app.

De uma forma mais prática, middlewares são configurados no Startup.cs , no método


Configure através do argumento IApplicationBuilder, ou seja, quando há o uso de:

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.

app.UseDeveloperExceptionPage() : Mostra erro detalhado e com muita informação.


Utilizado em modo de desenvolvimento.
app.UseExceptionHandler() : Define um tratamento de erro específico e mostra
mensagens de erros amigáveis geralmente criadas pelo desenvolvedor.
app.UseStatusCodePages() : Mostra página de erro para códigos de status HTTP, como
500, 404, 401..
app.UseStatusCodePagesWithRedirects() : Realiza o redirecionamento do URL após o
erro. Envia o código de status HTTP 302 (found) ao navegador e executa o redirecionamento
usando Action especificada.
app.UseStatusCodePagesWithReExecute() : Reexecuta a requisição com uma ação Action
alternativa.

Configurando dados do appsettings.json no


startup.cs
No projeto ASP.NET Core, existe um arquivo de configuração que costuma ser no formato Json
que se chama appsettings.json. Iremos aprender agora como obter dado dele através do
Startup.cs .

Para isso, iremos adicionar uma string ao appsettings.json :

{
 "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;

//PARA USAR O IConfiguration


using Microsoft.Extensions.Configuration;

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");

           //Após isso, atribuimos ao _config com o builder.Build().


           _config = builder.Build();
      }

       public void ConfigureServices(IServiceCollection services){


      }

       public void Configure(IApplicationBuilder app, IHostingEnvironment env)


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

           app.Run(async (context) =>


          {
//Acessamos através da _config como se fosse um dicionário.
Utilizando o chave de acesso do json.
               var mensagem = _config["Mensagem"];
               await context.Response.WriteAsync(mensagem);

          });
      }
  }
}

Acessamos dentro do método Configure a string do json com _config["Mensagem"] com a


chave da string setada, que nesse caso, era Mensagem .

O objeto IConfiguration é importada da using Microsoft.Extensions.Configuration; .

Nesse caso, estamos pegando a mensagem do appsettings.json e apresentando na tela


através do await context.Response.WriteAsync(mensagem); .

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  

A partir disso, todas as taghelpers ( asp-for , asp-action , asp-controller , asp-route-* e etc..)


podem ser utilizadas em todas as views.

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).

O projeto consiste na seguinte estrutura:

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;}
  }
}

que contém somente a estrutura do formulário.

e Repositorio.cs :

using System;
using System.Collections.Generic;

namespace app2.Models
{
   public static class Repositorio{
   
       private static List<RespostaConvidado> LISTA = new
List<RespostaConvidado>();

       public static void AdicionarResposta(RespostaConvidado resposta){


           LISTA.Add(resposta);
      }

       public static IEnumerable<RespostaConvidado> ListaFinal{


           get{
               return LISTA;
          }
      }
  }
}

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.

Agora vamos ver um pouco das Views. Foram criadas 4 Views:

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 :

<!-- É necessário dizer qual o Model que o formulário vai usar-->


@model app2.Models.RespostaConvidado

@{
  ViewData["Title"] = "Formulário";
}

<h1>Formulário</h1>
<hr />

<div>

   <!--O mesmo asp-action utilizado na página anterior, aciona um método de


HomeController-->
   <form asp-action="Formulario" method="post">
       <div>
           <!-- Com o asp-for, você especifica o atributo do Modele -->
           <label asp-for="Nome">O seu nome: </label>
           <input asp-for="Nome" />
       </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>

<!-- Quando é necessário acessar o modelo dentro de um if de sintax razor, não é


necessário colocar o @.-->
@if(Model.Presente == true){
   <p>Ficarei à sua espera para a festa.</p>
}else{
   <p>Tenho imensa pena de não poder vir a minha festa.</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>

E finalmente, vamos falar sobre o HomeController.cs :

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);
      }

       public ViewResult ListaConvidados(){


           return View("ListaFinal", Repositorio.ListaFinal.Where(c =>
c.Presente == true));
      }
  }
}

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.

Conteúdos estáticos (Html, css e images)


No AspNet, todo conteúdo estático fica dentro da pasta wwwroot. Para poder utilizar contéudo
estático, devemos adicionar ao Startup.cs no método Configure o app.UseStaticFiles() :

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();
      }

       public void Configure(IApplicationBuilder app, IHostingEnvironment env){


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

           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>

       <p class="card bg-light p-3">Temos o Bootstrap a funcionar!</p>

       <img src="~/images/csharp.png"/>

   </div>
</body>
</html>

Ao adicionarmos a class="container" pegamos uma propriedade do css do bootstrap, assim


como no <p class="card bg-light p-3"> .

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.
 

Recebendo parâmetros na action via


GET
Quando uma requisição chega em uma app ASP.NET Core ela é mapeada automaticamente para
os métodos action. Se o método action usar um parâmetro, o framework procura por um
parâmetro de mesmo nome nos dados da requisição, se o parâmetro existir o framework
automaticamente passa o valor do parametro para a action de destino.

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");
      }

       public IActionResult Parametro(string nome, int? idade){

           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.

Passamos um nome e idade, com parametro?nome=Ellison&idade=19 e ele exibiu o valor.

E nesse caso só atribuimos a idade.

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() .

Essa forma de definir o roteamento tem o seguinte template:


{controller=Home}/{action=Index}/{id?} , onde o controller inicial é o HomeController.cs e
o primeiro método ( action ) a ser executado é o Index .

Podemos definir um template de roteamento da seguinte forma:


app.UseMvc(route =>{
route.MapRoute("default", "{controller=Inicio}/{action=Pag1}/{id?}");
});

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.

Para isso, iremos construir um simples controller:

using System;
using Microsoft.AspNetCore.Mvc;

namespace asp8.Controllers
{
   public class InicioController : Controller
  {
       public string Index(int id){
           return "Index\nID: " + id;
      }
  }
}

Se formos executar isso no navegador, observamos o seguinte comportamento:

Observe que o link é exatamente https://localhost:5001 , que equivale a


https://localhost:5001/inicio e https://localhost:5001/inicio/index .

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.

O ID foi definido no Startup.cs quando configuramos o route padrão "


{controller=Inicio}/{action=Pag1}/{id?}" , podemos observa que existe o ID com uma
interrogação, o ID é exatamente o nome do atributo que o método vai receber. Já a interrogação
faz com que o atributo seja opcional, caso tirar, será obrigatório.

Obs: O nome do atributo no método deve ser igual a da declaração do route.

Manipulando Attribute Routing


Com a assinatura [Route()] acima do método especificado, você pode personalizar o método.

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 .

No método pag12345 , iremos acessar por https://localhost:5001/home/pag .

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

No método pagID , será acessado por https://localhost:5001/home/pag , assim como no


método pag12345 . Porém, só vai ser usado o método, caso passe algum argumento. Já que o
método pag12345 não tem argumento, ele procura o que tem e executa.

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}");
      }
  }
}

Ao tentar acessar https://localhost:5001/home/metodo/ellison/56 , não retornará nada, pois


a idade ultrapassa 55.

ViewBag, ViewData e ViewModel


Através dessas alternativas, podemos passar dados do Controller para o View.

Usando ViewBag para passar valores do Controller


para o View
O ViewBag é uma variável dinâmica, você pode atribuir ela no Controller e acessar da View:

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;}

       public IActionResult index(){


           return View();
      }

       public IActionResult pag2(){


           ViewData["frase1"] = "Rebeca é linda";
           ViewData["frase2"] = "E é meu amor!";

           return View();
      }

       public IActionResult pag3(){


           cpf = 123456789;
           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);
      }
  }
}

Agora, finalmente iremos tratar o objeto na View.

@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; }
  }

   public class Telefone{


       public string telefone { get; set; }
       public int ddd { get; set; }
  }

   public class BAG{


       public Humano humano { get; set; }
       public Telefone telefone { get; set; }
  }
}

Podemos observar abaixo, que iremos adicionar um Humano e um Telefone a um BAG e


passaremos como parâmetro para a view:

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"
          };

           BAG bag = new BAG(){


               humano = humano,
               telefone = telefone
          };

           return View(bag);
      }
  }
}

Agora, vamos tratar o Model na View. Views/Home/Index.cshtml :

@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>

Colocamos o model do tipo BAG e acessamos os atributos como se acessa classes.

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 :

<h1>Este conteúdo está dentro da parte do código (_parte1).</h1>

E agora, vamos criar a página que vai conter o conteúdo acima, de forma indireta. Para isso na
index.cshtml :

@{
  Layout = null;
}

<h1>Este conteudo pertence a página principal</h1>


@Html.Partial("_parte1")

Com o sintax razor @Html.Partial("_NomeView") conseguimos pegar o conteúdo da outra view


e colocar nessa. Também poderemos utilizar <partial name="_NomeView"/> . Funcionam da
mesma forma.
O '_' underline no nome da view é convenção de programadores

Resultado:

Valores passados entre Views e PartialViews


Quando temos uma view, e nela possui atributos de ViewBag ou ViewData, podemos passar
tranquilamente para as PartialViews. Mas, caso seja modificado numa PartialView o resultado
não vai ser alterado na View.

Vamos ilustrar com um exemplo: No HomeController.cs de um projeto, declaramos


ViewData[num] = 100 . E temos as View e a PartialView:

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.

Depois da PartialView ser executada, voltamos para o index.cshtml e terminamos de executar o


código, onde no final, há novamente a impressão na tela do num .

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();
      }

       public ViewResult Pag2(){


           return View();
      }
  }
}

Em seguida, dentro da página _Layouts.cshtml que vai estar na pasta Views/Shared :

<!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.

Agora que já temos o layouts, vamos criar as duas páginas:

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>

   <!-- Título e Menu -->


   <div style="padding:20px; background-color:gray;">
       <h2 style="color:white">Layout Section</h2>
       <a href="#">Inicio</a>
   </div>

   <!-- Corpo da página -->


   <div>
      @RenderBody()
   </div>

   <!-- Rodapé Copyright -->


   <div style="text-align:center; padding:20px;">
      @DateTime.Now.Year &copy. Todos os direitos reservados.
   </div>

   <!-- Section -->


   <div>
      @RenderSection("Erros")  
   </div>
   
</body>
</html>

Renderizamos dessa forma com o @RenderSection("Nome") . A partir desse momento, é


obrigatório ter a section Erros na index.cshtml . Para tornar opcional, deve-se acrescentar o
atributo false: @RenderSection("Erros", false) , nesse caso, se não houver na index.cshtml
não terá problema.
 

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>

   <!-- Título e Menu -->


   <div style="padding:20px; background-color:gray;">
       <h2 style="color:white">Layout Section</h2>
       <a href="#">Inicio</a>
   </div>

   <!-- Corpo da página -->


   <div>
      @RenderBody()
   </div>

   <!-- Rodapé Copyright -->


   <div style="text-align:center; padding:20px;">
      @DateTime.Now.Year &copy. Todos os direitos reservados.
   </div>

   <!-- Section -->


   <div>
       <!-- IsSectionDefined -->
      @if(IsSectionDefined("Erros")){
           <p>Tem</p>
          @RenderSection("Erros", false)
      }else{
           <p>Nao tem</p>
      }
   </div>
 
</body>
</html>

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:

Ná página inicial, contém um link para a página de formulário.

Views/Inicio/index.cshtml :

@{
  Layout = null;
}

<h2>Página inicial</h2>
<p>Clique <a asp-action="form">AQUI</a> para cadastrar.</p>

Você poderá observar que eu chamei form no link.

Além disso, também temos a Formulario.cshtml :

<h3>Formulário</h3>

<form asp-controller="inicio" asp-action="form" method="post">


   <input />
   <button>Salvar</button>
</form>

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.

Anchor Tag Helper


Ao trabalhar com âncoras, conseguimos acionar um método contido no controller e
action/método definidos por asp-controller e asp-action . Ainda, podemos passar atributos
da view para o controller através do asp-route-* , método utilizado nos projetos 02 e 03.

Para exemplificar, vamos criar o seguinte index.cshtml :

<a asp-controller="Home" asp-action="Metodo1" asp-route-id="12345">Link 1</a>


<br/>
<a asp-controller="Home" asp-action="Metodo2" asp-route-nome="Ellison">Link
2</a>

Podemos observar que existem em HomeController.cs dois método, um chamado Metodo1


que vai ser executado pelo link1 e Metodo2 que irá ser executado pelo segundo link.

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();
      }

       public string Metodo1(int id){


           return "Estou no método 1 e o id é: " + id;
      }

      [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.

No Metodo1 quando clicamos no link, somos redirecionado pro link


https://localhost:5001/Home/Metodo1?num=12345 com enfase no /Metodo1/12345 . Já no
Metodo2 ficou https://localhost:5001/home/metodo2/Ellison com enfase no
/metodo2/Ellison pois foi definido pelo sistema de Routes [Route("home/metodo2/{nome}")]
onde depois do método, existia uma barra e em seguida o atributo.

Caso não houvesse o sistema de routes, teriamos como se fosse um get, da seguinte forma:
/Metodo2?nome=Ellison .

Projeto 02: Adicionar e remover model


(Usando @Html.ActionLink para passar
valores para o controller)
Consiste em um projeto onde há uma tela index.cshtml contendo numa tabela os dados de
pessoas (nome, sobrenome). Nessa mesma página, há um botão para adicionar novos elementos
e também há a possibilidade de excluir algum dos elementos.

Inicialmente o projeto é composto por index.cshtml :

@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

<form asp-action="Formulario" method="post">


   <div>
       <!-- Com o asp-for, você especifica o atributo do Model -->
       <label asp-for="Nome">O seu nome: </label>
       <input asp-for="Nome" />
   </div>
   <div>
       <label asp-for="Sobrenome">O seu email: </label>
       <input asp-for="Sobrenome" />
   </div>
   <div>
       <label asp-for="Id">O seu Id: </label>
       <input asp-for="Id" />
   </div>

   <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>

E quando a Pessoa é deletada, a seguinte página confirmando é retornada:


@model asp15.Models.Pessoa

<p>@Model.Nome foi removido com sucesso! @Model.Id</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; }
  }
}

E criamos a BAGDATA.cs que contém uma lista de Pessoa:

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"}
          };
       
  }
}

Agora iremos pro HomeController.cs :

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);
      }

//Ao fazer a requisição (Clicar no botão Adicionar) é retornado a View


      [HttpGet]
       public ViewResult Formulario(){
           return View();
      }

//Ao clicar no botão submit, adiciona a Pessoa na lista.


      [HttpPost]
       public ViewResult Formulario(Pessoa p){
           BAGDATA.pessoas.Add(p);
           return View("Obrigado", p);
      }

//Método Delete acionado pela @Html.ActionLink


       public ViewResult Delete(int id){
           var Pessoa = BAGDATA.pessoas.Single(p => p.Id == id);
           BAGDATA.pessoas.Remove(Pessoa);
           return View(Pessoa);
      }
       
  }
}

Assim como comentado no index.cshtml , o @Html.ActionLink criou o link passando id por


parâmetro, e através do id conseguimos remover na lista com o método Delete . No método
além de remover, redirecionamos para a página delete.cshtml .

A estrutura dos arquivos ficou:

Resultado:

Página inicial:
Adicionando uma Pessoa:

Após remover:

Projeto 03: Adicionar, remover e editar.


(Usando Anchor Tag Helper asp-route-*
para passagem de valores para o
controller). Usando conteúdo estático.
O projeto 3 consiste em uma página inicial de gestão de pessoas, ou seja, um ambiente de
cadastro de pessoas. Nela você pode inserir, editar e remover uma pessoa.

A estrutura segue da seguinte forma:


No Models temos o modelo Pessoa e o ARMAZEN , que é onde armazenamos os dados.

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>();

       public static List<Pessoa> pessoas{


           get{
               return PESSOAS;
          }
      }
       
       //ATRIBUI UM ID E ADICIONA A PESSOA A LISTA
       public static void adicionarPessoa(Pessoa pessoa){
           pessoa.Id = (PESSOAS.Count > 0)? PESSOAS[PESSOAS.Count-1].Id + 1 :
0;
           PESSOAS.Add(pessoa);
      }

       //REMOVE UMA PESSOA DA LISTA: Procura pelo Id e remove


       public static void removerPessoa(int id){
           Pessoa pessoa = PESSOAS.Single(p => p.Id == id);
           PESSOAS.Remove(pessoa);
      }

       //Procura uma pessoa pelo id e retorna ela


       public static Pessoa procurarPessoa(int id){
           return PESSOAS.Single(p => p.Id == id);
      }

       //Edita os dados de uma pessoa.


       public static void editarPessoa(Pessoa pessoa){
           PESSOAS.Single(p => p.Id == pessoa.Id).Nome = pessoa.Nome;
           PESSOAS.Single(p => p.Id == pessoa.Id).Sobrenome = pessoa.Sobrenome;
      }
  }
}

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 .

Temos a página index, que é composta por:

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>

Composta por um formulário para cadastro do tipo @model Projeto02.Models.Pessoa .

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>

                  @foreach(var item in Model){


                       <tr>
                           <td>@item.Id</td>
                           <td>@item.Nome</td>
                           <td>@item.Sobrenome</td>
                           <td>
                               <a asp-controller="Home" asp-action="Edit" asp-
route-id="@item.Id"><img src="~/img/edit.png"/></a>
                               <a asp-controller="Home" asp-action="Delete"
asp-route-id="@item.Id"><img src="~/img/delete.png"/></a>
                           </td>
                       </tr>
                  }
                   
               </tbody>
           </table>

      }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 cereja do bolo do projeto 02 é o @Html.ActionLink() , que ao clicar no link é acionado um


método no HomeController e passado um parâmetro. Nesse projeto utilizamos a própria
ancora pra fazer isso.

<a asp-controller="Home" asp-action="Edit" asp-route-id="@item.Id">


   <img src="~/img/edit.png"/>
</a>

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.

E quando possui cadastro a tela exibe:

E novamente, quando clicamos em cada ação é executado um hiperlink com asp-route-*


executando o método e passando o id de cada dado.

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");
      }

       public ViewResult Lista(){


           return View(ARMAZEN.pessoas);
      }

       public IActionResult Delete(int id){


           ARMAZEN.removerPessoa(id);
           return RedirectToAction("Lista");
      }

      [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#.

<!-- Bloco de código -->


@{
string nome = "Ellison";
string apelido = "William";
List<string> lista = new List<string>();
}

<!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=""/> .

A seguir verá a tabela que contém as principais HtmlHelpers:

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.TextBoxFor(m => m.Name)


<input id="Name" name="Name" type="text" value="Name-val"/>

O que muda é o Value.

Html.BeginForm()
Gera o HTML para o elemento Form configurado para postar para o método Action(POST).

Quando não passamos argumentos o form é postado para o mesmo método do


controlador que o invocou.

Sintax: Html.BeginForm("Nome da Action", "Nome do controlador")

É 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).

Sintax: @Html.ActionLink("Texto do link", "método Action", "Nome Controlador")

Exemplo:

@Html.ActionLink("Retornar", "Index", "Home")

<!--
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/>

@Html.LabelFor(model => model.Id);


@Html.DisplayFor(model => model.Id);
<br/>

@Html.LabelFor(model => model.Nome);


@Html.EditorFor(model => model.Nome);
<br/>

@Html.LabelFor(model => model.Email);


@Html.TextBoxFor(model => model.Email);
<br/>

@Html.ActionLink("Retornar", "Index", "Home");

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.

Basicamente, é um formulário na página index.cshtml e ao clicar em salvar, a view


result.cshtml exibirá os resultados desse formulário. Esse formulário é do tipo cliente:
namespace Proj05_TagHelper.Models
{
   public class Cliente
  {
       public int Id { get; set; }
       public string Nome { get; set; }
       public bool Fiel { get; set; }
  }
}

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/>

<form asp-controller="Home" asp-action="Index" method="post">

   <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.

Essas tags são:

Tag Observação

Required Campo obrigatório.

O campo não pode conter mais de 50


StringLength(50)
caracteres.

DataType(DataType.Tipo) Esse campo é um tipo de dado específico.

Valida o campo através de uma expressão


RegularExpression
regular.

Range(1, 100) Esse campo só aceita valores entre 1 e 100.

Compare("AtributeName") Compare Validar se 2 atributos são iguais.

Permite mudar o nome que será criado no


Table("TabelaName")
banco de dados.

Permite melhorar o mapeamento de atributos


para o banco de dados. É possível mudar o
[Column(“Nome")]
nome, tipo e ordem em que o atributo
aparecerá na tabela.

Permite melhorar o mapeamento de atributos


para o banco de dados. É possível mudar o
[Key]
nome, tipo e ordem em que o atributo
aparecerá na tabela.
p
Tag Observação
[Display(Name = "Nome")] Exibe o que texto que você quiser na view.

[DisplayFormat(ApplyFormatInEditMode
Exibe o que texto no formato que você quiser.
= true, DataFormatString = "
Nesse caso, formatando um campo de data.
{0:dd/MM/yyyy}")]

Informa se os dados da chave primária serão


fornecidos pelo usuário ou gerados pelo bd, ou
até mesmo, pelo identity. Para isso, temos as
seguintes opções do DatabaseGeneratedOption:
[DatabaseGenerated( DatabaseGeneratedOption.None: Gerada pelo
DatabaseGeneratedOption.None)] usuário.
DatabaseGeneratedOption.Identity: Gerado pelo
Identity.
DatabaseGeneratedOption.Computed: Gerado
pelo banco de dados.

[NotMapped] Não mapeia o atributo para o banco de dados.

Quase todas elas possuem o atributo ErrorMessage que recebe uma string que contém a
mensagem caso o erro acontecer.

Para demostrar, iremos criar uma model chamado Pessoa.cs :

using System;
using System.ComponentModel.DataAnnotations;

namespace Validacao.Models
{
   public class Pessoa
  {
      [Key]
       public int Id { get; set; }

      [Required(ErrorMessage = "Campo obrigatório.")]


      [StringLength(50, ErrorMessage = "Contém mais de 50 caracteres.")]
       public string Nome { get; set; }

      [Required(ErrorMessage = "Campo obrigatório.")]


      [Range(1, 90, ErrorMessage = "Idade deve ser entre 1 e 90")]
      [Column("Age")]
       public int Idade { get; set; }

      [Required(ErrorMessage = "Campo obrigatório.")]


      [DataType(DataType.Date)]
      [Display(Name = "Data de nascimento")]
      [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "
{0:dd/MM/yyyy}")]
       public DateTime Nascimento { get; set; }

      [Required(ErrorMessage = "Campo obrigatório.")]


      [DataType(DataType.EmailAddress)]
      [StringLength(50, ErrorMessage = "Email muito extenso.")]
       public string Email { get; set; }
      [Required(ErrorMessage = "Campo obrigatório.")]
      [DataType(DataType.EmailAddress)]
      [StringLength(50, ErrorMessage = "Email muito extenso.")]
      [Compare("Email", ErrorMessage = "Email não confere")]
      [Display(Name = "Confirme o email")]
      [NotMapped]
       public string ConfirmedEmail { get; set; }
  }
}

O campo DataType recebe um objeto do tipo DataType.Tipo , onde existem os seguintes


tipos:

Tipo Descrição

CreditCard Cartão de Crédito

Currency Dinheiro

Custom Tipo de dados personalizados

Date Data

Datetime Data e hora do dia

Duration Tempo em que o objeto existe

EmailAddress Email

Html Arquivo Html

MultilineText Texto de várias linhas

Password Senha

PhoneNumber Número de telefone

PostalCode Código Postal

Text Representa o texto a ser exibido

Time Valor temporal

Upload Upload de arquivo

Url Representa um valor de url

ImageUrl Url para uma imagem

Se tentar criar uma nova pessoa sem informar nenhum dado:


Como todos os campos são obrigatórios, ocorre o erro em todos.

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.

No atributo a ser validado, deve-se utilizar a anotação Remote e, em seguida, o método e o


controller em que o método se encontra. Método no qual irá tratar de verificar no banco de
dados se existe o atributo:

[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; }

      [Required(ErrorMessage = "Campo obrigatório.")]


      [StringLength(50, ErrorMessage = "Contém mais de 50 caracteres.")]
       public string Nome { get; set; }

      [Required(ErrorMessage = "Campo obrigatório.")]


      [Range(1, 90, ErrorMessage = "Idade deve ser entre 1 e 90")]
       public int Idade { get; set; }

      [Required(ErrorMessage = "Campo obrigatório.")]


      [DataType(DataType.Date)]
       public DateTime Nascimento { get; set; }

      [Required(ErrorMessage = "Campo obrigatório.")]


      [DataType(DataType.EmailAddress)]
      [StringLength(50, ErrorMessage = "Email muito extenso.")]
       //REMOTE("Nome método", "Controlador")
      [Remote("EmailExistAsync", "Pessoas")]
       public string Email { get; set; }

      [Required(ErrorMessage = "Campo obrigatório.")]


      [DataType(DataType.EmailAddress)]
      [StringLength(50, ErrorMessage = "Email muito extenso.")]
      [Compare("Email", ErrorMessage = "Email não confere")]
       public string ConfirmedEmail { get; set; }
  }
}

E dentro do controlador PessoasController.cs, vamos adicionar o método:


//* Recebe por parâmetro oq foi digitado
//* Deve ser um método assincrono, retornando JsonResult.
public async Task<JsonResult> EmailExistAsync(string email)
{
   //Verifica se existe alguém com o mesmo email
   if(await _context.Pessoas.AnyAsync(p => p.Email == email))
  {
       //Se sim, retorna uma mensagem de erro dizendo que já existe.
  return Json("Email já existe.");
  }
//Se não, retorna um true para aceitar.
   return Json(true);
}

Para verificar se está funcionando, cadastramos duas pessoas ao banco de dados:

Tentaremos agora cadastrar com o mesmo email:


Ao tentar digitar um email já existe, exibe o json de error.

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.

A classe deve ter o nome Attribute no final.

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; }

      [Required(ErrorMessage = "Campo obrigatório.")]


      [StringLength(50, ErrorMessage = "Contém mais de 50 caracteres.")]
       public string Nome { get; set; }

      [Required(ErrorMessage = "Campo obrigatório.")]


      [Range(1, 90, ErrorMessage = "Idade deve ser entre 1 e 90")]
       //Adicionamos nossa validação.
      [Adulto]
       public int Idade { get; set; }

      [Required(ErrorMessage = "Campo obrigatório.")]


      [DataType(DataType.Date)]
       public DateTime Nascimento { get; set; }

      [Required(ErrorMessage = "Campo obrigatório.")]


      [DataType(DataType.EmailAddress)]
      [StringLength(50, ErrorMessage = "Email muito extenso.")]
       //REMOTE("Nome método", "Controlador")
      [Remote("EmailExistAsync", "Pessoas")]
       public string Email { get; set; }

      [Required(ErrorMessage = "Campo obrigatório.")]


      [DataType(DataType.EmailAddress)]
      [StringLength(50, ErrorMessage = "Email muito extenso.")]
      [Compare("Email", ErrorMessage = "Email não confere")]
       public string ConfirmedEmail { 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;
      }
  }
}

Ao tentar colocar uma idade menor que a permitida:

Aparece a mensagem retornada pelo ValidationResult de dentro do if.

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>

//FORMULÁRIO RESPONSÁVEL PELA PESQUISA, PODEMOS OBSERVAR QUE É FEITO NO post DO


MÉTODO index.
<form asp-controller="Pessoas" asp-action="Index" method="post">
//O método irá receber como argumento um elemento do tipo string com o nome
name="textProcurar". Ou seja, receberá um argumento string textProcurar.
   <input class="form-control" name="textProcurar"/>
   <button type="submit">Procurar</button>
</form>

<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>
 

E adicionaremos no controller, um método index do tipo post, PessoasController.cs :

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;

       public PessoasController(ApplicationContext context)


      {
           _context = context;
      }

      [HttpGet]
       public async Task<IActionResult> Index()
      {
           var applicationContext = _context.Pessoas.Include(p => p.Endereco);
           return View(await applicationContext.ToListAsync());
      }

//Aqui está o método post chamado pelo formulário de busca


      [HttpPost]
       public async Task<IActionResult> Index(string textProcurar)
      {
      //Verifica se o campo digitador é null ou empty
           if (!String.IsNullOrEmpty(textProcurar))
          {
          //Se não, retorna uma lista com a pesquisa.
               return View(await _context.Pessoas.Where(
              p => p.Nome.ToUpper()
              .Contains(textProcurar.ToUpper())).ToListAsync());
          }

           return View(await _context.Pessoas


          .Include(p => p.Endereco).ToListAsync());
      }

       public async Task<IActionResult> Detail................

A view index ficou da seguinte forma:


 

Se tentar pesquisar "Dias":

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);
      }
  }
}

Além disso, a view retornada deve estar na pasta Views/Shared/Components/Nome_da_View .


Que o no caso acima, deve-se estar na pasta Views/Shared/Components/Lista . E como
especificado, deve se chamar Lista.cshtml .

Se não fosse especificada o n0me da View, ela ia procurar na pasta mencionada um


default.cshtml .

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>

finalmente, nosso projeto tinha a seguinte estrutura de arquivos:


 

E agora? como importamos esse componente?

Existem três formas de importar esse componente:

Usando a ViewComponent na view


Usando o método Component.InvokeAsync("Nome", obj anom)
Usar a TagHelper (Não funcionou):
Usando no controlador

return ViewComponent("Lista", new{sequencia = "Laranja, limão, pêra"})

Podemos ver nossa index.cshtml usando o primeiro método:

<h1>Index</h1>

<!-- Usando Component.InvokeAsync("nome", objeto anonimo) -->


@{
  ViewData["Titulo"] = "Cidades";
}
@await Component.InvokeAsync("Lista", new { sequencia="Ilhéus,Itabuna,Porto
Seguro,Jequié"})

<!--
Usando taghelpers
É necessário adicionar ao _ViewImports.cshtml a linha:
@addTagHelper *, NomeProjeto
-->
@{
  ViewData["Titulo"] = "Times";
}
<vc:lista sequencia="Santos"></vc:lista>
 

E o nosso controlador HomeController.cs , poderemos ver a Action Frutas retornando um


ViewComponent:

using Microsoft.AspNetCore.Mvc;

namespace Proj07_ViewComponent.Controllers
{
   public class HomeController : Controller
  {
       public IActionResult Index(){
           return View();
      }

       public IActionResult Frutas()


      {
           ViewData["Titulo"] = "Frutas";
           return ViewComponent("Lista",
           new {sequencia = "Morango, Pera, Maça, Laranja"});
      }
  }
}
 

E agora um pequeno passo a passo para criar uma ViewComponent:

1. Criar pasta Components

2. Criar a classe ListaViewComponent que deriva de ViewComponent

3. Criar a pasta /Views/Shared/Components/Lista

4. Criar a view Lista.cshtml nesta pasta

5. Usar através dos métodos:

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:

@inject <serviço> <nome> , onde:

<serviço> - É a classe de serviço


<nome> - É o nome da injeção do serviço pelo qual poderemos acessar o método do
serviço.

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; }

       public Estado(string Nome, string Sigla)


      {
           this.Nome = Nome;
           this.Sigla = Sigla;
      }
  }
}

2. Registrar o serviço

Devemos registrar o serviços no Startup.cs no método ConfigureServices.cs . Para isso,


devemos entender que um serviço lida com o tempo de vida (quando cria/descarta o
objeto). E existem 4 modos de vida:

Modos Descrição

Um objeto do serviço é criado e fornecido para todas as requisições.


Singleton
Todas as requisições obtém o mesmo objeto.

Um objeto do serviço é criado para cada requisição. Cada requisição


Scoped
obtém uma nova instância do serviço.

Transient Um objeto do serviço é criado toda vez que um objeto for requisitado.

Você é responsável por criar um objeto do serviço. O framework de DI


Instance
então usa esse instância em um modo Singleton.

No nosso caso, escolhemos o Transient.

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?}");
          });
      }
  }
}

Adicionamos com Transient com services.AddTransient<EstadosService>(); .

 
3. Injetar o serviço na View

E finalmente injetamos no serviço com a sintax vista anteriormente.


index.cshtml :
@inject Proj08Inject.Services.EstadosService estados

<h1>Inject Dependency : Services</h1>

<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

É ilimitada o número de áreas.


Cada área possui seus próprios controladores, modelos e views.
Suporta múltiplos controladores com o mesmo nome, desde que tenham áreas diferentes.

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")]
.

Vamos abrir o arquivo BlogController.cs :

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

Para criar um route geral, basta:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)


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

   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.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)


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

   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?}");
  });
}

Nesse caso criamos a rota para Loja e Blog.

Para mais informações, clique aqui.

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:

<a asp-area="Blog" asp-controller="Home" asp-action="Index"/>

Se o asp-area for vazio, ou seja, asp-area="" , indica que a pasta de procura é o


Controllers principal da pasta raiz.

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 &copy;. Todos os direitos reservados.</p>
   </div>

</body>
</html>

Se estiver usando HtmlHelper, a sintax é: @Html.ActionLink("Produtos", "Index",


"Home", new{ area = "Produtos" }) . É passado via objeto anónimo.

Falando sobre _ViewStart e Layouts.

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.

Há a possibilidade também de usar um Layout setando todo o caminho dele.


 

Entity Framework Core (ORM)


O EFCore é uma ferrramenta ORM (Object-relational mapping) que permite ao desenvolvedor
trabalhar com dados relacionais na forma de objetos específico do domínio.

Ele elimina a necessidade da maior parte do código de acesso a dados que os desenvolvedores
normalmente precisam escrever.

O EF Core é uma versão totalmente nova baseado no EF6.x:

Reescrito a partir do zero.


Multiplataforma.
Modular e suporta diversos provedores: SQL Server, Mysql, PostgreeSql, Oracle, SqLite etc..
É open source e está disponível no github.
Pode ser usado em aplicações Windows Form, WPF, Console, ASP.NET, ASP.NET Core, WUP
etc.
Suporta as abordagens: Code First, Database First, Shadow Properties, Alternate Keys, etc.
Suporta a ferramenta de linha de comando NET CLI.

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.

O EntityFramework trabalha com duas abordagens:

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.

Deve-se usar a Database First somente quando já existir um banco de dados.

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.

Primeiro passo: Adicionando pacote do Entity


Framework Core
O primeiro passo é instalar o EFCore no projeto. Podemos através do NuGet procurar pelo
pacote Microsoft.EntityFrameworkCore.SqlServer:

A melhor opção é utilizar a mesma versão do ASP.NET Core. A versão utilizada é a 2.2.
 

Segunda passo: Definindo uma tabela do tipo


Aluno.
Vamos definir um modelo do tipo Aluno.cs .

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; }

       public DateTime Nascimento { get; set; }


  }
}

A seguir tabela de anotações:

Notação Descrição

[Required] Significa que o campo é obrigatório.

[StringLength(n)] Significa que a string terá tamanho n.

[Key] Chave primária

O nome da tabela no banco de dados vai ser Alunos. Caso não


mencionado, a tabela terá o nome da classe no plural, que nesse
[Table("Alunos")] caso, continuaria sendo Alunos.
É necessário using
System.ComponentModel.DataAnnotations.Schema; .

   

 
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; }

       public AlunoContext(DbContextOptions<AlunoContext> options)


      : base(options){
       
      }
  }
}

tendo um DbSet do tipo Aluno. E um construtor que recebe um


DbContextOptions<AlunoContext> e passa ele pra classe base.

Quarto passo: Definir uma string de conexão no


appsettings.json e aplicar o serviço.
Iremos definir uma string de conexão no arquivo appsettings.json :

{
 "ConnectionStrings": {
   "conexaoSQLServer": "Data Source=DESKTOP-88QLLPP;Initial
Catalog=EscolaDemo;Integrated Security=True;"
},
   
 "Logging": {
   "LogLevel": {
     "Default": "Warning"
  }
},
   
 "AllowedHosts": "*"
}

O nome deve ser obrigatoriamente ConnectionStrings e o nome da string de conexão se chama


conexaoSQLServer. Nela, o schema (Initial Catalog) foi chamado de EscolaDemo e o Data Source é
o nome da maquina.

APLICANDO O SERVIÇO NA Startup.cs :


Primeiramente o método Startup.cs deve conter um atributo IConfiguration e um construtor
que irá receber um IConfiguration e atribuí-lo ao atributo IConfiguration da classe.

public class Startup


{
   public IConfiguration Configuration { get; set; }

   public Startup(IConfiguration configuration)


  {
       Configuration = configuration;
  }

   public voi.......

Após isso, devemos configurar o serviço.

public void ConfigureServices(IServiceCollection services)


{
   services.AddDbContext<AlunoContext>
  ( options =>
options.UseSqlServer(Configuration.GetConnectionString("conexaoSQLServer"))
  );

   services.AddMvc();
}

O serviço é .AddDbContext<TipoContext>() , e dentro usando uma lambda expression options


=> options.UseSqlServer(Configuration.GetConnectionString("conexaoSQLServer")) onde
contém o nome da string de conexão conexaoSQLServer.

Finalmente, teremos a seguinte estrutura do projeto:


 

Quinto passo: Migrations


O migration é um controle de versionamento do banco de dados. Ele gera um código de
configuração do banco de dados baseado na DbContext e DbSet construído.

Para gerar o código de configuração, deve-se usar o migrations no Package Manager Console:

Existem alguns comandos utilizados para migrations:


Código Descrição

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.

remove-migration Remove a ultima ação do add-migration

update-database Vai aplicar as alterações pendentes no banco de dados

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.

E nesse código, terá as alterações necessárias para ser aplicadas ao banco:

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);
              });
      }

       protected override void Down(MigrationBuilder migrationBuilder)


      {
           migrationBuilder.DropTable(
               name: "Alunos");
      }
  }
}

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.

Agora iremos até o banco de dados ver se a tabela foi gerada:

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.

Alimentando banco de dados através do


Migrations.
Quando aplicamos um migration, é gerado o arquivo .cs como comentamos no tópico de Add-
Migration. Dentro desse arquivo gerado, há dois métodos override chamados up() e down() .
Nesses métodos contém as alterações do banco de dados conforme configurado nas entidades e
context. Esses métodos suportam códigos SQL, e será dessa forma que iremos incluir dados ao
banco pelo migrations.
Ao dar um migration sem alterações nas entidades/context, os métodos sobrecarregador up() e
down() serão vazios:

Após o Add-Migration SeedDatabase foi gerado o arquivo de nome


20190518182602_SeedDatabase.cs com os métodos override up e down vazios como
mencionados anteriormente.

Para adicionarmos via comando SQL, adicionamos usando o comando


migrationBuilder.Sql("Código sql") . Vamos adicionar três pessoas:

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')");
      }

//TAMBÉM DEVE CONTER ESSA LINHA DE CODIGO "DELETE FROM Alunos"


       protected override void Down(MigrationBuilder migrationBuilder)
      {
           migrationBuilder.Sql("DELETE FROM Alunos");
      }
  }
}

 
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.

No quarto passo, adicionamos o serviço services.AddDbContext<AlunoContext> . Sendo assim,


quando adicionado o serviço, se o Controller tiver um construtor, esse construtor recebe uma
instância do contexto. Exemplo:

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;

//O construtor recebe uma instancia do contexto AlunoContext e atribui


ao AlunoContext local.
       public HomeController(AlunoContext context)
      {
           _context = context;
      }

       public IActionResult Ind.............

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;

       public HomeController(AlunoContext context)


      {
           _context = context;
      }

       public IActionResult Index()


      {
           var alunos = _context.Alunos.ToList();
           return View(alunos);
      }

Adicionar
Para adicionar, iremos criar uma página de formulário [HttpGet] chamada Create.cshtml .

HomeController.cs : Método Create() .

[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>

<h4>Criar novo aluno</h4>


<hr />
<div class="row">
   <div class="col-md-4">
       <form asp-action="Create">
           <div asp-validation-summary="ModelOnly" class="text-danger"></div>
           <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="Create" class="btn btn-primary" />
           </div>
       </form>
   </div>
</div>

<div>
   <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
  @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
</body>
</html>

Já no método [HttpPost] , é recebido um objeto do tipo Aluno.

Para o método post, utilizamos duas notações necessárias:

HttpPost - Pra identificar que é o post.


ValidateAntiForgeryToken - Que verifica o token gerado para o formulário através do
ASP.NET Core. Verifica se o token corresponde com o gerado.

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){

//Verifica se o ModelState é válido


   if (ModelState.IsValid){
       _context.Add(aluno);
       _context.SaveChanges();

       return RedirectToAction(nameof(Index));
  }

return View(aluno);
}

A validação do modelo( ModelState.IsValid ) ocorre após o model binding e relata os


erros em que os dados não estão em conformidade com as regras de negócio (por
exemplo, um 0 é inserido em um campo que espera uma classificação entre 1 e 5).

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();
  }

   var aluno = _context.Alunos.SingleOrDefault(a => a.Id == id);

   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();
  }

   //Verifica se ModelState é válido


   if (ModelState.IsValid)
  {
       try
      {
           //Utiliza .Update para atualizar o objeto no banco de dados
           _context.Update(aluno);
           //Sempre o SaveChanges para poder persistir no banco de dados.
           _context.SaveChanges();
      }
       catch(DbUpdateConcurrencyException)
      {
           if (!AlunoExists(id))
          {
               return NotFound();
          }
           else
          {
               throw;
          }
      }
       return RedirectToAction(nameof(Index));
  }
   return View(aluno);
}

private bool AlunoExists(int id)


{
   return _context.Alunos.Any(e => e.Id == id);
}

Para a alteração, utilizamos o .Update(entidade) . Neste método, tratamos o erro de duas


pessoas estarem acessando a mesma entidade do bd, chamado
DbUpdateConcurrencyException .

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.

public IActionResult Delete(int? id)


{
   if(id != null){
       var aluno = _context.Alunos.SingleOrDefault(a => a.Id == id);
       _context.Alunos.Remove(aluno);
       _context.SaveChanges();

       return RedirectToAction(nameof(Index));
  }

return Content("Não foi possível remover.");


}

É 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() .

Relacionamento entre entidades


Para o relacionamento de entidades, vamos criar uma nova entidade chama TipoSocio:

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; }
  }
}

Além disso, devemos adicionar o DbSet dela ao Context:

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; }

       public AlunoContext(DbContextOptions<AlunoContext> options)


      : base(options)
      {
      }
  }
}

Agora, vamos atribuir o relacionamento a entidade Aluno.cs :

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; }

       public DateTime Nascimento { 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.

Mostrando dados relacionados na View


O EF Core permite que você use as propriedades de navegação em seu modelo para carregar
entidades relacionadas. Existe duas formas de carregamento das tabelas:

EagerLoading - É o mecanismo pelo qual uma associação, coleção ou atributo é carregado


imediatamente quando o objeto principal é carregado.
LazyLoading - Carregar informações sobre demanda. Esse mecanismo torna as entidades
mais leves, pois suas associações são carregadas apenas no momento em que o método
que disponibiliza o dado associativo é chamado. (Até o momento das aulas de Macoratti
ainda não estava disponível).

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; }

       public AlunoTipoSocioController(AlunoContext context)


      {
           _context = context;
      }

       public IActionResult Index()


      {
           var infoAluno = _context.Alunos.Include(tp => tp.TipoSocio);
           return View(infoAluno);
      }
  }
}

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.

A seguir, a view listando os dados:

@model IEnumerable<Aluno>

<h1>Informação de alunos e taxa de descontos</h1>

@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.

O FLIENT API define o mapeamento e a configuração via código no método override


OnModelCreating do DbContext. Dentro do método, podemos configurar cada entidade
através de um ModelBuilder passado por parâmetro para esse método.

Para exemplificar o uso de Fluent API, iremos criar três modelos:

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; }

       public virtual List<Pedido> Pedidos { get; set; }

       public virtual Endereco Endereco { 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; }

       public virtual Cliente Cliente { 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; }

       public virtual Cliente Cliente { get; set; }


       public int ClienteId { 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; }

       public ApplicationContext(DbContextOptions<ApplicationContext> options)


      : base(options) { }

//Aqui é o método OnModelCreating que deve receber um modelBuilder.


       protected override void OnModelCreating(ModelBuilder modelBuilder)
      {
      //Deve passar o modelBuilder para a classe base
           base.OnModelCreating(modelBuilder);

//CONFIGURANDO A ENTIDADE Cliente


//.ToTable("Nome") seta o nome da tabela no banco
           modelBuilder.Entity<Cliente>().ToTable("Clientes");
           //.HasKey(c => c.Id), seta a chave primária com a função lambda.
           modelBuilder.Entity<Cliente>().HasKey(c => c.Id);
           /*.Property(c => c.Atributo): Quando usamos o property, podemos
configurar cada propriedade do modelo. Nesse caso, configuramos:
          .HasMaxLenght() - Tamanho da string
          .IsRequired() - Atributo obrigatório
           */
           modelBuilder.Entity<Cliente>()
          .Property(c => c.Nome).HasMaxLength(200).IsRequired();
           modelBuilder.Entity<Cliente>()
          .Property(c => c.Telefone).HasMaxLength(20).IsRequired();

//CONFIGURANDO A ENTIDADE Pedido


           modelBuilder.Entity<Pedido>().ToTable("Pedidos");
           modelBuilder.Entity<Pedido>().HasKey(p => p.Id);
           modelBuilder.Entity<Pedido>()
          .Property(p => p.Item).HasMaxLength(100).IsRequired();
           modelBuilder.Entity<Pedido>()
          .Property(p => p.Quantidade).IsRequired();
           modelBuilder.Entity<Pedido>().Property(p => p.Preco).IsRequired();

//CONFIGURANDO A ENTIDADE Endereco


           modelBuilder.Entity<Endereco>().ToTable("Enderecos");
           modelBuilder.Entity<Endereco>().HasKey(e => e.Id);
           modelBuilder.Entity<Endereco>()
          .Property(e => e.Bairro).HasMaxLength(50).IsRequired();
           modelBuilder.Entity<Endereco>()
          .Property(e => e.Cidade).HasMaxLength(20).IsRequired();
           modelBuilder.Entity<Endereco>()
          .Property(e => e.Estado).HasMaxLength(50).IsRequired();
           modelBuilder.Entity<Endereco>()
          .Property(e => e.Rua).HasMaxLength(80).IsRequired();

      }
  }
}
Dentro do OnModelCreating, usamos o ModelBuilder para configurar as entidades. Com a
sintax:

modelBuilder.Entity<Modelo>() . A seguir, contém uma tabela de métodos para configurar a


entidades:

E com o Property, modelBuilder.Entity<Modelo>().Property(m => m.Atributo) podemos


configurar as propriedades com os seguintes métodos:

Com o property, podemos fazer chamadas de método encadeada. Exemplo:

modelBuilder.Entity<Endereco>()
.Property(e => e.Rua)
.HasMaxLength(80)
.IsRequired();
 

Depois de termos configurado todas as propriedades, precisamos configurar o relacionamento


no próximo tópico.

Relacionamento com FLUENT API


Podemos verificar entre as entidades, que há um relacionamento como citado anteriormente.
Verificamos 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.

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.

Nosso modelo segue a seguinte estrutura:

Um para um

O relacionamento entre Cliente e Endereco é de um para um. Ou seja:

Cliente tem um Endereco ( .HasOne(c => c.Endereco) ) e Endereco tem um Cliente


( .WithOne(e => e.Cliente) ).

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

Configuramos o relacionamento de 1 para 1. Onde a chave estrangeira( ClienteId ) vai


ficar na tabela Endereço.
 

Caso queira que que a chave estrangeira fique na tabela Cliente, deve seguir o passo abaixo:

Endereco tem um Cliente ( .HasOne(e => e.Cliente) ) e Cliente tem um Endereco


( .WithOne(c => c.Endereco) ).

modelBuilder.Entity<Endereco>()
  .HasOne(e => e.Cliente)
  .WithOne(c => c.Endereco)
  .HasForeignKey<Cliente>(c => c.EnderecoId);

Configuramos o relacionamento de 1 para 1. Onde a chave estrangeira ( EnderecoId ) vai


ficar na tabela Cliente.

Lembrando que a chave estrangeira do método 1 estava na entidade Endereco e na


debaixo está na entidade Cliente .

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; }

    public virtual Cliente Cliente { get; set; }


    //Foi adicionado o ? para receber null.
    public int? ClienteId { get; set; }
}
}

a partir disso, o Endereço não necessariamente precisa ter um Cliente.

Um para muitos

O relacionamento entre Cliente e Pedidos é de 1(um) para N, ou seja, um Cliente tem N


Pedido e um Pedido tem um(1) Cliente . Ou seja:

Pedido tem um Cliente ( .HasOne(p => p.Cliente) ) e Cliente tem vários


Pedido ( .WithMany(c => c.Pedidos) ).

modelBuilder.Entity<Pedido>()
  .HasOne(p => p.Cliente)
  .WithMany(c => c.Pedidos)
  .OnDelete(DeleteBehavior.Restrict);

Configuramos o relacionamento de um para muitos. Onde a chave estrangeira


( ClienteId ) vai ficar na tabela Pedidos.

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);

As duas formas de atribuir, seja de um-para-muitos ou muitos-para-um resulta na


mesma coisa.

 
 

Usando EF Core com CLI e VSCODE


Para usar o EF Core, é necessário adicionar dois pacotes ao projeto:

Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Design

Pacote pode ser adicionado no CLI com o comando:

dotnet add package Microsoft.EntityFrameworkCore.SqlServer

Além disso, deve referenciar no arquivo .csproj (DotNetCliToolReference):

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>

 <!-- Elementos adicionados -->


 <ItemGroup>
   <DotNetCliToolReference
 Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0"/>
   <DotNetCliToolReference
Include="Microsoft.EntityFrameworkCore.Tools.DotNet"  Version="2.2.0"/>
 </ItemGroup>

</Project>

Foram adicionados no final do arquivo .csproj as duas referência de DotNetCliToolReference.

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.

Os códigos para Migrations e Database são:

dotnet ef migrations add Nome


dotnet ef database update

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.

Instalar pacote Microsoft.EntityFrameworkCore.Proxies : O proxies é o pacote


responsável por implementar o LazyLoading.

Fazer a configuração na Startup.cs para que o projeto utilize o LL: Adicionar ao serviço do
DbContext o método .UseLazyLoadingProxies() .

Startup.cs no método ConfigureServices :

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.

Já na pasta Data contém um ApplicationDbContext.cs que contém a configuração, ou melhor,


o DbContext dos dados que vai ser aplicado ao banco de dados.

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();

           //E, devemos utilizar o UseAuthentication:


           app.UseAuthentication();

           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": "*"
}

Exibição somente para logados: [Authorize],


[AllowAnonymous] e User.Identity.IsAuthenticated.
Podemos limitar alguns controllers ou views para que só sejam acessadas caso o usuário esteja
logado com sua conta, ou seja, esteja autenticado. Existem duas formas de fazer isso, uma é
através do uso de User.Identity.IsAuthenticated que retorna True se o usuário estiver
autenticado.

Exemplo:

ContatoController :

using IdentityMacoratti.Data;
using Microsoft.AspNetCore.Mvc;
using System.Linq;

namespace IdentityMacoratti.Controllers
{
   public class ContatoController : Controller
  {
       private ApplicationDbContext _context;

       public ContatoController(ApplicationDbContext contexto)


      {
           _context = contexto;
      }

       public IActionResult Index()


      {
           if (User.Identity.IsAuthenticated)
          {
               var contatos = _context.Contatos.ToList();
               return View(contatos);
          }
           else
          {
               return RedirectToAction("Index", "Home");
          }
           
      }
  }
}

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;

       public ContatoController(ApplicationDbContext contexto)


      {
           _context = contexto;
      }

[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.

Podemos ainda, utilizar o decorador no controlador, onde todos os métodos do controlador é


restringido, podendo somente acessar se estiver logado:

ContatoController :

using IdentityMacoratti.Data;
using Microsoft.AspNetCore.Mvc;
using System.Linq;

namespace IdentityMacoratti.Controllers
{
[Authorize]
   public class ContatoController : Controller
  {
       private ApplicationDbContext _context;

       public ContatoController(ApplicationDbContext contexto)


      {
           _context = contexto;
      }
       public IActionResult Index()
      {
          var contatos = _context.Contatos.ToList();
          return View(contatos);
           
      }
  }
}

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;

       public ContatoController(ApplicationDbContext contexto)


      {
           _context = contexto;
      }
       
      [AllowAnonymous]
       public IActionResult Index()
      {
          var contatos = _context.Contatos.ToList();
          return View(contatos);
           
      }
  }
}

Alteração da configuração padrão do Identity


O Identity padroniza algumas coisas no sistema de login, como por exemplo o formato do
password, bloqueio de usuário, validações e etc.

Através da classe Startup.cs conseguimos modificar alguns dessas políticas.

Esses políticas são editadas dentro do método ConfigureServices na configuração do


Identity aos serviços. São configuradas dentro do AddDefaultIdentity com uma função
lambda.

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:

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.AddDefaultIdentity<IdentityUser>( options =>


  {
       //Politicas de senha
       options.Password.RequiredLength = 10;
       options.Password.RequireLowercase = false;
       options.Password.RequireUppercase = false;
       options.Password.RequireNonAlphanumeric = false;
       options.Password.RequireDigit = false;
       options.Password.RequiredUniqueChars = 1;
  })
  .AddDefaultUI(UIFramework.Bootstrap4)
  .AddEntityFrameworkStores<ApplicationDbContext>();

   

   services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

options.Password.RequiredLength é o tamanho mínimo da senha


options.Password.RequireLowercase por padrão é true, ou seja, obrigatório os
minúsculos
options.Password.RequireUppercase por padrão é true, ou seja, obrigatório os
maiúsculos
options.Password.RequireNonAlphanumeric por padrão é true, ou seja, torna obrigatório
o uso de símbolos
options.Password.RequireDigit por padrão é true, ou seja, é obrigatório ter dígitos na
senha
options.Password.RequiredUniqueChars por padrão ela é 1. Determina a quantidade de
caracteres distintos na senha, ou seja, quantos caracteres diferente no mínimo deve ter.

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.

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.AddDefaultIdentity<IdentityUser>( options =>


  {
       //Bloqueio do usuario
       options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(3);
       options.Lockout.MaxFailedAccessAttempts = 3;
       options.Lockout.AllowedForNewUsers = true;
  })
  .AddDefaultUI(UIFramework.Bootstrap4)
  .AddEntityFrameworkStores<ApplicationDbContext>();

   

   services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

options.Lockout.DefaultLockoutTimeSpam Determina a quantidade de tempo que o


usuário fique bloqueado ao ocorrer um bloqueio ( TimeSpan.FroMinutes(qtdad) ). Padrão é
5 minutos.
options.Lockout.MaxFailedAccessAttempts a quantidade de acesso mal sucedido para o
bloqueio.
options.Lockout.AllowedForNewUsers determina se um novo usuário poderá ser
bloqueado ou não na aplicação. O default é true.

O padrão é 5 minutos após 5 tentativas.

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.AddDefaultIdentity<IdentityUser>( options =>


  {
       //Configuração de login
       options.SignIn.RequireConfirmedEmail = true;
       options.SignIn.RequireConfirmedPhoneNumber = true;
  })
  .AddDefaultUI(UIFramework.Bootstrap4)
  .AddEntityFrameworkStores<ApplicationDbContext>();

   

   services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

options.SignIn.RequireConfirmedEmail , por padrão é false. Significa que não requer um


email confirmado para iniciar sessão.
options.SignIn.RequireConfirmedPhoneNumber , por padrão é false. Significa que não
requer um número de telefone confirmado para iniciar sessão.

Configuração de validação do usuário


Podemos obrigar que cada usuário possua um email exclusivo ou citar com que caracteres são
composto o nome de usuário.

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.AddDefaultIdentity<IdentityUser>( options =>


  {
       //Configurações de validação do usuário
       options.User.RequireUniqueEmail = true;
       //options.User.AllowedUserNameCharacters = "abcdef..";
  })
  .AddDefaultUI(UIFramework.Bootstrap4)
  .AddEntityFrameworkStores<ApplicationDbContext>();

   

   services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

options.User.RequireUniqueEmail significa que requer um único email, não poderá ter


duas contas com o mesmo email. Por padrão é false.

options.User.AllowedUserNameCharacters Define os caracteres permitidos para a


composição da senha. Pode atribuir uma string a essa variável com os dígitos que o usuário
pode conter, exemplo:

options.User.AllowedUserNameCharacters = "abcdefg123456789"

significa que o usuário só pode ser preenchido com algum desses caracteres informados.

Roles (Função ou Cargo)


Nesse momento iremos verificar como funciona as Roles, mas, oque é Roles? Roles representa
um perfil de usuário, cargos dado a certo usuário dentro do sistema, onde esse cargo tem
recursos disponíveis diferentes de outros.

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:

Como fazer a verificação de autorização usando Role


Fazemos a verificação através do atributo/modificador [Authorize] passando por parâmetro o
atributo Role

[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
{
....
}

ou ainda, misturar o [Authorize] entre controlador e suas actions:

[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.

Como atribuir uma Role a um usuário


Sabemos como limitar métodos e controladores ao acesso a diferentes Roles, agora só
precisamos atribuir as Roles aos usuários. Obviamente, a melhor forma de atribuir um Role é
através de um CRUD, porém, iremos ver aqui como atribuir na hora da criação do usuário
( Account/Register ) e no Startup.cs .

A partir do ASP.NET Core 2.1, é necessário adicionar o serviço na Startup.cs no


ConfigureServices :

services.AddDefaultIdentity<IdentityUser>()
.AddRoles<IdentityRole>() //Configurando Service Role
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();

Atribuindo na hora do registro

Para atribuir na hora do registro, é necessário acessar a controlador da view Account/register .


Primeiro precisamos adicionar a instância de um serviço chamado
RoleManager<IdentityRole> no controlador do Register :

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;
      }
      ....
      ....
      ....
}

Após declararmos um atributo do tipo RoleManager<IdentityRole> e através do


construtor receber o serviço e atribuir a ela, iremos configurar o método OnPostAsync() .

Ir até o método OnPostAsync() , que é o método Post do Register e:

public async Task<IActionResult> OnPostAsync(string returnUrl = null)


{
   returnUrl = returnUrl ?? Url.Content("~/");
   if (ModelState.IsValid)
  {
       //Cria um novo usuário
       var user = new IdentityUser
      { UserName = Input.Email, Email = Input.Email };
       //Atribuir esse novo usuário ao banco _userManager
       var result = await _userManager.CreateAsync(user, Input.Password);
       
       //Se for criado com sucesso:
       if (result.Succeeded)
      {
           //Verifica se o Role "Cliente" existe.
           //roleManager.RoleExistsAsync("Cliente") retorna true se existir
e false se não existir
           if(!await _roleManager.RoleExistsAsync("Cliente"))
          {
               //Se não existir ele cria o novo Role com .CreateAsync()
               await _roleManager.CreateAsync(new IdentityRole("Cliente"));
          }

           //Depois de criar o Role, atribui ele usando _userManager


passando por parâmetro o Role e o usuário que vai receber o Role Cliente.
           await _userManager.AddToRoleAsync(user, "Cliente");

           _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>.");

           await _signInManager.SignInAsync(user, isPersistent: false);


           return LocalRedirect(returnUrl);
      }
       
       foreach (var error in result.Errors)
      {
           ModelState.AddModelError(string.Empty, error.Description);
      }
  }

   // If we got this far, something failed, redisplay form


   return Page();
}

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:

_roleManager.ExistsAsync("Role") : Verificamos se existe o Role no banco de dados.


_roleManager.CreateAsync(new IdentityRole("Role")) : Criamos um Role.

E que o IdentityRole é o modelo das Roles.

 
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:

_userManager.AddToRoleAsync(user, "Role") : Para atribuir um Role a um usuário.

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.

Sempre é necessário adicionar o serviço dos Roles no ConfigureServices .

Para iniciar, vamos adicionar a classe Startup.cs um método novo:

private void CreateRoles(IServiceProvider serviceProvider)


{
   //ADQUIRINDO INSTÂNCIA DO SERVIÇO
   var roleManager = serviceProvider
      .GetRequiredService<RoleManager<IdentityRole>>();
   var userManager = serviceProvider
      .GetRequiredService<UserManager<IdentityUser>>();

   Task<IdentityResult> roleResult;
   string email = "admin@admin.com"; //EMAIL

   //VERIFICANDO SE O Role Admin existe, se não, cria dentro do IF


   Task<bool> hasAdminRole = roleManager.RoleExistsAsync("Admin");
   hasAdminRole.Wait();

   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();

   if (testUser.Result == null)


  {
       //CRIA E SETA INFORMAÇÕES DA CONTA ADMIN
       IdentityUser administrator = new IdentityUser();
       administrator.Email = email;
       administrator.UserName = email;

       // CRIA O USUÁRIO E ATRIBUI UMA SENHA "123456"


       Task<IdentityResult> newUser = userManager
          .CreateAsync(administrator, "123456");
       newUser.Wait();

       //SE O USUARIO FOR CRIADO COM SUCESSO, ATRIBUI O PERFIL "Admin"


       if (newUser.Result.Succeeded)
      {
           Task<IdentityResult> newUserRole = userManager
              .AddToRoleAsync(administrator, "Admin");
           newUserRole.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 :

public void Configure(


   IApplicationBuilder app,
   IHostingEnvironment env,
   IServiceProvider serviceProvider //Recebendo IServiceProvider
){

   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);
}

Precisamos receber um objeto do tipo IServiceProvider por parâmetro na configure


para passar para o método CreateRoles .

Personalizando a tabela de usuário: AspNetUsers


Para personalizar a tabela de usuário, é necessário fazer algumas alterações no projeto. A tabela
usuário é do tipo IdentityUser, e para personalizar, é necessário a criação de um novo tipo
derivado do IdentityUser, ou seja, utilizar a IdentityUser como herança.

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; }
  }
}

Ou seja, na tabela AspNetUsers vai ter o campo Endereco_completo.

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)
      { }

       protected override void OnModelCreating(ModelBuilder builder)


      {
           base.OnModelCreating(builder);
//CONFIGURANDO O NOVO ATRIBUTO NO BANCO
           builder.Entity<ApplicationUser>().Property(a => a.Endereco_completo)
              .HasMaxLength(120)
              .IsRequired();
      }
  }
}

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 .

Finalmente configuramos e estendemos a classe IdentityUser para a ApplicationUser. Agora,


precisamos adicionar o campo Endereco_completo a página de registro de usuário. Para isso,
iremos gerar a página Register com scaffounding e fazer as seguintes alterações:

No arquivo .cs :

Adicionar o campo Endereco_completo ao InputModel

Adicionar o campo Endereco_completo na criação do ApplicationUser no método post.

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; }

       public string ReturnUrl { get; set; }

       public class InputModel


      {
          [Required]
          [EmailAddress]
          [Display(Name = "Email")]
           public string Email { 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; }

//ADICIONANDO ENDEREÇO AO INPUT MODEL


          [Required]
          [Display(Name = "Endereço Completo")]
           public string Endereco_completo { get; set; }
      }

       public void OnGet(string returnUrl = null)


      {
           ReturnUrl = returnUrl;
      }

       public async Task<IActionResult> OnPostAsync(string returnUrl =


null)
      {
           returnUrl = returnUrl ?? Url.Content("~/");
           if (ModelState.IsValid)
          {
               var user = new ApplicationUser {
              UserName = Input.Email,
              Email = Input.Email,
              //ADICIONANDO O CAMPO AO ApplicationUser CRIADO.
              Endereco_completo = Input.Endereco_completo};
             
               var result =
              await _userManager.CreateAsync(user, Input.Password);
               if (result.Succeeded)
              {
                   _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>.");

                   await _signInManager.SignInAsync(user, isPersistent:


false);
                   return LocalRedirect(returnUrl);
              }
               foreach (var error in result.Errors)
              {
                   ModelState.AddModelError(string.Empty,
error.Description);
              }
          }

           // If we got this far, something failed, redisplay form


           return Page();
      }
  }
}

No arquivo razor .cshtml :

Adicionar mais um campo no formulário.

@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>

           <!-- Adicionando um novo campo para Endereco_completo -->


           <div class="form-group">
               <label asp-for="Input.Endereco_completo"></label>
               <input asp-for="Input.Endereco_completo"
                      class="form-control" />
               <span asp-validation-for="Input.Endereco_completo"
class="text-danger"></span>
           </div>

           <button type="submit" class="btn btn-primary">Register</button>


       </form>
   </div>
</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;

       public HomeController(UserManager<ApplicationUser> userManager)


      {
           _userManager = userManager;
      }
       public IActionResult Index()
      {
           return View();
      }

       public IActionResult Privacy()


      {
           return View();
      }

/*Método responsável po apresentar a View.*/


      [Authorize]
       public IActionResult Endereco()
      {
           var Usuario = _userManager.GetUserAsync(HttpContext.User).Result;
           return View(Usuario);
      }

      [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None,


NoStore = true)]
       public IActionResult Error()
      {
           return View(new ErrorViewModel
          { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier
});
      }
  }
}

Endereco.cshtml :

@model ApplicationUser
@{
   ViewData["Title"] = "Endereco";
}

<h1>Endereco</h1>

<p>@Model.Endereco_completo</p>

Relacionamento com a tabela AspNetUsers


Iremos aplicar o relacionamento com a tabela AspNetUsers. Para isso, iremos fazer o seguinte
relacionamento:
Onde um usuário tem n contatos e cada contato há um único usuário.

Para isso, iremos criar a classe Contatos.cs :

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; }
  }
}

Agora que temos as duas classes, precisamos aplicar ao ApplicationDbContext o DbSet de


Contato.cs e configurá-lo no OnModelCreating , além de configurar o relacionamento:

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)
      { }

       protected override void OnModelCreating(ModelBuilder builder)


      {
           base.OnModelCreating(builder);
           
//CONFIGURANDO A TABELA CONTATOS
           builder.Entity<Contato>().ToTable("Contatos");
           builder.Entity<Contato>().HasKey(c => c.Id);
           builder.Entity<Contato>()
          .Property(c => c.Nome).HasMaxLength(95).IsRequired();

//CONFIGURANDO O RELACIONAMENTO: Um para muitos


           builder.Entity<Contato>()
              .HasOne(c => c.ApplicationUser)
              .WithMany(au => au.Contatos);
      }
  }
}

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);
      }

       public async Task<IActionResult> Delete(int? id)


      {
           if (id == null)
          {
               return NotFound();
          }

           var contato = await _context.Contatos


              .FirstOrDefaultAsync(m => m.Id == id);
           if (contato == null)
          {
               return NotFound();
          }

           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 :
 

Banco de dados, tabela Contatos:

Tabela AspNetUsers:

Você também pode gostar