Você está na página 1de 2068

Table of Contents

Introdução
Introdução
Criar um aplicativo Web
Criar uma API Web
Tutoriais
Criar um aplicativo Web de Páginas do Razor
Introdução a Páginas do Razor
Adicionar um modelo
Páginas do Razor geradas por scaffolding
LocalDB do SQL Server
Atualizar as páginas
Adicionar pesquisa
Adicionar um novo campo
Adicionar validação
Carregar arquivos
Criar um aplicativo Web do MVC
Introdução
Adicionar um controlador
Adicionar uma exibição
Adicionar um modelo
Trabalhar com o SQL Server LocalDB
Exibições e métodos do controlador
Adicionar pesquisa
Adicionar um novo campo
Adicionar validação
Examinar os métodos Details e Delete
Criar APIs da Web
Criar uma API Web no Visual Studio Code
Criar uma API Web no Visual Studio para Mac
Criar uma API Web no Visual Studio para Windows
Criar serviços de back-end para aplicativos móveis nativos
Páginas de Ajuda usando o Swagger
Acesso a dados – com EF Core
Acesso a dados – com páginas do Razor e EF Core
Acesso a dados – MVC com EF Core
Tutoriais de plataforma cruzada
Aplicativo Web de Páginas do Razor no macOS
Aplicativo Web de Páginas Razor com o VSCode
Aplicativo Web MVC com o Visual Studio para Mac
Aplicativo Web MVC com o Visual Studio Code no macOS ou no Linux
API Web com o Visual Studio para Mac
API Web com o Visual Studio Code
Criar serviços de back-end para aplicativos móveis
Conceitos básicos
Inicialização de aplicativos
Injeção de dependência (serviços)
Middleware
Middleware
Middleware de fábrica
Middleware baseado em fábrica com contêiner de terceiros
Trabalhar com arquivos estáticos
Roteamento
Middleware de regravação de URL
Trabalhar com vários ambientes
Configuração e opções
Configuração
Opções
Registro em log
Fazendo log com o LoggerMessage
Tratar erros
Provedores de arquivo
Hospedagem
Estado de sessão e de aplicativo
Servidores
Kestrel
Módulo do ASP.NET Core
HTTP.sys
Globalização e localização
Configurar a localização do objeto portátil com Orchard Core
Iniciar solicitações HTTP
Recursos de solicitação
Tarefas em segundo plano com serviços hospedados
Primitives
Alterar tokens
OWIN (Open Web Interface para .NET)
WebSockets
Metapacote Microsoft.AspNetCore.All
Escolher entre o .NET Core e o .NET Framework
Escolher entre o ASP.NET Core e o ASP.NET
Páginas do Razor
Métodos de filtro para as páginas Razor
Criar uma biblioteca de classes Razor
Recursos de convenção de aplicativo e roteamento
SDK do Razor
MVC
Associação de modelos
Validação de modelo
Exibições
Sintaxe Razor
Exibir compilação
Layout
Auxiliares de marcação
Exibições parciais
Injeção de dependência em exibições
Exibir componentes
Controladores
Rotear para ações do controlador
Uploads de arquivo
Injeção de dependência em controladores
Controladores de teste
Avançado
Trabalhar com o modelo de aplicativo
Filtros
Áreas
Partes do aplicativo
Associação de modelos personalizada
API Web
Tipos de retorno de ação do controlador
Avançado
Formatadores personalizados
Formatar dados de resposta
Testar, depurar e solucionar problemas
Teste de unidade
Testes de integração
Teste de páginas Razor
Controladores de teste
Depuração remota
Depuração de instantâneo
Depurando de instantâneo no Visual Studio
Solução de problemas
Acesso a dados com o EF Core e o Azure
Introdução às páginas do Razor e ao EF Core usando o Visual Studio
Introdução ao ASP.NET Core e ao EF Core usando o Visual Studio
ASP.NET Core com EF Core – novo banco de dados
ASP.NET Core com EF Core – banco de dados existente
Introdução ao ASP.NET Core e ao Entity Framework 6
Armazenamento do Azure
Adicionando o Armazenamento do Azure Usando o Visual Studio Connected
Services
Introdução ao Armazenamento de Blobs e ao Visual Studio Connected Services
Introdução ao Armazenamento de Filas e ao Visual Studio Connected Services
Introdução ao Armazenamento de Tabelas e ao Visual Studio Connected Services
Desenvolvimento do lado do cliente
Usar o Gulp
Usar o Grunt
Gerenciar pacotes do lado do cliente com o Bower
Criar sites responsivos com o Bootstrap
Criando estilos em aplicativos com o LESS, Sass e Font Awesome
Agrupar e minificar
Usar Link do Navegador
Usar JavaScriptServices para SPAs
Usar os modelos de projeto do SPA
Modelo de projeto Angular
Modelo de projeto React
Modelo de projeto React with Redux
SignalR
Introdução
Introdução
Hubs
Cliente JavaScript
Publicar no Azure
Plataformas com suporte
Dispositivo móvel
Criar serviços de back-end para aplicativos móveis nativos
Hospedar e implantar
Hospedar um Serviço de Aplicativo do Azure
Publicar no Azure com o Visual Studio
Publicar no Azure com as ferramentas CLI
Implantação contínua no Azure com o Visual Studio e o Git
Implantação contínua no Azure com o VSTS
Solucionar problemas no ASP.NET Core no Serviço de Aplicativo do Azure
Hospedar no Windows com o IIS
Solucionar problemas do ASP.NET Core no IIS
Referência de configuração do Módulo do ASP.NET Core
Suporte IIS de tempo de desenvolvimento no Visual Studio para ASP.NET Core
Módulos do IIS com o ASP.NET Core
Hospedar em um serviço Windows
Hospedar em Linux com o Nginx
Hospedar em Linux com o Apache
Hospedar no Docker
Compilar imagens do Docker
Ferramentas do Visual Studio para Docker
Publicar em uma imagem do Docker
Configuração de balanceador de carga e de proxy
Perfis de publicação do Visual Studio
Estrutura de diretórios
Referência de erros comuns para o Serviço de Aplicativo do Azure e o IIS
Adicionar recursos do aplicativo usando uma configuração específica da plataforma
Segurança
Autenticação
Opções de autenticação de OSS da comunidade
Introdução ao Identity
Configurar o Identity
Configurar a Autenticação do Windows
Configurar o tipo de chave primária para o Identity
Provedores de armazenamento personalizados para o Identity
Habilitar a autenticação usando o Facebook, o Google e outros provedores externos
Autenticação de Web Services Federation
Confirmação de conta e recuperação de senha
Habilitar a geração de código QR no Identity
Autenticação de dois fatores com SMS
Usar a autenticação de cookie sem o Identity
Azure Active Directory
Proteger aplicativos ASP.NET Core com o IdentityServer4
Proteger aplicativos ASP.NET Core com a Autenticação do Serviço de Aplicativo do
Azure (autenticação fácil)
Contas de usuários individuais
Autorização
Introdução
Criar um aplicativo com os dados do usuário protegidos por autorização
Autorização de páginas Razor
Autorização simples
Autorização baseada em função
Autorização baseada em declarações
Autorização baseada em política
Injeção de dependência em manipuladores de requisitos
Autorização baseada em recursos
Autorização baseada em exibição
Limitar a identidade por esquema
Proteção de dados
Introdução à proteção de dados
Introdução às APIs de proteção de dados
APIs de consumidor
Configuração
APIs de extensibilidade
Implementação
Compatibilidade
Impor o HTTPS
Armazenamento seguro dos segredos do aplicativo no desenvolvimento
Provedor de configuração do Azure Key Vault
Falsificação antissolicitação
Prevenir ataques de redirecionamento abertos
Evitar scripts entre sites
Habilitar o CORS (Solicitações Entre Origens)
Compartilhar cookies entre aplicativos
Desempenho
Respostas de cache
Cache na memória
Trabalhar com um cache distribuído
Cache de resposta
Middleware de cache de resposta
Middleware de compactação de resposta
Migração
ASP.NET para ASP.NET Core 1.x
Configuração
Autenticação e identidade
API Web
Módulos HTTP para middleware
ASP.NET para ASP.NET Core 2.0
ASP.NET Core 1.x para 2.0
Autenticação e identidade
Referência de API
Notas de versão 2.0
Notas de versão 1.1
Notas de versão anteriores
Documentos do VS 2015/project.json
Contribuir
Introdução ao ASP.NET Core
10/04/2018 • 6 min to read • Edit Online

Por Daniel Roth, Rick Anderson e Shaun Luttin


O ASP.NET Core é uma estrutura de software livre, de multiplaforma e alto desempenho para a criação de
aplicativos modernos conectados à Internet e baseados em nuvem. Com o ASP.NET Core, você pode:
Compilar aplicativos e serviços Web, aplicativos IoT e back-ends móveis.
Usar suas ferramentas de desenvolvimento favoritas no Windows, macOS e Linux.
Implantar na nuvem ou local.
Executar no .NET Core ou no .NET Framework.

Por que usar o ASP.NET Core?


Milhões de desenvolvedores usaram (e continuam usando) o ASP.NET 4.x para criar aplicativos Web. O ASP.NET
Core é uma reformulação do ASP.NET 4.x, com alterações de arquitetura que resultam em uma estrutura mais
enxuta e modular.
O ASP.NET Core oferece os seguintes benefícios:
Uma história unificada para a criação da interface do usuário da Web e das APIs Web.
Integração de estruturas modernas do lado do cliente e fluxos de trabalho de desenvolvimento.
Um sistema de configuração pronto para a nuvem, baseado no ambiente.
Injeção de dependência interna.
Um pipeline de solicitação HTTP leve, modular e de alto desempenho.
A capacidade de hospedar no IIS, Nginx, Apache, Docker ou hospedar em seu próprio processo.
Controle de versão do aplicativo do lado a lado ao direcionar .NET Core.
Ferramentas que simplificam o moderno desenvolvimento para a Web.
A capacidade de criar e executar no Windows, macOS e Linux.
De software livre e voltado para a comunidade.
O ASP.NET Core é fornecido inteiramente como pacotes NuGet. Os pacotes NuGet permitem otimizar o
aplicativo para incluir somente as dependências necessárias. Na verdade, aplicativos do ASP.NET Core 2.x
direcionando o .NET Core só exigem um único pacote de NuGet. Os benefícios de uma área de superfície menor
do aplicativo incluem maior segurança, manutenção reduzida e desempenho aprimorado.

Compilar APIs Web e uma interface do usuário da Web usando o


ASP.NET Core MVC
O ASP.NET Core MVC fornece recursos que ajudam você a compilar APIs Web e aplicativos Web:
O padrão MVC (Model-View -Controller) ajuda a tornar as APIs Web e os aplicativos Web testáveis.
As Páginas Razor (novidade no ASP.NET Core 2.0) é um modelo de programação baseado em página que
torna a criação da interface do usuário da Web mais fácil e produtiva.
A marcação Razor fornece uma sintaxe produtiva para Páginas Razor e as Exibições do MVC.
Os Auxiliares de Marcação permitem que o código do servidor participe da criação e renderização de
elementos HTML em arquivos do Razor.
O suporte interno para vários formatos de dados e negociação de conteúdo permite que as APIs Web
alcancem uma ampla gama de clientes, incluindo navegadores e dispositivos móveis.
A Associação de Modelos mapeia automaticamente os dados de solicitações HTTP para os parâmetros de
método de ação.
A Validação de Modelos executa automaticamente a validação do cliente e do servidor.

Desenvolvimento do lado do cliente


ASP.NET Core integra-se perfeitamente com estruturas conhecidas do lado do cliente e bibliotecas, incluindo
Angular, React e Bootstrap. Para saber mais, consulte Desenvolvimento do lado do cliente.

ASP.NET Core direcionado para o .NET Framework


O ASP.NET Core pode ser direcionado para o .NET Core ou ao .NET Framework. Os aplicativos do ASP.NET Core
direcionados ao .NET Framework não são multiplataforma,— são executados somente no Windows. Não existem
planos para interromper o suporte ao direcionamento para .NET Framework no ASP.NET Core. Em geral, o
ASP.NET Core é composto de bibliotecas do .NET Standard. Aplicativos criados com o .NET Standard 2.0 são
executados em qualquer lugar com suporte para ele.
Há várias vantagens em direcionar para o .NET Core, e essas vantagens aumentam com cada versão. Algumas
vantagens do .NET Core em relação ao .NET Framework incluem:
Multiplataforma. É executado no Windows, no Linux e no macOS.
Desempenho aprimorado
Controle de versão lado a lado
Novas APIs
Código Aberto
Estamos trabalhando duro para diminuir a diferença de API entre o .NET Framework e o .NET Core. O Pacote de
Compatibilidade do Windows disponibilizou milhares de APIs exclusivas do Windows no .NET Core. Essas APIs
não estavam disponíveis no .NET Core 1.x.

Próximas etapas
Para obter mais informações, consulte os seguintes recursos:
Introdução a Páginas do Razor
Tutoriais do ASP.NET Core
Conceitos básicos do ASP.NET Core
O Community Standup semanal do ASP.NET aborda o progresso e os planos da equipe. Ele apresenta o novo
software de terceiros e blogs.
Introdução ao ASP.NET Core
31/01/2018 • 1 min to read • Edit Online

OBSERVAÇÃO
Essas instruções referem-se à última versão do ASP.NET Core. Deseja começar com uma versão anterior? Consulte a versão
1.1 deste tutorial.

1. Instale o .NET Core.


2. Crie um novo projeto .NET Core.
No macOS e no Linux, abra uma janela de terminal. No Windows, abra um prompt de comando. Insira o
seguinte comando:

dotnet new razor -o aspnetcoreapp

3. Execute o aplicativo.
Use os seguintes comandos para executar o aplicativo:

cd aspnetcoreapp
dotnet run

4. Navegue para http://localhost:5000


5. Abra Pages/About.cshtml e modifique a página para exibir a mensagem "Olá, mundo! O horário no
servidor é @DateTime.Now ":

@page
@model AboutModel
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"]</h2>
<h3>@Model.Message</h3>

<p>Hello, world! The time on the server is @DateTime.Now</p>

6. Navegue para http://localhost:5000/About e verifique as alterações.


Próximas etapas
Para obter tutoriais de introdução, consulte Tutoriais do ASP.NET Core
Para obter uma introdução aos conceitos e à arquitetura do ASP.NET Core, consulte Introdução ao ASP.NET Core
e Conceitos básicos do ASP.NET Core.
Um aplicativo ASP.NET Core pode usar a Biblioteca de Classes Base e o tempo de execução do .NET Core ou do
.NET Framework. Para obter mais informações, consulte Escolhendo entre o .NET Core e o .NET Framework.
Introdução a Páginas do Razor no ASP.NET Core
08/05/2018 • 31 min to read • Edit Online

Por Rick Anderson e Ryan Nowak


Páginas do Razor é um novo recurso do ASP.NET Core MVC que torna a codificação de cenários focados em
página mais fácil e produtiva.
Se você estiver procurando um tutorial que utiliza a abordagem Modelo-Exibição-Controlador, consulte a
Introdução ao ASP.NET Core MVC.
Este documento proporciona uma introdução a páginas do Razor. Este não é um tutorial passo a passo. Se você
achar que algumas das seções são muito avançadas, consulte a Introdução a Páginas do Razor. Para obter uma
visão geral do ASP.NET Core, consulte a Introdução ao ASP.NET Core.

Pré-requisitos
Install one of the following:
CLI tooling: Windows, Linux, or macOS: .NET Core SDK 2.0 or later
IDE/editor tooling
Windows: Visual Studio for Windows
ASP.NET and web development workload
.NET Core cross-platform development workload
Linux: Visual Studio Code
macOS: Visual Studio for Mac

Criando um projeto de Páginas do Razor


Visual Studio
Visual Studio para Mac
Visual Studio Code
CLI do .NET Core
Consulte a Introdução a Páginas do Razor para obter instruções detalhadas sobre como criar um projeto de
Páginas do Razor usando o Visual Studio.

Páginas do Razor
O Páginas do Razor está habilitado em Startup.cs:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Includes support for Razor Pages and controllers.
services.AddMvc();
}

public void Configure(IApplicationBuilder app)


{
app.UseMvc();
}
}

Considere uma página básica:

@page

<h1>Hello, world!</h1>
<h2>The time on the server is @DateTime.Now</h2>

O código anterior é muito parecido com um arquivo de exibição do Razor. O que o torna diferentes é a diretiva
@page . @page transforma o arquivo em uma ação do MVC – o que significa que ele trata solicitações diretamente,
sem passar por um controlador. @page deve ser a primeira diretiva do Razor em uma página. @page afeta o
comportamento de outros constructos do Razor.
Uma página semelhante, usando uma classe PageModel , é mostrada nos dois arquivos a seguir. O arquivo
Pages/Index2.cshtml:

@page
@using RazorPagesIntro.Pages
@model IndexModel2

<h2>Separate page model</h2>


<p>
@Model.Message
</p>

O modelo de página Pages/Index2.cshtml.cs:

using Microsoft.AspNetCore.Mvc.RazorPages;
using System;

namespace RazorPagesIntro.Pages
{
public class IndexModel2 : PageModel
{
public string Message { get; private set; } = "PageModel in C#";

public void OnGet()


{
Message += $" Server time is { DateTime.Now }";
}
}
}

Por convenção, o arquivo de classe PageModel tem o mesmo nome que o arquivo na Página do Razor com .cs
acrescentado. Por exemplo, a Página do Razor anterior é Pages/Index2.cshtml. O arquivo que contém a classe
PageModel é chamado Pages/Index2.cshtml.cs.
As associações de caminhos de URL para páginas são determinadas pelo local da página no sistema de arquivos. A
tabela a seguir mostra um caminho de Página do Razor e a URL correspondente:

CAMINHO E NOME DO ARQUIVO URL CORRESPONDENTE

/Pages/Index.cshtml / ou /Index

/Pages/Contact.cshtml /Contact

/Pages/Store/Contact.cshtml /Store/Contact

/Pages/Store/Index.cshtml /Store ou /Store/Index

Notas:
O tempo de execução procura arquivos de Páginas do Razor na pasta Pages por padrão.
Index é a página padrão quando uma URL não inclui uma página.

Escrevendo um formulário básico


Os recursos de Páginas do Razor são projetados para tornar fáceis os padrões comuns usados com navegadores
da Web. Associação de modelos, auxiliares de marcas e auxiliares HTML funcionam todos apenas com as
propriedades definidas em uma classe de Página do Razor. Considere uma página que implementa um formulário
básico "Fale conosco" para o modelo Contact :
Para as amostras neste documento, o DbContext é inicializado no arquivo Startup.cs.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesContacts.Data;

namespace RazorPagesContacts
{
public class Startup
{
public IHostingEnvironment HostingEnvironment { get; }

public void ConfigureServices(IServiceCollection services)


{
services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("name"));
services.AddMvc();
}

public void Configure(IApplicationBuilder app)


{
app.UseMvc();
}
}
}

O modelo de dados:
using System.ComponentModel.DataAnnotations;

namespace RazorPagesContacts.Data
{
public class Customer
{
public int Id { get; set; }

[Required, StringLength(100)]
public string Name { get; set; }
}
}

O contexto do banco de dados:

using Microsoft.EntityFrameworkCore;

namespace RazorPagesContacts.Data
{
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions options)
: base(options)
{
}

public DbSet<Customer> Customers { get; set; }


}
}

O arquivo de exibição Pages/Create.cshtml:

@page
@model RazorPagesContacts.Pages.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div>Name: <input asp-for="Customer.Name" /></div>
<input type="submit" />
</form>
</body>
</html>

O modelo de página Pages/Create.cshtml.cs:


using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;

namespace RazorPagesContacts.Pages
{
public class CreateModel : PageModel
{
private readonly AppDbContext _db;

public CreateModel(AppDbContext db)


{
_db = db;
}

[BindProperty]
public Customer Customer { get; set; }

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
return RedirectToPage("/Index");
}
}
}

Por convenção, a classe PageModel é chamada de <PageName>Model e está no mesmo namespace que a página.
A classe PageModel permite separar a lógica de uma página da respectiva apresentação. Ela define manipuladores
para as solicitações enviadas e os dados usados para renderizar a página. Esta separação permite gerenciar as
dependências da página por meio de injeção de dependência e realizar um teste de unidade nas páginas.
A página tem um método de manipulador OnPostAsync , que é executado em solicitações POST (quando um
usuário posta o formulário). Você pode adicionar métodos de manipulador para qualquer verbo HTTP. Os
manipuladores mais comuns são:
OnGet para inicializar o estado necessário para a página. Amostra de OnGet.
OnPost para manipular envios de formulário.

O sufixo de nomenclatura Async é opcional, mas geralmente é usado por convenção para funções assíncronas. O
código OnPostAsync no exemplo anterior tem aparência semelhante ao que você normalmente escreve em um
controlador. O código anterior é comum para as Páginas do Razor. A maioria dos primitivos MVC como associação
de modelos, validação e resultados da ação são compartilhados.
O método OnPostAsync anterior:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}

_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
return RedirectToPage("/Index");
}

O fluxo básico de OnPostAsync :


Verifique se há erros de validação.
Se não houver nenhum erro, salve os dados e redirecione.
Se houver erros, mostre a página novamente com as mensagens de validação. A validação do lado do cliente é
idêntica para aplicativos ASP.NET Core MVC tradicionais. Em muitos casos, erros de validação seriam
detectados no cliente e nunca enviados ao servidor.
Quando os dados são inseridos com êxito, o método de manipulador OnPostAsync chama o método auxiliar
RedirectToPage para retornar uma instância de RedirectToPageResult . RedirectToPage é um novo resultado de
ação, semelhante a RedirectToAction ou RedirectToRoute , mas personalizado para páginas. Na amostra anterior,
ele redireciona para a página de Índice raiz ( /Index ). RedirectToPage é descrito em detalhes na seção Geração de
URLs para páginas.
Quando o formulário enviado tem erros de validação (que são passados para o servidor), o método de
manipulador OnPostAsync chama o método auxiliar Page . Page retorna uma instância de PageResult . Retornar
Page é semelhante a como as ações em controladores retornam View . PageResult é o tipo de retorno padrão
para um método de manipulador. Um método de manipulador que retorna void renderiza a página.
A propriedade Customer usa o atributo [BindProperty] para aceitar a associação de modelos.

public class CreateModel : PageModel


{
private readonly AppDbContext _db;

public CreateModel(AppDbContext db)


{
_db = db;
}

[BindProperty]
public Customer Customer { get; set; }

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
return RedirectToPage("/Index");
}
}

Páginas do Razor, por padrão, associam as propriedades somente com verbos não GET. A associação de
propriedades pode reduzir a quantidade de código que você precisa escrever. A associação reduz o código usando a
mesma propriedade para renderizar os campos de formulário ( <input asp-for="Customer.Name" /> ) e aceitar a
entrada.

OBSERVAÇÃO
Por motivos de segurança, você deve optar por associar os dados da solicitação GET às propriedades do modelo de página.
Verifique a entrada do usuário antes de mapeá-la para as propriedades. Aceitar esse comportamento é útil quando você cria
recursos que contam com a cadeia de caracteres de consulta ou com os valores de rota.
Para associar uma propriedade às solicitações GET, defina a propriedade SupportsGet do atributo [BindProperty] como
true : [BindProperty(SupportsGet = true)]

A home page (Index.cshtml):

@page
@model RazorPagesContacts.Pages.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>Contacts</h1>
<form method="post">
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
</tr>
</thead>
<tbody>
@foreach (var contact in Model.Customers)
{
<tr>
<td>@contact.Id</td>
<td>@contact.Name</td>
<td>
<a asp-page="./Edit" asp-route-id="@contact.Id">edit</a>
<button type="submit" asp-page-handler="delete"
asp-route-id="@contact.Id">delete</button>
</td>
</tr>
}
</tbody>
</table>

<a asp-page="./Create">Create</a>
</form>

O arquivo code-behind Index.cshtml.cs:


using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;

namespace RazorPagesContacts.Pages
{
public class IndexModel : PageModel
{
private readonly AppDbContext _db;

public IndexModel(AppDbContext db)


{
_db = db;
}

public IList<Customer> Customers { get; private set; }

public async Task OnGetAsync()


{
Customers = await _db.Customers.AsNoTracking().ToListAsync();
}

public async Task<IActionResult> OnPostDeleteAsync(int id)


{
var contact = await _db.Customers.FindAsync(id);

if (contact != null)
{
_db.Customers.Remove(contact);
await _db.SaveChangesAsync();
}

return RedirectToPage();
}
}
}

O arquivo cshtml contém a marcação a seguir para criar um link de edição para cada contato:

<a asp-page="./Edit" asp-route-id="@contact.Id">edit</a>

O auxiliar de marcas de âncora usou o atributo asp-route-{value} para gerar um link para a página Edit. O link
contém dados de rota com a ID de contato. Por exemplo, http://localhost:5000/Edit/1 .
O arquivo Pages/Edit.cshtml:
@page "{id:int}"
@model RazorPagesContacts.Pages.EditModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@{
ViewData["Title"] = "Edit Customer";
}

<h1>Edit Customer - @Model.Customer.Id</h1>


<form method="post">
<div asp-validation-summary="All"></div>
<input asp-for="Customer.Id" type="hidden" />
<div>
<label asp-for="Customer.Name"></label>
<div>
<input asp-for="Customer.Name" />
<span asp-validation-for="Customer.Name" ></span>
</div>
</div>

<div>
<button type="submit">Save</button>
</div>
</form>

A primeira linha contém a diretiva @page "{id:int}" . A restrição de roteamento "{id:int}" informa à página para
aceitar solicitações para a página que contêm dados da rota int . Se uma solicitação para a página não contém
dados de rota que podem ser convertidos em um int , o tempo de execução retorna um erro HTTP 404 (não
encontrado). Para tornar a ID opcional, acrescente ? à restrição de rota:

@page "{id:int?}"

O arquivo Pages/Edit.cshtml.cs:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;

namespace RazorPagesContacts.Pages
{
public class EditModel : PageModel
{
private readonly AppDbContext _db;

public EditModel(AppDbContext db)


{
_db = db;
}

[BindProperty]
public Customer Customer { get; set; }

public async Task<IActionResult> OnGetAsync(int id)


{
Customer = await _db.Customers.FindAsync(id);

if (Customer == null)
{
return RedirectToPage("/Index");
}

return Page();
}

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

_db.Attach(Customer).State = EntityState.Modified;

try
{
await _db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
throw new Exception($"Customer {Customer.Id} not found!");
}

return RedirectToPage("/Index");
}
}
}

O arquivo Index.cshtml também contém a marcação para criar um botão de exclusão para cada contato de cliente:

<button type="submit" asp-page-handler="delete"


asp-route-id="@contact.Id">delete</button>

Quando o botão de exclusão é renderizado em HTML, seu formaction inclui parâmetros para:
A ID de contato do cliente especificada pelo atributo asp-route-id .
O handler especificado pelo atributo asp-page-handler .

Este é um exemplo de um botão de exclusão renderizado com uma ID de contato do cliente de 1 :

<button type="submit" formaction="/?id=1&amp;handler=delete">delete</button>

Quando o botão é selecionado, uma solicitação de formulário POST é enviada para o servidor. Por convenção, o
nome do método do manipulador é selecionado com base no valor do parâmetro handler de acordo com o
esquema OnPost[handler]Async .
Como o handler é delete neste exemplo, o método do manipulador OnPostDeleteAsync é usado para processar a
solicitação POST . Se o asp-page-handler for definido como um valor diferente, como remove , um método de
manipulador de página com o nome OnPostRemoveAsync será selecionado.

public async Task<IActionResult> OnPostDeleteAsync(int id)


{
var contact = await _db.Customers.FindAsync(id);

if (contact != null)
{
_db.Customers.Remove(contact);
await _db.SaveChangesAsync();
}

return RedirectToPage();
}

O método OnPostDeleteAsync :
Aceita o id da cadeia de caracteres de consulta.
Consulta o banco de dados para o contato de cliente com FindAsync .
Se o contato do cliente for encontrado, eles serão removidos da lista de contatos do cliente. O banco de dados é
atualizado.
Chama RedirectToPage para redirecionar para a página de índice de raiz ( /Index ).

Gerenciar solicitações HEAD com o manipulador OnGet


Geralmente, um manipulador HEAD é criado e chamado para solicitações HEAD:

public void OnHead()


{
HttpContext.Response.Headers.Add("HandledBy", "Handled by OnHead!");
}

Caso nenhum manipulador HEAD ( OnHead ) seja definido, as Páginas Razor voltam a chamar o manipulador de
página GET ( OnGet ) no ASP.NET Core 2.1 ou posterior. Aceite o seguinte comportamento com o método
SetCompatibilityVersion, no Startup.Configure para ASP.NET Core 2.1 a 2.x:

services.AddMvc()
.SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1);

SetCompatibilityVersion define de forma eficiente a opção de Páginas Razor AllowMappingHeadRequestsToGetHandler


como true . O comportamento é de aceitação até a liberação da versão prévia 1 do ASP.NET Core 3.0 ou posterior.
Cada versão principal do ASP.NET Core adota todos os comportamentos de liberação de patch da versão anterior.
O comportamento de aceitação global da liberação dos patches 2.1 a 2.x pode ser evitado com uma configuração
de aplicativo que mapeia solicitações HEAD para o manipulador GET. Defina a opção de Páginas Razor
AllowMappingHeadRequestsToGetHandler como true , sem chamar SetCompatibilityVersion , no Startup.Configure :

services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.AllowMappingHeadRequestsToGetHandler = true;
});

XSRF/CSRF e Páginas do Razor


Você não precisa escrever nenhum código para validação antifalsificação. Validação e geração de token
antifalsificação são automaticamente incluídas nas Páginas do Razor.

Usando Layouts, parciais, modelos e auxiliares de marcas com Páginas


do Razor
As Páginas funcionam com todos os recursos do mecanismo de exibição do Razor. Layouts, parciais, modelos,
auxiliares de marcas, _ViewStart.cshtml e _ViewImports.cshtml funcionam da mesma forma que funcionam
exibições convencionais do Razor.
Organizaremos essa página aproveitando alguns desses recursos.
Adicione uma página de layout a Pages/_Layout.cshtml:

<!DOCTYPE html>
<html>
<head>
<title>Razor Pages Sample</title>
</head>
<body>
<a asp-page="/Index">Home</a>
@RenderBody()
<a asp-page="/Customers/Create">Create</a> <br />
</body>
</html>

O Layout:
Controla o layout de cada página (a menos que a página opte por não usar o layout).
Importa estruturas HTML como JavaScript e folhas de estilo.
Veja página de layout para obter mais informações.
A propriedade Layout é definida em Pages/_ViewStart.cshtml:

@{
Layout = "_Layout";
}

Observação: o layout está na pasta Pages. As páginas buscam outras exibições (layouts, modelos, parciais)
hierarquicamente, iniciando na mesma pasta que a página atual. Um layout na pasta Pages pode ser usado em
qualquer Página do Razor na pasta Pages.
Recomendamos que você não coloque o arquivo de layout na pasta Views/Shared. Views/Shared é um padrão de
exibições do MVC. As Páginas do Razor devem confiar na hierarquia de pasta e não nas convenções de caminho.
A pesquisa de modo de exibição de uma Página do Razor inclui a pasta Pages. Os layouts, modelos e parciais que
você está usando com controladores MVC e exibições do Razor convencionais apenas funcionam.
Adicione um arquivo Pages/_ViewImports.cshtml:

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@namespace é explicado posteriormente no tutorial. A diretiva @addTagHelper coloca os auxiliares de marcas


internos em todas as páginas na pasta Pages.
Quando a diretiva @namespace é usada explicitamente em uma página:

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

<h2>Name space</h2>
<p>
@Model.Message
</p>

A diretiva define o namespace da página. A diretiva @model não precisa incluir o namespace.
Quando a diretiva @namespace está contida em _ViewImports.cshtml, o namespace especificado fornece o prefixo
do namespace gerado na página que importa a diretiva @namespace . O restante do namespace gerado (a parte do
sufixo) é o caminho relativo separado por ponto entre a pasta que contém _ViewImports.cshtml e a pasta que
contém a página.
Por exemplo, o arquivo code-behind Pages/Customers/Edit.cshtml.cs define explicitamente o namespace:

namespace RazorPagesContacts.Pages
{
public class EditModel : PageModel
{
private readonly AppDbContext _db;

public EditModel(AppDbContext db)


{
_db = db;
}

// Code removed for brevity.

O arquivo Pages/_ViewImports.cshtml define o namespace a seguir:

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

O namespace gerado para a Página do Razor Pages/Customers/Edit.cshtml é o mesmo que o do arquivo code-
behind. A diretiva @namespace foi projetada de modo que as classes C# adicionadas a um projeto e o código gerado
pelas páginas funcione sem a necessidade de adicionar uma diretiva @using para o arquivo code-behind.
Observação: @namespace também funciona com exibições do Razor convencionais.
O arquivo de exibição Pages/Create.cshtml original:
@page
@model RazorPagesContacts.Pages.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div>Name: <input asp-for="Customer.Name" /></div>
<input type="submit" />
</form>
</body>
</html>

O arquivo de exibição Pages/Create.cshtml atualizado:

@page
@model CreateModel

<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div>Name: <input asp-for="Customer.Name" /></div>
<input type="submit" />
</form>
</body>
</html>

O projeto inicial de Páginas do Razor contém o Pages/_ValidationScriptsPartial.cshtml, que conecta a validação do


lado do cliente.

Geração de URL para Páginas


A página Create , exibida anteriormente, usa RedirectToPage :

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
return RedirectToPage("/Index");
}

O aplicativo tem a estrutura de arquivos/pastas a seguir:


/Pages
Index.cshtml
/Clientes
Create.cshtml
Edit.cshtml
Index.cshtml
As páginas Pages/Customers/Create.cshtml e Pages/Customers/Edit.cshtml redirecionam para o
Pages/Index.cshtml após êxito. A cadeia de caracteres /Index faz parte do URI para acessar a página anterior. A
cadeia de caracteres /Index pode ser usada para gerar URIs para a página Pages/Index.cshtml. Por exemplo:
Url.Page("/Index", ...)
<a asp-page="/Index">My Index Page</a>
RedirectToPage("/Index")

O nome da página é o caminho para a página da pasta raiz /Pages (incluindo um / à direita, por exemplo, /Index
). As amostras anteriores de geração de URL são muito mais ricas em recursos do que apenas codificar uma URL.
A geração de URL usa roteamento e pode gerar e codificar parâmetros de acordo com o modo como a rota é
definida no caminho de destino.
A Geração de URL para páginas dá suporte a nomes relativos. A tabela a seguir mostra qual página de Índice é
selecionada com diferentes parâmetros RedirectToPage de Pages/Customers/Create.cshtml:

REDIRECTTOPAGE(X) PÁGINA

RedirectToPage("/Index") Pages/Index

RedirectToPage("./Index"); Pages/Customers/Index

RedirectToPage("../Index") Pages/Index

RedirectToPage("Index") Pages/Customers/Index

RedirectToPage("Index") , e RedirectToPage("../Index") são nomes relativos. O


RedirectToPage("./Index")
parâmetro RedirectToPage é combinado com o caminho da página atual para calcular o nome da página de
destino.
Vinculação de nome relativo é útil ao criar sites com uma estrutura complexa. Se você usar nomes relativos para
vincular entre páginas em uma pasta, você poderá renomear essa pasta. Todos os links ainda funcionarão (porque
eles não incluirão o nome da pasta).

TempData
O ASP.NET Core expõe a propriedade TempData em um controlador. Essa propriedade armazena dados até eles
serem lidos. Os métodos Keep e Peek podem ser usados para examinar os dados sem exclusão. TempData é útil
para redirecionamento nos casos em que os dados são necessários para mais de uma única solicitação.
O atributo [TempData] é novo no ASP.NET Core 2.0 e tem suporte em controladores e páginas.
Os conjuntos de código a seguir definem o valor de Message usando TempData :
public class CreateDotModel : PageModel
{
private readonly AppDbContext _db;

public CreateDotModel(AppDbContext db)


{
_db = db;
}

[TempData]
public string Message { get; set; }

[BindProperty]
public Customer Customer { get; set; }

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
Message = $"Customer {Customer.Name} added";
return RedirectToPage("./Index");
}
}

A marcação a seguir no arquivo Pages/Customers/Index.cshtml exibe o valor de Message usando TempData .

<h3>Msg: @Model.Message</h3>

O modelo de página Pages/Customers/Index.cshtml.cs aplica o atributo [TempData] à propriedade Message .

[TempData]
public string Message { get; set; }

Consulte TempData para obter mais informações.

Vários manipuladores por página


A página a seguir gera marcação para dois manipuladores de página usando o auxiliar de marcas
asp-page-handler :
@page
@model CreateFATHModel

<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div>Name: <input asp-for="Customer.Name" /></div>
<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
</form>
</body>
</html>

O formulário no exemplo anterior tem dois botões de envio, cada um usando o FormActionTagHelper para enviar
para uma URL diferente. O atributo asp-page-handler é um complemento para asp-page . asp-page-handler gera
URLs que enviam para cada um dos métodos de manipulador definidos por uma página. asp-page não foi
especificado porque a amostra está vinculando à página atual.
O modelo de página:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;

namespace RazorPagesContacts.Pages.Customers
{
public class CreateFATHModel : PageModel
{
private readonly AppDbContext _db;

public CreateFATHModel(AppDbContext db)


{
_db = db;
}

[BindProperty]
public Customer Customer { get; set; }

public async Task<IActionResult> OnPostJoinListAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
return RedirectToPage("/Index");
}

public async Task<IActionResult> OnPostJoinListUCAsync()


{
if (!ModelState.IsValid)
{
return Page();
}
Customer.Name = Customer.Name?.ToUpper();
return await OnPostJoinListAsync();
}
}
}

O código anterior usa métodos de manipulador nomeados. Métodos de manipulador nomeados são criados
colocando o texto no nome após On<HTTP Verb> e antes de Async (se houver). No exemplo anterior, os métodos de
página são OnPostJoinListAsync e OnPostJoinListUCAsync. Com OnPost e Async removidos, os nomes de
manipulador são JoinList e JoinListUC .

<input type="submit" asp-page-handler="JoinList" value="Join" />


<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />

Usando o código anterior, o caminho da URL que envia a é


OnPostJoinListAsync
http://localhost:5000/Customers/CreateFATH?handler=JoinList . O caminho da URL que envia a
OnPostJoinListUCAsync é http://localhost:5000/Customers/CreateFATH?handler=JoinListUC .

Personalizando o roteamento
Se você não deseja a cadeia de consulta ?handler=JoinList na URL, você pode alterar a rota para colocar o nome
do manipulador na parte do caminho da URL. Você pode personalizar a rota adicionando um modelo de rota entre
aspas duplas após a diretiva @page .
@page "{handler?}"
@model CreateRouteModel

<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div>Name: <input asp-for="Customer.Name" /></div>
<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
</form>
</body>
</html>

A rota anterior coloca o nome do manipulador no caminho da URL em vez da cadeia de consulta. O ? após
handler significa que o parâmetro de rota é opcional.

Você pode usar @page para adicionar parâmetros e segmentos adicionais a uma rota de página. Tudo que está lá é
acrescentado à rota padrão da página. Não há suporte para o uso de um caminho absoluto ou virtual para alterar
a rota da página (como "~/Some/Other/Path" ).

Configuração e definições
Para configurar opções avançadas, use o método de extensão AddRazorPagesOptions no construtor de MVC:

public void ConfigureServices(IServiceCollection services)


{
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.RootDirectory = "/MyPages";
options.Conventions.AuthorizeFolder("/MyPages/Admin");
});
}

No momento, você pode usar o RazorPagesOptions para definir o diretório raiz para páginas ou adicionar as
convenções de modelo de aplicativo para páginas. Permitiremos mais extensibilidade dessa maneira no futuro.
Para pré-compilar exibições, consulte Compilação de exibição do Razor.
Baixar ou exibir código de exemplo.
Consulte a Introdução a Páginas do Razor, que se baseia nesta introdução.
Especificar que as Páginas Razor estão na raiz do conteúdo
Por padrão, as Páginas Razor estão na raiz do diretório /Pages. Adicione WithRazorPagesAtContentRoot em
AddMvc para especificar que as Páginas Razor estão na raiz do conteúdo (ContentRootPath) do aplicativo:

services.AddMvc()
.AddRazorPagesOptions(options =>
{
...
})
.WithRazorPagesAtContentRoot();

Especificar que as Páginas Razor estão em um diretório raiz personalizado


Adicione WithRazorPagesRoot em AddMvc para especificar que as Páginas Razor estão em um diretório raiz
personalizado no aplicativo (forneça um caminho relativo):

services.AddMvc()
.AddRazorPagesOptions(options =>
{
...
})
.WithRazorPagesRoot("/path/to/razor/pages");

Consulte também
Introdução ao ASP.NET Core
Sintaxe Razor
Introdução a Páginas do Razor
Convenções de autorização de Páginas Razor
Provedores de modelo personalizado de página e rota de Páginas Razor
Testes de integração e unidade de Páginas Razor
28/02/2018 • 16 min to read • Edit Online

#Criar uma API Web com o ASP.NET Core e o Visual Studio para Windows
Por Rick Anderson e Mike Wasson
Este tutorial compilará uma API Web para gerenciar uma lista de itens de “tarefas pendentes”. Uma interface do
usuário (UI) não será criada.
Há três versões deste tutorial:
Windows: API Web com o Visual Studio para Windows (este tutorial)
macOS: API Web com o Visual Studio para Mac
macOS, Linux, Windows: API Web com o Visual Studio Code

Visão geral
Este tutorial cria a seguinte API:

API DESCRIÇÃO CORPO DA SOLICITAÇÃO CORPO DA RESPOSTA

GET /api/todo Obter todos os itens de Nenhum Matriz de itens de tarefas


tarefas pendentes pendentes

GET /api/todo/{id} Obter um item por ID Nenhum Item de tarefas pendentes

POST /api/todo Adicionar um novo item Item de tarefas pendentes Item de tarefas pendentes

PUT /api/todo/{id} Atualizar um item existente Item de tarefas pendentes Nenhum

DELETE /api/todo/{id} Excluir um item Nenhum Nenhum

O diagrama a seguir mostra o design básico do aplicativo.

O cliente é tudo o que consome a API Web (aplicativo móvel, navegador, etc.). Este tutorial não cria um
cliente. Postman ou curl são usados como clientes para testar o aplicativo.
Um modelo é um objeto que representa os dados no aplicativo. Nesse caso, o único modelo é um item de
tarefas pendentes. Modelos são representados como classes c#, também conhecidos como Plain Old C#
Objeto (POCOs).
Um controlador é um objeto que manipula solicitações HTTP e cria a resposta HTTP. Este aplicativo tem um
único controlador.
Para manter o tutorial simples, o aplicativo não usa um banco de dados persistente. O aplicativo de exemplo
armazena os itens de tarefas pendentes em um banco de dados em memória.

Pré-requisitos
Install the following:
.NET Core 2.0.0 SDK or later.
Visual Studio 2017 version 15.3 or later with the ASP.NET and web development workload.
Consulte este PDF para o ASP.NET Core versão 1.1.

Criar o projeto
No Visual Studio, selecione o menu Arquivo, > Novo > Projeto.
Selecione o modelo de projeto Aplicativo Web ASP.NET Core (.NET Core). Nomeie o projeto TodoApi e
selecione OK.

Na caixa de diálogo Novo aplicativo Web ASP.NET Core – TodoApi, selecione o modelo API Web. Selecione
OK. Não selecione Habilitar Suporte ao Docker.

Iniciar o aplicativo
No Visual Studio, pressione CTRL + F5 para iniciar o aplicativo. O Visual Studio inicia um navegador e navega para
http://localhost:port/api/values``http://localhost:port/api/values , em que port é um número da porta escolhido
aleatoriamente. O Chrome, Microsoft Edge e Firefox exibem a seguinte saída:

["value1","value2"]

Adicionar uma classe de modelo


Um modelo é um objeto que representa os dados no aplicativo. Nesse caso, o único modelo é um item de tarefas
pendentes.
Adicione uma pasta denominada "Modelos". No Gerenciador de Soluções, clique com o botão direito do mouse no
projeto. Selecione Adicionar > Nova Pasta. Nomeie a pasta Models.
Observação: as classes de modelo entram em qualquer lugar no projeto. A pasta Modelos é usada por convenção
para as classes de modelo.
Adicione uma classe TodoItem . Clique com o botão direito do mouse na pasta Modelos e selecione Adicionar >
Classe. Nomeie a classe TodoItem e, em seguida, selecione Adicionar.
Atualize a classe TodoItem com o código a seguir:
namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
}

O banco de dados gera o Id quando um TodoItem é criado.


Criar o contexto de banco de dados
O contexto de banco de dados é a classe principal que coordena a funcionalidade do Entity Framework para um
determinado modelo de dados. Essa classe é criada derivando-a da classe Microsoft.EntityFrameworkCore.DbContext
.
Adicione uma classe TodoContext . Clique com o botão direito do mouse na pasta Modelos e selecione Adicionar >
Classe. Nomeie a classe TodoContext e, em seguida, selecione Adicionar.
Substitua a classe pelo código a seguir:

using Microsoft.EntityFrameworkCore;

namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}

public DbSet<TodoItem> TodoItems { get; set; }

}
}

Registrar o contexto de banco de dados


Nesta etapa, o contexto do banco de dados é registrado com o contêiner injeção de dependência. Os serviços
(como o contexto do banco de dados) que são registrados com o contêiner de injeção de dependência (DI) estão
disponíveis para os controladores.
Registre o contexto de banco de dados com o contêiner de serviço usando o suporte interno para injeção de
dependência. Substitua o conteúdo do arquivo Startup.cs pelo seguinte código:
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using TodoApi.Models;

namespace TodoApi
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));
services.AddMvc();
}

public void Configure(IApplicationBuilder app)


{
app.UseMvc();
}
}
}

O código anterior:
Remove o código que não é usado.
Especifica que um banco de dados em memória é injetado no contêiner de serviço.
Adicionar um controlador
No Gerenciador de Soluções, clique com o botão direito do mouse na pasta Controladores. Selecione Adicionar >
Novo Item. Na caixa de diálogo Adicionar Novo Item, selecione o modelo Classe do Controlador de API
Web. Nomeie a classe TodoController .

Substitua a classe pelo código a seguir:


using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using TodoApi.Models;
using System.Linq;

namespace TodoApi.Controllers
{
[Route("api/[controller]")]
public class TodoController : Controller
{
private readonly TodoContext _context;

public TodoController(TodoContext context)


{
_context = context;

if (_context.TodoItems.Count() == 0)
{
_context.TodoItems.Add(new TodoItem { Name = "Item1" });
_context.SaveChanges();
}
}
}
}

O código anterior:
Define uma classe de controlador vazia. Nas próximas seções, os métodos serão adicionados para implementar
a API.
O construtor usa a Injeção de Dependência para injetar o contexto de banco de dados ( TodoContext ) no
controlador. O contexto de banco de dados é usado em cada um dos métodos CRUD no controlador.
O construtor adiciona um item no banco de dados em memória, caso ele não exista.

Obtendo itens de tarefas pendentes


Para obter os itens de tarefas pendentes, adicione os métodos a seguir à classe TodoController .

[HttpGet]
public IEnumerable<TodoItem> GetAll()
{
return _context.TodoItems.ToList();
}

[HttpGet("{id}", Name = "GetTodo")]


public IActionResult GetById(long id)
{
var item = _context.TodoItems.FirstOrDefault(t => t.Id == id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}

Esses métodos de implementam os dois métodos GET:


GET /api/todo
GET /api/todo/{id}

Esta é uma resposta HTTP de exemplo para o método GetAll :


[
{
"id": 1,
"name": "Item1",
"isComplete": false
}
]

Mais adiante no tutorial, mostrarei como a resposta HTTP pode ser visualizada com o Postman ou o curl.
Roteamento e caminhos de URL
O atributo [HttpGet] especifica um método HTTP GET. O caminho da URL de cada método é construído da
seguinte maneira:
Use a cadeia de caracteres de modelo no atributo Route do controlador:

namespace TodoApi.Controllers
{
[Route("api/[controller]")]
public class TodoController : Controller
{
private readonly TodoContext _context;

Substitua [controller] com o nome do controlador, que é o nome de classe do controlador menos o sufixo
"Controlador". Para esta amostra, o nome de classe do controlador é TodoController e o nome da raiz é “todo”.
O roteamento do ASP.NET Core não diferencia maiúsculas de minúsculas.
Se o atributo [HttpGet] tiver um modelo de rota (como [HttpGet("/products")] ), acrescente isso ao caminho.
Esta amostra não usa um modelo. Consulte Roteamento de atributo com atributos Http[verb] para obter mais
informações.
No método GetById :

[HttpGet("{id}", Name = "GetTodo")]


public IActionResult GetById(long id)
{
var item = _context.TodoItems.FirstOrDefault(t => t.Id == id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}

"{id}" é uma variável de espaço reservado para a ID do item todo . Quando GetById é invocado, ele atribui o
valor “{id}” na URL ao parâmetro id do método.
Name = "GetTodo" cria uma rota nomeada. Rotas nomeadas:
permitem que o aplicativo crie um link HTTP usando o nome da rota.
Serão explicadas posteriormente no tutorial.
Valores de retorno
O método GetAll retorna um IEnumerable . O MVC serializa automaticamente o objeto em JSON e grava o JSON
no corpo da mensagem de resposta. O código de resposta para esse método é 200, supondo que não haja
nenhuma exceção sem tratamento. (Exceções sem tratamento são convertidas em erros 5xx.)
Por outro lado, o método GetById retorna o tipo IActionResult mais geral, que representa uma ampla variedade
de tipos de retorno. GetById tem dois tipos retornados diferentes:
Se nenhum item corresponder à ID solicitada, o método retornará um erro 404. Retornar NotFound retorna
uma resposta HTTP 404.
Caso contrário, o método retornará 200 com um corpo de resposta JSON. Retornar ObjectResult retorna
uma resposta HTTP 200.
Iniciar o aplicativo
No Visual Studio, pressione CTRL + F5 para iniciar o aplicativo. O Visual Studio inicia um navegador e navega para
http://localhost:port/api/values``http://localhost:port/api/values , em que port é um número da porta escolhido
aleatoriamente. Navegue até o controlador Todo no http://localhost:port/api/todo .

Implementar as outras operações CRUD


Nas próximas seções, os métodos Create , Update e Delete serão adicionados ao controlador.
Create
Adicione o seguinte método Create .

[HttpPost]
public IActionResult Create([FromBody] TodoItem item)
{
if (item == null)
{
return BadRequest();
}

_context.TodoItems.Add(item);
_context.SaveChanges();

return CreatedAtRoute("GetTodo", new { id = item.Id }, item);


}

O código anterior é um método HTTP POST, indicado pelo atributo [HttpPost] . O atributo [FromBody] informa ao
MVC para obter o valor do item de tarefas pendentes do corpo da solicitação HTTP.
O método CreatedAtRoute :
Retorna uma resposta 201. HTTP 201 é a resposta padrão para um método HTTP POST que cria um novo
recurso no servidor.
Adiciona um cabeçalho Local à resposta. O cabeçalho Location especifica o URI do item de tarefas pendentes
recém-criado. Consulte 10.2.2 201 criado.
Usa a rota chamada "GetTodo" para criar a URL. A rota chamada "GetTodo" é definida em GetById :

[HttpGet("{id}", Name = "GetTodo")]


public IActionResult GetById(long id)
{
var item = _context.TodoItems.FirstOrDefault(t => t.Id == id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}

Usar o Postman para enviar uma solicitação Create


Defina o método HTTP como POST
Selecione o botão de opção Corpo
Selecione o botão de opção bruto
Definir o tipo como JSON
No editor de chave-valor, insira um item de tarefas pendentes, como

{
"name":"walk dog",
"isComplete":true
}

Selecione Enviar
Selecione a guia Cabeçalhos no painel inferior e copie o cabeçalho Location:

O URI do cabeçalho Local pode ser usado para acessar o novo item.
Atualização
Adicione o seguinte método Update :
[HttpPut("{id}")]
public IActionResult Update(long id, [FromBody] TodoItem item)
{
if (item == null || item.Id != id)
{
return BadRequest();
}

var todo = _context.TodoItems.FirstOrDefault(t => t.Id == id);


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

todo.IsComplete = item.IsComplete;
todo.Name = item.Name;

_context.TodoItems.Update(todo);
_context.SaveChanges();
return new NoContentResult();
}

Update é semelhante a Create , mas usa HTTP PUT. A resposta é 204 (Sem conteúdo). De acordo com a
especificação HTTP, uma solicitação PUT exige que o cliente envie a entidade atualizada inteira, não apenas os
deltas. Para dar suporte a atualizações parciais, use HTTP PATCH.

Excluir
Adicione o seguinte método Delete :
[HttpDelete("{id}")]
public IActionResult Delete(long id)
{
var todo = _context.TodoItems.FirstOrDefault(t => t.Id == id);
if (todo == null)
{
return NotFound();
}

_context.TodoItems.Remove(todo);
_context.SaveChanges();
return new NoContentResult();
}

A resposta Delete é 204 (Sem conteúdo).


Teste Delete :

Próximas etapas
Páginas de ajuda da API Web ASP.NET Core usando o Swagger
Roteamento para ações do controlador
Para obter informações sobre como implantar uma API, incluindo o Serviço de Aplicativo do Azure, confira
Host e implantação.
Exibir ou baixar o código de exemplo. Consulte como baixar.
Postman
Tutoriais do ASP.NET Core
28/04/2018 • 3 min to read • Edit Online

Os seguintes guias passo a passo para desenvolvimento de aplicativos ASP.NET Core estão disponíveis:

Criar aplicativos Web


As Páginas Razor são a abordagem recomendada para criar um novo aplicativo da interface do usuário da Web
com o ASP.NET Core 2.0.
Introdução a Páginas do Razor no ASP.NET Core
Criar um aplicativo Web de Páginas do Razor com o ASP.NET Core
Páginas do Razor no Windows
Páginas do Razor no macOS
Páginas do Razor com o VS Code
Criar um aplicativo Web MVC ASP.NET Core
Aplicativo Web com o Visual Studio para Windows
Aplicativo Web com o Visual Studio para Mac
Aplicativo Web com o Visual Studio Code no macOS ou no Linux
Introdução ao ASP.NET Core e ao Entity Framework Core usando o Visual Studio
Criar auxiliares de marcação
Criar um componente de exibição simples
Desenvolvendo aplicativos ASP.NET Core usando a inspeção dotnet

Criar APIs da Web


Criar uma API Web com o ASP.NET Core
API Web com o Visual Studio para Windows
API Web com o Visual Studio para Mac
API Web com o Visual Studio Code
Páginas de ajuda da API Web ASP.NET Core usando o Swagger
Introdução ao NSwag
Introdução ao Swashbuckle
Criar serviços Web de back-end para aplicativos móveis nativos

Armazenamento e acesso a dados


Introdução às páginas do Razor e ao EF Core usando o Visual Studio
Introdução ao ASP.NET Core MVC e ao EF Core usando o Visual Studio
ASP.NET Core MCV com EF Core – novo banco de dados
ASP.NET Core MCV com EF Core – banco de dados existente

Autenticação e autorização
Habilitar a autenticação usando o Facebook, o Google e outros provedores externos
Confirmação de conta e recuperação de senha
Autenticação de dois fatores com SMS

Desenvolvimento do lado do cliente


Usar o Gulp
Usar o Grunt
Gerenciar pacotes do lado do cliente com o Bower
Criar sites responsivos com o Bootstrap

Teste
Teste de unidade no .NET Core usando dotnet test

Hospedar e implantar
Implantar um aplicativo Web ASP.NET Core para o Azure usando o Visual Studio
Implantar um aplicativo Web do ASP.NET Core no Azure usando a linha de comando
Publicar um aplicativo Web do Azure com implantação contínua
Implantar um contêiner ASP.NET em um host remoto do Docker
ASP.NET Core e Azure Service Fabric

Como baixar uma amostra


1. Baixe o arquivo zip do repositório ASP.NET.
2. Descompacte o arquivo Docs-master.zip.
3. Use a URL no link de exemplo para ajudá-lo a navegar até o diretório de exemplo.
Criar um aplicativo Web de Páginas do Razor com o
ASP.NET Core
27/04/2018 • 1 min to read • Edit Online

Essa série explica as noções básicas de criação de um aplicativo Web de Páginas do Razor com o ASP.NET Core
usando o Visual Studio. Outras versões dessa série incluem uma versão do macOS e uma versão do Visual Studio
Code.
1. Introdução a Páginas do Razor
2. Adicionar um modelo a um aplicativo de Páginas do Razor
3. Páginas do Razor geradas por scaffolding
4. Trabalhar com o SQL Server LocalDB
5. Atualizando as páginas
6. Adicionar pesquisa
7. Adicionar um novo campo
8. Adicionar validação
9. Carregar arquivos
Introdução às Páginas do Razor no ASP.NET Core
31/01/2018 • 6 min to read • Edit Online

Por Rick Anderson


Este tutorial ensina as noções básicas de criação de um aplicativo Web de Páginas do Razor do ASP.NET
Core. As Páginas do Razor são a maneira recomendada para criar a interface do usuário para aplicativos
Web no ASP.NET Core.
Há três versões deste tutorial:
Windows: este tutorial
MacOS: Introdução a Páginas do Razor com o Visual Studio para Mac
macOS, Linux e Windows: Introdução a Páginas do Razor no ASP.NET Core com o Visual Studio Code
Exibir ou baixar código de exemplo (como baixar)

Pré-requisitos
Install the following:
.NET Core 2.0.0 SDK or later.
Visual Studio 2017 version 15.3 or later with the ASP.NET and web development workload.

Criar um aplicativo Web do Razor


No menu Arquivo do Visual Studio, selecione Novo > Projeto.
Crie um novo Aplicativo Web ASP.NET Core. Nomeie o projeto RazorPagesMovie. É importante
nomear o projeto RazorPagesMovie de modo que os namespaces façam a correspondência quando
você copiar/colar código.

Selecione ASP.NET Core 2.0 na lista suspensa e selecione Aplicativo Web.


OBSERVAÇÃO
Para usar o ASP.NET Core com o .NET Framework, primeiro selecione .NET Framework na lista suspensa
mais à esquerda na caixa de diálogo e, em seguida, selecione a versão desejada do ASP.NET Core.

O modelo do Visual Studio cria um projeto inicial:

Pressione F5 para executar o aplicativo no modo de depuração ou Ctrl-F5 para executar sem anexar o
depurador
O Visual Studio inicia o IIS Express e executa o aplicativo. A barra de endereços mostra localhost:port#
e não algo como example.com . Isso ocorre porque localhost é o nome do host padrão do computador
local. Localhost serve somente solicitações da Web do computador local. Quando o Visual Studio cria
um projeto Web, uma porta aleatória é usada para o servidor Web. Na imagem anterior, o número da
porta é 5000. Quando você executar o aplicativo, verá um número da porta diferente.
Iniciar o aplicativo com Ctrl+F5 (modo de não depuração) permite que você faça alterações de código,
salve o arquivo, atualize o navegador e veja as alterações de código. Muitos desenvolvedores preferem
usar modo de não depuração para iniciar o aplicativo e exibir alterações rapidamente.
O modelo padrão cria os links e páginas RazorPagesMovie, Início, Sobre e Contato. Dependendo do
tamanho da janela do navegador, talvez você precise clicar no ícone de navegação para mostrar os links.
Teste os links. Os links RazorPagesMovie e Início vão para a página de Índice. Os links Sobre e Contato
vão para as páginas About e Contact , respectivamente.

Pastas e arquivos de projeto


A tabela a seguir lista os arquivos e pastas no projeto. Para este tutorial, o arquivo Startup.cs é o mais
importante de se entender. Não é necessário examinar cada link fornecido abaixo. Os links são fornecidos
como uma referência para quando você precisar de mais informações sobre um arquivo ou pasta no
projeto.

ARQUIVO OU PASTA FINALIDADE

wwwroot Contém arquivos estáticos. Veja trabalhando com


arquivos estáticos.

Páginas Pasta para Páginas do Razor.

appsettings.json Configuração

Program.cs Hospeda o aplicativo ASP.NET Core.

Startup.cs Configura os serviços e o pipeline de solicitação. Consulte


Inicialização.

A pasta Páginas
O arquivo _Layout.cshtml contém elementos HTML comuns (scripts e folhas de estilo) e define o layout
para o aplicativo. Por exemplo, quando você clica em RazorPagesMovie, Início, Sobre ou Contato, você
vê os mesmos elementos. Os elementos comuns incluem o menu de navegação na parte superior e o
cabeçalho na parte inferior da janela. Veja Layout para obter mais informações.
O _ViewStart.cshtml define a propriedade Layout das Páginas do Razor para usar o arquivo
_Layout.cshtml. Veja Layout para obter mais informações.
O arquivo _ViewImports.cshtml contém diretivas do Razor que são importadas para cada Página do Razor.
Veja Importando diretivas compartilhadas para obter mais informações.
O arquivo _ValidationScriptsPartial.cshtml fornece uma referência a scripts de validação jQuery. Quando
adicionarmos as páginas Create e Edit posteriormente no tutorial, o arquivo
_ValidationScriptsPartial.cshtml será usado.
As páginas About , Contact e Index são páginas básicas que você pode usar para iniciar um aplicativo. A
página Error é usada para exibir informações de erro.

Próximo: adicionando um modelo

Próximo: adicionando um modelo


Adicionando um modelo a um aplicativo de Páginas
do Razor
31/01/2018 • 6 min to read • Edit Online

Por Rick Anderson


Nesta seção, você adiciona classes para gerenciamento de filmes em um banco de dados. Você usa essas classes
com o Entity Framework Core (EF Core) para trabalhar com um banco de dados. O EF Core é uma estrutura
ORM (de mapeamento relacional de objetos) que simplifica o código de acesso a dados que você precisa escrever.
As classes de modelo que você cria são conhecidas como classes de dados POCO (de "objetos CLR básicos")
porque elas não têm nenhuma dependência do EF Core. Elas definem as propriedades dos dados que são
armazenados no banco de dados.
Neste tutorial, você escreve as classes de modelo primeiro e o EF Core cria o banco de dados. Uma abordagem
alternativa não abordada aqui é gerar classes de modelo de um banco de dados existente.
Exiba ou baixe a amostra.

Adicionar um modelo de dados


No Gerenciador de Soluções, clique com o botão direito do mouse no projeto RazorPagesMovie > Adicionar >
Nova Pasta. Nomeie a pasta Models.
Clique com o botão direito do mouse na pasta Modelos. Selecione Adicionar > Classe. Nomeie a classe Movie e
adicione as seguintes propriedades:
Adicione as seguintes propriedades à classe Movie :

using System;

namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}

O campo ID é necessário para o banco de dados para a chave primária.


Adicionar uma classe de contexto de banco de dados
Adicione a seguinte classe derivada DbContext chamada MovieContext.cs à pasta Models: [!code-csharpMain]
O código anterior cria uma propriedade DbSet para o conjunto de entidades. Na terminologia do Entity
Framework, um conjunto de entidades normalmente corresponde a uma tabela de banco de dados, enquanto
uma entidade corresponde a uma linha na tabela.
Adicionar uma cadeia de conexão de banco de dados
Adicione uma cadeia de conexão ao arquivo appsettings.json.
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"ConnectionStrings": {
"MovieContext": "Server=(localdb)\\mssqllocaldb;Database=Movie-
1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}

Registrar o contexto do banco de dados


Registre o contexto do banco de dados com o contêiner de injeção de dependência no arquivo Startup.cs.

public void ConfigureServices(IServiceCollection services)


{
// requires
// using RazorPagesMovie.Models;
// using Microsoft.EntityFrameworkCore;

services.AddDbContext<MovieContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MovieContext")));
services.AddMvc();
}

Compile o projeto para verificar se não há erros.

Adicionar ferramentas de scaffold e executar a migração inicial


Nesta seção, você deve usar o PMC (Console de Gerenciador de Pacotes) para:
Adicione o pacote de geração de código da Web do Visual Studio. Esse pacote é necessário para executar o
mecanismo de scaffolding.
Adicionar uma migração inicial.
Atualize o banco de dados com a migração inicial.
No menu Ferramentas, selecione Gerenciador de pacotes NuGet > Console do Gerenciador de pacotes.
No PMC, digite os seguintes comandos:

Install-Package Microsoft.VisualStudio.Web.CodeGeneration.Design
Add-Migration Initial
Update-Database

O comando Install-Package instala as ferramentas necessárias para executar o mecanismo de scaffolding.


O comando Add-Migration gera código para criar o esquema de banco de dados inicial. O esquema é baseado no
modelo especificado no DbContext (no arquivo Models/MovieContext.cs). O argumento Initial é usado para
nomear as migrações. Você pode usar qualquer nome, mas, por convenção, escolha um nome que descreve a
migração. Consulte Introdução às migrações para obter mais informações.
O comando Update-Database executa o método Up no arquivo Migrations/<time-stamp>_InitialCreate.cs, que
cria o banco de dados.
Fazer scaffolding do modelo de filme
Execute o seguinte na linha de comando (o diretório do projeto que contém os arquivos Program.cs,
Startup.cs e .csproj):

dotnet aspnet-codegenerator razorpage -m Movie -dc MovieContext -udl -outDir Pages\Movies --


referenceScriptLibraries

Se você obtiver o erro:

No executable found matching command "dotnet-aspnet-codegenerator"

Abra um Shell de Comando no diretório do projeto (o diretório que contém os arquivos Program.cs, Startup.cs e
.csproj).
Se você obtiver o erro:

The process cannot access the file


'RazorPagesMovie/bin/Debug/netcoreapp2.0/RazorPagesMovie.dll'
because it is being used by another process.

Saia do Visual Studio e execute o comando novamente.


A tabela a seguir detalha os parâmetros dos geradores de código do ASP.NET Core:

PARÂMETRO DESCRIÇÃO

-m O nome do modelo.

-dc O contexto de dados.

-udl Use o layout padrão.

-outDir O caminho da pasta de saída relativa para criar as exibições.

--referenceScriptLibraries Adiciona _ValidationScriptsPartial para editar e criar


páginas

Use a opção h para obter ajuda sobre o comando aspnet-codegenerator razorpage :


dotnet aspnet-codegenerator razorpage -h

Testar o aplicativo
Executar o aplicativo e acrescentar /Movies à URL no navegador ( http://localhost:port/movies ).
Teste o link Criar.

Teste os links Editar, Detalhes e Excluir.


Se você receber uma exceção SQL, verifique se você executou migrações e atualizou o banco de dados:
O tutorial a seguir explica os arquivos criados por scaffolding.

Anterior: Introdução Próximo: Páginas do Razor geradas por scaffolding


Páginas do Razor geradas por scaffolding no
ASP.NET Core
08/05/2018 • 12 min to read • Edit Online

Páginas do Razor geradas por scaffolding no ASP.NET Core


Por Rick Anderson
Este tutorial examina as Páginas do Razor criadas por scaffolding no tutorial anterior.
Exiba ou baixe a amostra.

As páginas Criar, Excluir, Detalhes e Editar.


Examine o modelo de página, Pages/Movies/Index.cshtml.cs: [!code-csharpMain]
As Páginas do Razor são derivadas de PageModel . Por convenção, a classe derivada de PageModel é chamada de
<PageName>Model . O construtor usa injeção de dependência para adicionar o MovieContext à página. Todas as
páginas geradas por scaffolding seguem esse padrão. Consulte Código assíncrono para obter mais informações
sobre a programação assíncrona com o Entity Framework.
Quando uma solicitação é feita à página, o método OnGetAsync retorna uma lista de filmes para a Página do
Razor. OnGetAsync ou OnGet é chamado em uma Página do Razor para inicializar o estado da página. Nesse caso,
OnGetAsync obtém uma lista de filmes e os exibe.

Quando OnGet retorna void ou OnGetAsync retorna Task , então nenhum método de retorno é usado. Quando
o tipo de retorno for IActionResult ou Task<IActionResult> , é necessário fornecer uma instrução de retorno. Por
exemplo, o método OnPostAsync do arquivo Pages/Movies/Create.cshtml.cs:

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

_context.Movie.Add(Movie);
await _context.SaveChangesAsync();

return RedirectToPage("./Index");
}

Examine a Página do Razor Pages/Movies/Index.cshtml:


@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movie) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

O Razor pode fazer a transição do HTML em C# ou em marcação específica do Razor. Quando um símbolo @ é
seguido por uma palavra-chave reservada do Razor, ele faz a transição para marcação específica do Razor, caso
contrário, ele faz a transição para C#.
A diretiva do Razor @page transforma o arquivo em uma ação do MVC —, o que significa que ele pode
manipular as solicitações. @page deve ser a primeira diretiva do Razor em uma página. @page é um exemplo de
transição para a marcação específica do Razor. Consulte Sintaxe Razor para obter mais informações.
Examine a expressão lambda usada no auxiliar HTML a seguir:
@Html.DisplayNameFor(model => model.Movie[0].Title))

O auxiliar HTML DisplayNameFor inspeciona a propriedade Title referenciada na expressão lambda para
determinar o nome de exibição. A expressão lambda é inspecionada em vez de avaliada. Isso significa que não há
nenhuma violação de acesso quando model , model.Movie ou model.Movie[0] são null ou vazios. Quando a
expressão lambda é avaliada (por exemplo, com @Html.DisplayFor(modelItem => item.Title) ), os valores de
propriedade do modelo são avaliados.
A diretiva @model

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

A diretiva @model especifica o tipo de modelo passado para a Página do Razor. No exemplo anterior, a linha
@model torna a classe derivada de PageModel disponível para a Página do Razor. O modelo é usado nos auxiliares
HTML @Html.DisplayNameFor e @Html.DisplayName na página.
ViewData e layout
Considere o código a seguir:

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
ViewData["Title"] = "Index";
}

O código realçado anterior é um exemplo de transição do Razor para C#. Os caracteres { e } circunscrevem
um bloco de código C#.
A classe base PageModel tem uma propriedade de dicionário ViewData que pode ser usada para adicionar os
dados que você deseja passar para uma exibição. Você adiciona objetos ao dicionário ViewData usando um
padrão de chave/valor. No exemplo anterior, a propriedade "Title" é adicionada ao dicionário ViewData . A
propriedade "Título" é usada no arquivo Pages/_Layout.cshtml. A marcação a seguir mostra as primeiras linhas do
arquivo Pages/_Layout.cshtml.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - RazorPagesMovie</title>

@*Markup removed for brevity.*@

A linha @*Markup removed for brevity.*@é um comentário do Razor. Ao contrário de comentários HTML (
<!-- --> ), comentários do Razor não são enviados ao cliente.

Execute o aplicativo e teste os links no projeto (Início, Sobre, Contato, Criar, Editar e Excluir). Cada página
define o título, que pode ser visto na guia do navegador. Quando você coloca um indicador em uma página, o
título é usado para o indicador. Pages/Index.cshtml e Pages/Movies/Index.cshtml atualmente têm o mesmo título,
mas você pode modificá-los para terem valores diferentes.
A propriedade Layout é definida no arquivo Pages/_ViewStart.cshtml:
@{
Layout = "_Layout";
}

A marcação anterior define o arquivo de layout Pages/_Layout.cshtml para todos os arquivos do Razor na pasta
Pages. Veja Layout para obter mais informações.
Atualizar o layout
Altere o elemento <title> no arquivo Pages/_Layout.cshtml para usar uma cadeia de caracteres mais curta.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Movie</title>

Localizar o elemento de âncora a seguir no arquivo Pages/_Layout.cshtml.

<a asp-page="/Index" class="navbar-brand">RazorPagesMovie</a>

Substitua o elemento anterior pela marcação a seguir.

<a asp-page="/Movies/Index" class="navbar-brand">RpMovie</a>

O elemento de âncora anterior é um Auxiliar de Marcas. Nesse caso, ele é o Auxiliar de Marcas de Âncora. O
atributo e valor do auxiliar de marcas asp-page="/Movies/Index" cria um link para a Página do Razor
/Movies/Index .

Salve suas alterações e teste o aplicativo clicando no link RpMovie. Consulte o arquivo cshtml no GitHub.
O modelo Criar página
Examine o modelo de página Pages/Movies/Create.cshtml.cs:
// Unused usings removed.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;
using System.Threading.Tasks;

namespace RazorPagesMovie.Pages.Movies
{
public class CreateModel : PageModel
{
private readonly RazorPagesMovie.Models.MovieContext _context;

public CreateModel(RazorPagesMovie.Models.MovieContext context)


{
_context = context;
}

public IActionResult OnGet()


{
return Page();
}

[BindProperty]
public Movie Movie { get; set; }

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

_context.Movie.Add(Movie);
await _context.SaveChangesAsync();

return RedirectToPage("./Index");
}
}
}

O método OnGet inicializa qualquer estado necessário para a página. A página Criar não tem nenhum estado
para inicializar. O método Page cria um objeto PageResult que renderiza a página Create.cshtml.
A propriedade Movie usa o atributo [BindProperty] para aceitar a associação de modelos. Quando o formulário
Criar posta os valores de formulário, o tempo de execução do ASP.NET Core associa os valores postados ao
modelo Movie .
O método OnPostAsync é executado quando a página posta dados de formulário:

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

_context.Movie.Add(Movie);
await _context.SaveChangesAsync();

return RedirectToPage("./Index");
}

Se há algum erro de modelo, o formulário é reexibido juntamente com quaisquer dados de formulário postados.
A maioria dos erros de modelo podem ser capturados no lado do cliente antes do formulário ser enviado. Um
exemplo de um erro de modelo é postar, para o campo de data, um valor que não pode ser convertido em uma
data. Falaremos sobre a validação do lado do cliente e a validação de modelo posteriormente no tutorial.
Se não há nenhum erro de modelo, os dados são salvos e o navegador é redirecionado à página Índice.
A Página do Razor Criar
Examine o arquivo na Página do Razor Pages/Movies/Create.cshtml:

@page
@model RazorPagesMovie.Pages.Movies.CreateModel

@{
ViewData["Title"] = "Create";
}

<h2>Create</h2>

<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.ReleaseDate" class="control-label"></label>
<input asp-for="Movie.ReleaseDate" class="form-control" />
<span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Genre" class="control-label"></label>
<input asp-for="Movie.Genre" class="form-control" />
<span asp-validation-for="Movie.Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Price" class="control-label"></label>
<input asp-for="Movie.Price" class="form-control" />
<span asp-validation-for="Movie.Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>

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

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

O Visual Studio exibe a marca <form method="post"> em uma fonte diferente usada para os auxiliares de marcas:
O elemento <form method="post"> é um auxiliar de marcas de formulário. O auxiliar de marcas de formulário
inclui automaticamente um token antifalsificação.
O mecanismo de scaffolding cria marcação do Razor para cada campo no modelo (exceto a ID ) semelhante ao
seguinte:

<div asp-validation-summary="ModelOnly" class="text-danger"></div>


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

Os auxiliares de marcas de validação ( <div asp-validation-summary e <span asp-validation-for ) exibem erros de


validação. A validação será abordada em mais detalhes posteriormente nesta série.
O auxiliar de marcas de rótulo ( <label asp-for="Movie.Title" class="control-label"></label> ) gera a legenda do
rótulo e o atributo for para a propriedade Title .
O auxiliar de marcas de entrada ( <input asp-for="Movie.Title" class="form-control" /> ) usa os atributos
DataAnnotations e produz os atributos HTML necessários para validação jQuery no lado do cliente.
O tutorial a seguir explica o LocalDB do SQL Server e a propagação do banco de dados.
A N T E R IO R : A D IC IO N A N D O U M P R Ó X IM O : L O C A L D B D O S Q L
M ODELO SERVER
Trabalhando com o SQL Server LocalDB e o
ASP.NET Core
31/01/2018 • 5 min to read • Edit Online

Por Rick Anderson e Joe Audette


O objeto MovieContext cuida da tarefa de se conectar ao banco de dados e mapear objetos Movie para registros
do banco de dados. O contexto de banco de dados é registrado com o contêiner Injeção de Dependência no
método ConfigureServices no arquivo Startup.cs:

public void ConfigureServices(IServiceCollection services)


{
// requires
// using RazorPagesMovie.Models;
// using Microsoft.EntityFrameworkCore;

services.AddDbContext<MovieContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MovieContext")));
services.AddMvc();
}

O sistema de Configuração do ASP.NET Core lê a ConnectionString . Para o desenvolvimento local, ele obtém a
cadeia de conexão do arquivo appsettings.json:

"ConnectionStrings": {
"MovieContext": "Server=(localdb)\\mssqllocaldb;Database=Movie-
1;Trusted_Connection=True;MultipleActiveResultSets=true"
}

Quando você implanta o aplicativo em um servidor de teste ou de produção, você pode usar uma variável de
ambiente ou outra abordagem para definir a cadeia de conexão como um SQL Server real. Consulte
Configuração para obter mais informações.

SQL Server Express LocalDB


O LocalDB é uma versão leve do Mecanismo de Banco de Dados do SQL Server Express, que é direcionado para
o desenvolvimento de programas. O LocalDB é iniciado sob demanda e executado no modo de usuário e,
portanto, não há nenhuma configuração complexa. Por padrão, o banco de dados LocalDB cria arquivos “*.mdf”
no diretório C:/Users/<user>.
No menu Exibir, abra SSOX (Pesquisador de Objetos do SQL Server).
Clique com o botão direito do mouse na tabela Movie e selecione Designer de exibição:

Observe o ícone de chave ao lado de ID . Por padrão, o EF cria uma propriedade chamada ID para a chave
primária.
Clique com o botão direito do mouse na tabela Movie e selecione Exibir dados:
Propagar o banco de dados
Crie uma nova classe chamada SeedData na pasta Models. Substitua o código gerado pelo seguinte:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;

namespace RazorPagesMovie.Models
{
public static class SeedData
{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new MovieContext(
serviceProvider.GetRequiredService<DbContextOptions<MovieContext>>()))
{
// Look for any movies.
if (context.Movie.Any())
{
return; // DB has been seeded
}

context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-2-12"),
Genre = "Romantic Comedy",
Price = 7.99M
},

new Movie
{
Title = "Ghostbusters",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M
},

new Movie
{
Title = "Ghostbusters 2",
ReleaseDate = DateTime.Parse("1986-2-23"),
Genre = "Comedy",
Price = 9.99M
},

new Movie
{
Title = "Rio Bravo",
ReleaseDate = DateTime.Parse("1959-4-15"),
Genre = "Western",
Price = 3.99M
}
);
context.SaveChanges();
}
}
}
}

Se houver um filme no BD, o inicializador de semeadura será retornado e nenhum filme será adicionado.
if (context.Movie.Any())
{
return; // DB has been seeded.
}

Adicionar o inicializador de semeadura


Adicione o inicializador de semeadura ao final do método Main no arquivo Program.cs:

// Unused usings removed.


using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using RazorPagesMovie.Models;
using System;
using Microsoft.EntityFrameworkCore;

namespace RazorPagesMovie
{
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);

using (var scope = host.Services.CreateScope())


{
var services = scope.ServiceProvider;

try
{
var context = services.GetRequiredService<MovieContext>();
// requires using Microsoft.EntityFrameworkCore;
context.Database.Migrate();
// Requires using RazorPagesMovie.Models;
SeedData.Initialize(services);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred seeding the DB.");
}
}

host.Run();
}

public static IWebHost BuildWebHost(string[] args) =>


WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
}

Testar o aplicativo
Exclua todos os registros no BD. Faça isso com os links Excluir no navegador ou no SSOX
Force o aplicativo a ser inicializado (chame os métodos na classe Startup ) para que o método de
semeadura seja executado. Para forçar a inicialização, o IIS Express deve ser interrompido e reiniciado. Faça
isso com uma das seguintes abordagens:
Clique com botão direito do mouse no ícone de bandeja do sistema do IIS Express na área de
notificação e toque em Sair ou Parar site:

Se você estiver executando o VS no modo sem depuração, pressione F5 para executar no modo de
depuração.
Se você estiver executando o VS no modo de depuração, pare o depurador e pressione F5.
O aplicativo mostra os dados propagados:

O próximo tutorial limpará a apresentação dos dados.

Anterior: Páginas do Razor geradas por scaffolding Próximo: Atualização das páginas
Atualizar as páginas geradas
02/02/2018 • 7 min to read • Edit Online

Por Rick Anderson


Temos um bom começo para o aplicativo de filme, mas a apresentação não é ideal. Você não deseja ver a hora
(12:00:00 AM na imagem abaixo) e ReleaseDate deve ser Release Date (duas palavras).

Atualize o código gerado


Abra o arquivo Models/Movie.cs e adicione as linhas realçadas mostradas no seguinte código:

using System;

namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }

[Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}

Clique com o botão direito do mouse em uma linha curvada vermelha > ** Ações Rápidas e Refatorações**.
Selecione using System.ComponentModel.DataAnnotations;

O Visual Studio adiciona using System.ComponentModel.DataAnnotations; .


Abordaremos DataAnnotations no próximo tutorial. O atributo Display especifica o que deve ser exibido no nome
de um campo (neste caso, “Release Date” em vez de “ReleaseDate”). O atributo DataType especifica o tipo de
dados (Data) e, portanto, as informações de hora armazenadas no campo não são exibidas.
Procure Pages/Movies e focalize um link Editar para ver a URL de destino.
Os links Editar, Detalhes e Excluir são gerados pelo Auxiliar de Marcação de Âncora no arquivo
Pages/Movies/Index.cshtml.

@foreach (var item in Model.Movie) {


<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Os Auxiliares de Marcação permitem que o código do servidor participe da criação e renderização de elementos
HTML em arquivos do Razor. No código anterior, o AnchorTagHelper gera dinamicamente o valor do atributo
href HTML da página Razor (a rota é relativa), o asp-page e a ID da rota ( asp-route-id ). Consulte Geração de
URL para Páginas para obter mais informações.
Use Exibir Código-fonte em seu navegador favorito para examinar a marcação gerada. Uma parte do HTML
gerado é mostrada abaixo:
<td>
<a href="/Movies/Edit?id=1">Edit</a> |
<a href="/Movies/Details?id=1">Details</a> |
<a href="/Movies/Delete?id=1">Delete</a>
</td>

Os links gerados dinamicamente passam a ID de filme com uma cadeia de consulta (por exemplo,
http://localhost:5000/Movies/Details?id=2 ).

Atualize as Páginas Editar, Detalhes e Excluir do Razor para que elas usem o modelo de rota “{id:int}”. Altere a
diretiva de página de cada uma dessas páginas de @page para @page "{id:int}" . Execute o aplicativo e, em
seguida, exiba o código-fonte. O HTML gerado adiciona a ID à parte do caminho da URL:

<td>
<a href="/Movies/Edit/1">Edit</a> |
<a href="/Movies/Details/1">Details</a> |
<a href="/Movies/Delete/1">Delete</a>
</td>

Uma solicitação para a página com o modelo de rota “{id:int}” que não inclui o inteiro retornará um erro HTTP
404 (não encontrado). Por exemplo, http://localhost:5000/Movies/Details retornará um erro 404. Para tornar a
ID opcional, acrescente ? à restrição de rota:

@page "{id:int?}"

Atualizar o tratamento de exceção de simultaneidade


Atualize o método OnPostAsync no arquivo Pages/Movies/Edit.cshtml.cs. O seguinte código realçado mostra as
alterações:

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

_context.Attach(Movie).State = EntityState.Modified;

try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!_context.Movie.Any(e => e.ID == Movie.ID))
{
return NotFound();
}
else
{
throw;
}
}

return RedirectToPage("./Index");
}

O código anterior apenas detecta as exceções de simultaneidade quando o primeiro cliente simultâneo exclui o
filme e o segundo cliente simultâneo posta alterações no filme.
Para testar o bloco catch :
Definir um ponto de interrupção em catch (DbUpdateConcurrencyException)
Edite um filme.
Em outra janela do navegador, selecione o link Excluir do mesmo filme e, em seguida, exclua o filme.
Na janela do navegador anterior, poste as alterações no filme.
O código de produção geralmente detectará conflitos de simultaneidade quando dois ou mais clientes atualizarem
um registro ao mesmo tempo. Consulte Tratando conflitos de simultaneidade para obter mais informações.
Análise de postagem e associação
Examine o arquivo Pages/Movies/Edit.cshtml.cs: [!code-csharpMain]
Quando uma solicitação HTTP GET é feita para a página Movies/Edit (por exemplo,
http://localhost:5000/Movies/Edit/2 ):

O método OnGetAsync busca o filme do banco de dados e retorna o método Page .


O método Page renderiza a página Razor Pages/Movies/Edit.cshtml. O arquivo Pages/Movies/Edit.cshtml
contém a diretiva de modelo ( @model RazorPagesMovie.Pages.Movies.EditModel ), que disponibiliza o modelo de
filme na página.
O formulário Editar é exibido com os valores do filme.
Quando a página Movies/Edit é postada:
Os valores de formulário na página são associados à propriedade Movie . O atributo [BindProperty]
habilita a Associação de modelos.

[BindProperty]
public Movie Movie { get; set; }

Se houver erros no estado do modelo (por exemplo, ReleaseDate não pode ser convertida em uma data), o
formulário será postado novamente com os valores enviados.
Se não houver erros do modelo, o filme será salvo.
Os métodos HTTP GET nas páginas Índice, Criar e Excluir do Razor seguem um padrão semelhante. O método
OnPostAsync HTTP POST na página Criar do Razor segue um padrão semelhante ao método OnPostAsync na
página Editar do Razor.
A pesquisa é adicionada no próximo tutorial.

Anterior: Trabalhando com o SQL Server LocalDB Adicionando uma pesquisa


Adicionando pesquisa a um aplicativo de Páginas
Razor
31/01/2018 • 6 min to read • Edit Online

Por Rick Anderson


Neste documento, a funcionalidade de pesquisa é adicionada à página de Índice que permite pesquisar filmes por
gênero ou nome.
Atualize o método OnGetAsync da página de Índice pelo seguinte código:

public async Task OnGetAsync(string searchString)


{
var movies = from m in _context.Movie
select m;

if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}

Movie = await movies.ToListAsync();


}

A primeira linha do método OnGetAsync cria uma consulta LINQ para selecionar os filmes:

var movies = from m in _context.Movie


select m;

A consulta é somente definida neste ponto; ela não foi executada no banco de dados.
Se o parâmetro searchString contiver uma cadeia de caracteres, a consulta de filmes será modificada para filtrar
a cadeia de caracteres de pesquisa:

if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}

O código s => s.Title.Contains() é uma Expressão Lambda. Lambdas são usados em consultas LINQ baseadas
em método como argumentos para métodos de operadores de consulta padrão, como o método Where ou
Contains (usado no código anterior ). As consultas LINQ não são executadas quando são definidas ou quando
são modificadas com uma chamada a um método (como Where , Contains ou OrderBy ). Em vez disso, a
execução da consulta é adiada. Isso significa que a avaliação de uma expressão é atrasada até que seu valor
realizado seja iterado ou o método ToListAsync seja chamado. Consulte Execução de consulta para obter mais
informações.
Observação: o método Contains é executado no banco de dados, não no código C#. A diferenciação de
maiúsculas e minúsculas na consulta depende do banco de dados e do agrupamento. No SQL Server, Contains é
mapeado para SQL LIKE, que não diferencia maiúsculas de minúsculas. No SQLite, com o agrupamento padrão,
ele diferencia maiúsculas de minúsculas.
Navegue para a página Movies e acrescente uma cadeia de consulta, como ?searchString=Ghost , à URL (por
exemplo, http://localhost:5000/Movies?searchString=Ghost ). Os filmes filtrados são exibidos.

Se o modelo de rota a seguir for adicionado à página de Índice, a cadeia de caracteres de pesquisa poderá ser
passada como um segmento de URL (por exemplo, http://localhost:5000/Movies/ghost ).

@page "{searchString?}"

A restrição da rota anterior permite a pesquisa do título como dados de rota (um segmento de URL ), em vez de
como um valor de cadeia de caracteres de consulta. O ? em "{searchString?}" significa que esse é um
parâmetro de rota opcional.

No entanto, você não pode esperar que os usuários modifiquem a URL para pesquisar um filme. Nesta etapa, a
interface do usuário é adicionada para filtrar filmes. Se você adicionou a restrição de rota "{searchString?}" ,
remova-a.
Abra o arquivo Pages/Movies/Index.cshtml e, em seguida, adicione a marcação <form> realçada no seguinte
código:

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
<a asp-page="Create">Create New</a>
</p>

<form>
<p>
Title: <input type="text" name="SearchString">
<input type="submit" value="Filter" />
</p>
</form>

<table class="table">
@*Markup removed for brevity.*@

A marcação <form> HTML usa o Auxiliar de Marcação de Formulário. Quando o formulário é enviado, a cadeia
de caracteres de filtro é enviada para a página Pages/Movies/Index. Salve as alterações e teste o filtro.

Pesquisar por gênero


Adicione as seguintes propriedades realçadas a Pages/Movies/Index.cshtml.cs:
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Models.MovieContext _context;

public IndexModel(RazorPagesMovie.Models.MovieContext context)


{
_context = context;
}

public IList<Movie> Movie { get; set; }


public SelectList Genres { get; set; }
public string MovieGenre { get; set; }

O SelectList Genres contém a lista de gêneros. Isso permite que o usuário selecione um gênero na lista.
A propriedade MovieGenre contém o gênero específico selecionado pelo usuário (por exemplo, “Faroeste”).
Atualize o método OnGetAsync pelo seguinte código:

// Requires using Microsoft.AspNetCore.Mvc.Rendering;


public async Task OnGetAsync(string movieGenre, string searchString)
{
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;

var movies = from m in _context.Movie


select m;

if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}

if (!String.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
Genres = new SelectList(await genreQuery.Distinct().ToListAsync());
Movie = await movies.ToListAsync();
}

O código a seguir é uma consulta LINQ que recupera todos os gêneros do banco de dados.

// Use LINQ to get list of genres.


IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;

O SelectList de gêneros é criado com a projeção dos gêneros distintos.

Genres = new SelectList(await genreQuery.Distinct().ToListAsync());

Adicionando uma pesquisa por gênero


Atualize Index.cshtml da seguinte maneira:
@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
<a asp-page="Create">Create New</a>
</p>

<form>
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>

Title: <input type="text" name="SearchString">


<input type="submit" value="Filter" />
</p>
</form>

<table class="table">
<thead>

Teste o aplicativo pesquisando por gênero, título do filme e por ambos.

Anterior: Atualizando as páginas Próximo: Adicionando um novo campo


Adicionando um novo campo a uma página Razor
31/01/2018 • 7 min to read • Edit Online

Por Rick Anderson


Nesta seção, você usará as Migrações do Entity Framework Code First para adicionar um novo campo ao
modelo e migrar essa alteração ao banco de dados.
Ao usar o EF Code First para criar um banco de dados automaticamente, o Code First adiciona uma tabela ao
banco de dados para ajudar a acompanhar se o esquema do banco de dados está sincronizado com as classes de
modelo com base nas quais ele foi gerado. Se ele não estiver sincronizado, o EF gerará uma exceção. Isso facilita
a descoberta de problemas de código/banco de dados inconsistente.

Adicionando uma propriedade de classificação ao modelo de filme


Abra o arquivo Models/Movie.cs e adicione uma propriedade Rating :

public class Movie


{
public int ID { get; set; }
public string Title { get; set; }

[Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
public string Rating { get; set; }
}

Compile o aplicativo (Ctrl+Shift+B ).


Edite Pages/Movies/Index.cshtml e adicione um campo Rating :

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
<a asp-page="Create">Create New</a>
</p>

<form>
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>

Title: <input type="text" name="SearchString">


<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Price)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Rating)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movie) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rating)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Adicione o campo Rating às páginas Excluir e Detalhes.


Atualize Create.cshtml com um campo Rating . Copie/cole o elemento <div> anterior e permita que o
IntelliSense ajude você a atualizar os campos. O IntelliSense funciona com os Auxiliares de Marcação.
O seguinte código mostra Create.cshtml com um campo Rating :
@page
@model RazorPagesMovie.Pages.Movies.CreateModel

@{
ViewData["Title"] = "Create";
}

<h2>Create</h2>

<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.ReleaseDate" class="control-label"></label>
<input asp-for="Movie.ReleaseDate" class="form-control" />
<span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Genre" class="control-label"></label>
<input asp-for="Movie.Genre" class="form-control" />
<span asp-validation-for="Movie.Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Price" class="control-label"></label>
<input asp-for="Movie.Price" class="form-control" />
<span asp-validation-for="Movie.Price" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Rating" class="control-label"></label>
<input asp-for="Movie.Rating" class="form-control" />
<span asp-validation-for="Movie.Rating" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>

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

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Adicione o campo Rating à página Editar.


O aplicativo não funcionará até que o BD seja atualizado para incluir o novo campo. Se for executado agora, o
aplicativo gerará uma SqlException :

SqlException: Invalid column name 'Rating'.

Esse erro é causado devido à classe de modelo Movie atualizada ser diferente do esquema da tabela Movie do
banco de dados. (Não há nenhuma coluna Rating na tabela de banco de dados.)
Existem algumas abordagens para resolver o erro:
1. Faça com que o Entity Framework remova automaticamente e recrie o banco de dados usando o novo
esquema de classe de modelo. Essa abordagem é conveniente no início do ciclo de desenvolvimento; ela
permite que você desenvolva rapidamente o modelo e o esquema de banco de dados juntos. A
desvantagem é que você perde os dados existentes no banco de dados. Você não deseja usar essa
abordagem em um banco de dados de produção! A remoção do BD em alterações de esquema e o uso de
um inicializador para propagar automaticamente o banco de dados com os dados de teste é muitas vezes
uma maneira produtiva de desenvolver um aplicativo.
2. Modifique explicitamente o esquema do banco de dados existente para que ele corresponda às classes de
modelo. A vantagem dessa abordagem é que você mantém os dados. Faça essa alteração manualmente
ou criando um script de alteração de banco de dados.
3. Use as Migrações do Code First para atualizar o esquema de banco de dados.
Para este tutorial, use as Migrações do Code First.
Atualize a classe SeedData para que ela forneça um valor para a nova coluna. Uma alteração de amostra é
mostrada abaixo, mas é recomendável fazer essa alteração em cada bloco new Movie .

context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-2-12"),
Genre = "Romantic Comedy",
Price = 7.99M,
Rating = "R"
},

Consulte o arquivo SeedData.cs concluído.


Compile a solução.
No menu Ferramentas, selecione Gerenciador de Pacotes NuGet > Console do Gerenciador de Pacotes.
No PMC, insira os seguintes comandos:

Add-Migration Rating
Update-Database

O comando Add-Migration informa à estrutura:


Compare o modelo Movie com o esquema de BD Movie .
Crie um código para migrar o esquema de BD para o novo modelo.
O nome “Classificação” é arbitrário e é usado para nomear o arquivo de migração. É útil usar um nome
significativo para o arquivo de migração.
Se você excluir todos os registros do BD, o inicializador propagará o BD e incluirá o campo Rating . Faça isso
com os links Excluir no navegador ou no SSOX (Pesquisador de Objetos do SQL Server). Para excluir o banco de
dados do SSOX:
Selecione o banco de dados no SSOX.
Clique com o botão direito do mouse no banco de dados e selecione Excluir.
Marque Fechar conexões existentes.
Selecione OK.
No PMC, atualize o banco de dados:

Update-Database

Execute o aplicativo e verifique se você pode criar/editar/exibir filmes com um campo Rating . Se o banco de
dados não for propagado, pare o IIS Express e, em seguida, execute o aplicativo.

Anterior: Adicionando uma pesquisa Próximo: Adicionando Validação


Adicionando uma validação a uma página Razor
31/01/2018 • 14 min to read • Edit Online

Por Rick Anderson


Nesta seção, a lógica de validação é adicionada ao modelo Movie . As regras de validação são impostas sempre
que um usuário cria ou edita um filme.

Validação
Um princípio-chave do desenvolvimento de software é chamado DRY (“Don't Repeat Yourself”). As Páginas
Razor incentivam o desenvolvimento quando a funcionalidade é especificada uma vez e ela é refletida em todo o
aplicativo. O DRY pode ajudar a reduzir a quantidade de código em um aplicativo. O DRY faz com que o código
seja menos propenso a erros e mais fácil de testar e manter.
O suporte de validação fornecido pelas Páginas Razor e pelo Entity Framework é um bom exemplo do princípio
DRY. As regras de validação são especificadas de forma declarativa em um único lugar (na classe de modelo) e as
regras são impostas em qualquer lugar no aplicativo.
Adicionando regras de validação ao modelo de filme
Abra o arquivo Movie.cs. DataAnnotations fornece um conjunto interno de atributos de validação que são
aplicados de forma declarativa a uma classe ou propriedade. DataAnnotations também contém atributos de
formatação como DataType , que ajudam com a formatação e não fornecem validação.
Atualize a classe Movie para aproveitar os atributos de validação Required , StringLength , RegularExpression e
Range .

public class Movie


{
public int ID { get; set; }

[StringLength(60, MinimumLength = 3)]


[Required]
public string Title { get; set; }

[Display(Name = "Release Date")]


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

[Range(1, 100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]
[Required]
[StringLength(30)]
public string Genre { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]
[StringLength(5)]
[Required]
public string Rating { get; set; }
}

Os atributos de validação especificam o comportamento que é imposto nas propriedades do modelo:


Os atributos Required e MinimumLength indicam que uma propriedade deve ter um valor. No entanto, nada
impede que um usuário digite espaços em branco para atender à restrição de validação para um tipo de
permite valor nulo. Os tipos de valor que não permitem valores nulos (como decimal , int , float e
DateTime ) são inerentemente necessários e não precisam do atributo Required .
O atributo RegularExpression limita os caracteres que o usuário pode inserir. No código anterior, Genre e
Rating devem usar apenas letras (espaço em branco, números e caracteres especiais não são permitidos).
O atributo Range restringe um valor a um intervalo especificado.
O atributo StringLength define o tamanho máximo de uma cadeia de caracteres e, opcionalmente, o tamanho
mínimo.
Ter as regras de validação automaticamente impostas pelo ASP.NET Core ajuda a tornar um aplicativo mais
robusto. A validação automática em modelos ajuda a proteger o aplicativo porque você não precisa se lembrar de
aplicá-los quando um novo código é adicionado.
Interface do usuário do erro de validação nas Páginas Razor
Execute o aplicativo e navegue para Pages/Movies.
Selecione o link Criar Novo. Preencha o formulário com alguns valores inválidos. Quando a validação do lado do
cliente do jQuery detecta o erro, ela exibe uma mensagem de erro.
OBSERVAÇÃO
Talvez você não consiga inserir pontos decimais ou vírgulas no campo Price . Para dar suporte à validação do jQuery para
localidades de idiomas diferentes do inglês que usam uma vírgula (“,”) para um ponto decimal e formatos de data diferentes
do inglês dos EUA, você deve tomar medidas para globalizar o aplicativo. Consulte Recursos adicionais para obter mais
informações. Por enquanto, insira apenas números inteiros como 10.

Observe como o formulário renderizou automaticamente uma mensagem de erro de validação em cada campo
que contém um valor inválido. Os erros são impostos no lado do cliente (usando o JavaScript e o jQuery) e no
lado do servidor (quando um usuário tem o JavaScript desabilitado).
Uma vantagem significativa é que nenhuma alteração de código foi necessária nas páginas Criar ou Editar.
Depois que DataAnnotations foi aplicado ao modelo, a interface do usuário de validação foi habilitada. As Páginas
Razor criadas neste tutorial selecionaram automaticamente as regras de validação (usando os atributos de
validação nas propriedades da classe do modelo Movie ). Validação do teste usando a página Editar: a mesma
validação é aplicada.
Os dados de formulário não serão postados no servidor enquanto houver erros de validação do lado do cliente.
Verifique se os dados de formulário não são postados por uma ou mais das seguintes abordagens:
Coloque um ponto de interrupção no método OnPostAsync . Envie o formulário (selecione Criar ou Salvar). O
ponto de interrupção nunca é atingido.
Use a ferramenta Fiddler.
Use as ferramentas do desenvolvedor do navegador para monitorar o tráfego de rede.
Validação do servidor
Quando o JavaScript está desabilitado no navegador, o envio do formulário com erros será postado no servidor.
(Opcional) Teste a validação do servidor:
Desabilite o JavaScript no navegador. Se você não conseguir desabilitar o JavaScript no navegador, tente
outro navegador.
Defina um ponto de interrupção no método OnPostAsync da página Criar ou Editar.
Envie um formulário com erros de validação.
Verifique se o estado do modelo é inválido:

if (!ModelState.IsValid)
{
return Page();
}

O código a seguir mostra uma parte da página Create.cshtml gerada por scaffolding anteriormente no tutorial.
Ele é usado pelas páginas Criar e Editar para exibir o formulário inicial e exibir o formulário novamente, em caso
de erro.

<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>

O Auxiliar de Marcação de Entrada usa os atributos de DataAnnotations e produz os atributos HTML necessários
para a Validação do jQuery no lado do cliente. O Auxiliar de Marcação de Validação exibe erros de validação.
Consulte Validação para obter mais informações.
As páginas Criar e Editar não têm nenhuma regra de validação. As regras de validação e as cadeias de caracteres
de erro são especificadas somente na classe Movie . Essas regras de validação são aplicadas automaticamente às
Páginas Razor que editam o modelo Movie .
Quando a lógica de validação precisa ser alterada, ela é feita apenas no modelo. A validação é aplicada de forma
consistente em todo o aplicativo (a lógica de validação é definida em um único lugar). A validação em um único
lugar ajuda a manter o código limpo e facilita sua manutenção e atualização.

Usando atributos DataType


Examine a classe Movie . O namespace System.ComponentModel.DataAnnotations fornece atributos de formatação,
além do conjunto interno de atributos de validação. O atributo DataType é aplicado às propriedades ReleaseDate
e Price .
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }

[Range(1, 100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }

Os atributos DataType fornecem apenas dicas para que o mecanismo de exibição formate os dados (e fornece
atributos como <a> para as URLs e <a href="mailto:EmailAddress.com"> para o email). Use o atributo
RegularExpression para validar o formato dos dados. O atributo DataType é usado para especificar um tipo de
dados mais específico do que o tipo intrínseco de banco de dados. Os atributos DataType não são atributos de
validação. No aplicativo de exemplo, apenas a data é exibida, sem a hora.
A Enumeração DataType fornece muitos tipos de dados, como Date, Time, PhoneNumber, Currency,
EmailAddress e muito mais. O atributo DataType também pode permitir que o aplicativo forneça
automaticamente recursos específicos a um tipo. Por exemplo, um link mailto: pode ser criado para
DataType.EmailAddress . Um seletor de data pode ser fornecido para DataType.Date em navegadores que dão
suporte a HTML5. Os atributos DataType emitem atributos data- HTML 5 (pronunciados “data dash”) que são
consumidos pelos navegadores HTML 5. Os atributos DataType não fornecem nenhuma validação.
DataType.Date não especifica o formato da data exibida. Por padrão, o campo de dados é exibido de acordo com
os formatos padrão com base nas CultureInfo do servidor.
O atributo DisplayFormat é usado para especificar explicitamente o formato de data:

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]


public DateTime ReleaseDate { get; set; }

A configuração ApplyFormatInEditMode especifica que a formatação deve ser aplicada quando o valor é exibido
para edição. Não é recomendável ter esse comportamento em alguns campos. Por exemplo, em valores de
moeda, você provavelmente não deseja que o símbolo de moeda seja exibido na interface do usuário de edição.
O atributo DisplayFormat pode ser usado por si só, mas geralmente é uma boa ideia usar o atributo DataType . O
atributo DataType transmite a semântica dos dados, ao invés de apresentar como renderizá-lo em uma tela e
oferece os seguintes benefícios que você não obtém com DisplayFormat:
O navegador pode habilitar os recursos do HTML5 (por exemplo, mostrar um controle de calendário, o
símbolo de moeda apropriado à localidade, links de email, etc.)
Por padrão, o navegador renderizará os dados usando o formato correto de acordo com a localidade.
O atributo DataType pode permitir que a estrutura ASP.NET Core escolha o modelo de campo correto para
renderizar os dados. O DisplayFormat , se usado por si só, usa o modelo de cadeia de caracteres.

Observação: a validação do jQuery não funciona com os atributos Range e DateTime . Por exemplo, o seguinte
código sempre exibirá um erro de validação do lado do cliente, mesmo quando a data estiver no intervalo
especificado:

[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]

Geralmente, não é uma boa prática compilar datas rígidas nos modelos e, portanto, o uso do atributo Range e de
DateTime não é recomendado.

O seguinte código mostra como combinar atributos em uma linha:


public class Movie
{
public int ID { get; set; }

[StringLength(60, MinimumLength = 3)]


public string Title { get; set; }

[Display(Name = "Release Date"), DataType(DataType.Date)]


public DateTime ReleaseDate { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$"), Required, StringLength(30)]


public string Genre { get; set; }

[Range(1, 100), DataType(DataType.Currency)]


public decimal Price { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$"), StringLength(5)]
public string Rating { get; set; }
}

A Introdução a Páginas Razor e EF Core mostra mais operações avançadas do EF Core com Páginas Razor.
Publicar no Azure
Confira as instruções sobre como publicar este aplicativo no Azure em Publicar um aplicativo Web ASP.NET Core
no Serviço de Aplicativo do Azure usando o Visual Studio.

Recursos adicionais
Trabalhando com formulários
Globalização e localização
Introdução aos auxiliares de marcação
Criando auxiliares de marcação

Antes: adicionando um novo campo Depois: carregando arquivos


Carregando arquivos em uma página de Razor no
ASP.NET Core
12/02/2018 • 19 min to read • Edit Online

Por Luke Latham


Nesta seção, o carregamento de arquivos com uma página do Razor é demonstrado.
O aplicativo de exemplo Filme da páginas do Razor nesse tutorial usa a associação de modelos simples para
carregar arquivos, o que funciona bem para carregar arquivos pequenos. Para obter informações sobre a
transmissão de arquivos grandes, consulte Carregando arquivos grandes com streaming.
Nas etapas a seguir, um recurso de upload de arquivo da agenda de filmes será adicionado ao aplicativo de
exemplo. Um agendamento de filmes é representado por uma classe Schedule . A classe inclui duas versões do
agendamento. Uma versão é fornecida aos clientes, PublicSchedule . A outra versão é usada para os funcionários
da empresa, PrivateSchedule . Cada versão é carregada como um arquivo separado. O tutorial demonstra como
executar dois carregamentos de arquivos de uma página com um único POST para o servidor.

Considerações sobre segurança


É preciso ter cuidado ao fornecer aos usuários a possibilidade de carregar arquivos em um servidor. Os invasores
podem executar uma negação de serviço e outros ataques em um sistema. Algumas etapas de segurança que
reduzem a probabilidade de um ataque bem-sucedido são:
Fazer o upload de arquivos para uma área de upload de arquivos dedicada no sistema, o que facilita a aplicação
de medidas de segurança sobre o conteúdo carregado. Ao permitir os uploads de arquivos, certifique-se de
que as permissões de execução estejam desabilitadas no local do upload.
Use um nome de arquivo seguro determinado pelo aplicativo, não da entrada do usuário ou o nome do
arquivo carregado.
Permitir somente um conjunto específico de extensões de arquivo aprovadas.
Certifique-se de que as verificações do lado do cliente sejam realizadas no servidor. As verificações do lado do
cliente são fáceis de contornar.
Verifique o tamanho do upload e evite uploads maiores do que o esperado.
Execute um verificador de vírus/malware no conteúdo carregado.

AVISO
Carregar códigos mal-intencionados em um sistema é frequentemente a primeira etapa para executar o código que pode:
Tomar o controle total de um sistema.
Sobrecarregar um sistema, fazendo com que ele falhe completamente.
Comprometer dados do sistema ou de usuários.
Aplicar pichações a uma interface pública.

Adicionar uma classe FileUpload


Criar uma Página Razor para lidar com um par de carregamentos de arquivos. Adicione uma classe FileUpload ,
que é vinculada à página para obter os dados do agendamento. Clique com o botão direito do mouse na pasta
Modelos. Selecione Adicionar > Classe. Nomeie a classe FileUpload e adicione as seguintes propriedades:
using Microsoft.AspNetCore.Http;
using System.ComponentModel.DataAnnotations;

namespace RazorPagesMovie.Models
{
public class FileUpload
{
[Required]
[Display(Name="Title")]
[StringLength(60, MinimumLength = 3)]
public string Title { get; set; }

[Required]
[Display(Name="Public Schedule")]
public IFormFile UploadPublicSchedule { get; set; }

[Required]
[Display(Name="Private Schedule")]
public IFormFile UploadPrivateSchedule { get; set; }
}
}

A classe tem uma propriedade para o título do agendamento e uma propriedade para cada uma das duas versões
do agendamento. Todas as três propriedades são necessárias e o título deve ter 3-60 caracteres.

Adicionar um método auxiliar para carregar arquivos


Para evitar duplicação de código para processar arquivos do agendamento carregados, primeiro, adicione um
método auxiliar estático. Crie uma pasta de Utilitários no aplicativo e adicione um arquivo FileHelpers.cs com o
seguinte conteúdo. O método auxiliar, ProcessFormFile , usa um IFormFile e ModelStateDictionary e retorna uma
cadeia de caracteres que contém o tamanho e o conteúdo do arquivo. O comprimento e o tipo de conteúdo são
verificados. Se o arquivo não passar em uma verificação de validação, um erro será adicionado ao ModelState .

using System;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Utilities
{
public class FileHelpers
{
public static async Task<string> ProcessFormFile(IFormFile formFile, ModelStateDictionary modelState)
{
var fieldDisplayName = string.Empty;

// Use reflection to obtain the display name for the model


// property associated with this IFormFile. If a display
// name isn't found, error messages simply won't show
// a display name.
MemberInfo property =
typeof(FileUpload).GetProperty(formFile.Name.Substring(formFile.Name.IndexOf(".") + 1));

if (property != null)
{
var displayAttribute =
property.GetCustomAttribute(typeof(DisplayAttribute)) as DisplayAttribute;
if (displayAttribute != null)
{
fieldDisplayName = $"{displayAttribute.Name} ";
}
}

// Use Path.GetFileName to obtain the file name, which will


// strip any path information passed as part of the
// FileName property. HtmlEncode the result in case it must
// be returned in an error message.
var fileName = WebUtility.HtmlEncode(Path.GetFileName(formFile.FileName));

if (formFile.ContentType.ToLower() != "text/plain")
{
modelState.AddModelError(formFile.Name,
$"The {fieldDisplayName}file ({fileName}) must be a text file.");
}

// Check the file length and don't bother attempting to


// read it if the file contains no content. This check
// doesn't catch files that only have a BOM as their
// content, so a content length check is made later after
// reading the file's content to catch a file that only
// contains a BOM.
if (formFile.Length == 0)
{
modelState.AddModelError(formFile.Name, $"The {fieldDisplayName}file ({fileName}) is empty.");
}
else if (formFile.Length > 1048576)
{
modelState.AddModelError(formFile.Name, $"The {fieldDisplayName}file ({fileName}) exceeds 1
MB.");
}
else
{
try
{
string fileContents;

// The StreamReader is created to read files that are UTF-8 encoded.


// If uploads require some other encoding, provide the encoding in the
// using statement. To change to 32-bit encoding, change
// new UTF8Encoding(...) to new UTF32Encoding().
using (
var reader =
new StreamReader(
formFile.OpenReadStream(),
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes:
true),
detectEncodingFromByteOrderMarks: true))
{
fileContents = await reader.ReadToEndAsync();

// Check the content length in case the file's only


// content was a BOM and the content is actually
// empty after removing the BOM.
if (fileContents.Length > 0)
{
return fileContents;
}
else
{
modelState.AddModelError(formFile.Name,
$"The {fieldDisplayName}file ({fileName}) is empty.");
}
}
}
catch (Exception ex)
{
{
modelState.AddModelError(formFile.Name,
$"The {fieldDisplayName}file ({fileName}) upload failed. " +
$"Please contact the Help Desk for support. Error:
{ex.Message}");
// Log the exception
}
}

return string.Empty;
}
}
}

Salvar o arquivo no disco


O aplicativo de exemplo salva o conteúdo do arquivo em um campo de banco de dados. Para salvar o conteúdo
do arquivo no disco, use um FileStream:

using (var fileStream = new FileStream(filePath, FileMode.Create))


{
await formFile.CopyToAsync(fileStream);
}

O processo de trabalho deve ter permissões de gravação para o local especificado por filePath .
Salvar o arquivo no Armazenamento de Blobs do Azure
Para carregar o conteúdo do arquivo para o Armazenamento de Blobs do Azure, confira Introdução ao
Armazenamento de Blobs do Azure usando o .NET. O tópico demonstra como usar UploadFromStream para
salvar um FileStream para armazenamento de blobs.

Adicionar a classe de Agendamento


Clique com o botão direito do mouse na pasta Modelos. Selecione Adicionar > Classe. Nomeie a classe
Agendamento e adicione as seguintes propriedades:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models
{
public class Schedule
{
public int ID { get; set; }
public string Title { get; set; }

public string PublicSchedule { get; set; }

[Display(Name = "Public Schedule Size (bytes)")]


[DisplayFormat(DataFormatString = "{0:N1}")]
public long PublicScheduleSize { get; set; }

public string PrivateSchedule { get; set; }

[Display(Name = "Private Schedule Size (bytes)")]


[DisplayFormat(DataFormatString = "{0:N1}")]
public long PrivateScheduleSize { get; set; }

[Display(Name = "Uploaded (UTC)")]


[DisplayFormat(DataFormatString = "{0:F}")]
public DateTime UploadDT { get; set; }
}
}

Usa a classe usa os atributos Display e DisplayFormat , que produzem formatação e títulos fáceis quando os
dados de agendamento são renderizados.

Atualizar o MovieContext
Especifique um DbSet no MovieContext (Models/MovieContext.cs) para os agendamentos:

using Microsoft.EntityFrameworkCore;

namespace RazorPagesMovie.Models
{
public class MovieContext : DbContext
{
public MovieContext(DbContextOptions<MovieContext> options)
: base(options)
{
}

public DbSet<Movie> Movie { get; set; }


public DbSet<Schedule> Schedule { get; set; }
}
}

Adicione a tabela de Agendamento ao banco de dados


Abra o Console Gerenciador de pacote (PMC ): Ferramentas > Gerenciador de pacote NuGet > Console
Gerenciador de pacote.
No PMC, execute os seguintes comandos. Estes comandos adicionam uma tabela Schedule ao banco de dados:

Add-Migration AddScheduleTable
Update-Database

Adicionar uma página do Razor de upload de arquivo


Na pasta Páginas, crie uma pasta Agendamentos. Na pasta Agendamentos, crie uma página chamada
Index.cshtml para carregar um agendamento com o seguinte conteúdo:

@page
@model RazorPagesMovie.Pages.Schedules.IndexModel

@{
ViewData["Title"] = "Schedules";
}

<h2>Schedules</h2>
<hr />

<h3>Upload Schedules</h3>
<div class="row">
<div class="col-md-4">
<form method="post" enctype="multipart/form-data">
<div class="form-group">
<label asp-for="FileUpload.Title" class="control-label"></label>
<input asp-for="FileUpload.Title" type="text" class="form-control" />
<span asp-validation-for="FileUpload.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="FileUpload.UploadPublicSchedule" class="control-label"></label>
<input asp-for="FileUpload.UploadPublicSchedule" type="file" class="form-control"
style="height:auto" />
<span asp-validation-for="FileUpload.UploadPublicSchedule" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="FileUpload.UploadPrivateSchedule" class="control-label"></label>
<input asp-for="FileUpload.UploadPrivateSchedule" type="file" class="form-control"
style="height:auto" />
<span asp-validation-for="FileUpload.UploadPrivateSchedule" class="text-danger"></span>
</div>
<input type="submit" value="Upload" class="btn btn-default" />
</form>
</form>
</div>
</div>

<h3>Loaded Schedules</h3>
<table class="table">
<thead>
<tr>
<th></th>
<th>
@Html.DisplayNameFor(model => model.Schedule[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Schedule[0].UploadDT)
</th>
<th class="text-center">
@Html.DisplayNameFor(model => model.Schedule[0].PublicScheduleSize)
</th>
<th class="text-center">
@Html.DisplayNameFor(model => model.Schedule[0].PrivateScheduleSize)
</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Schedule) {
<tr>
<td>
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.UploadDT)
</td>
<td class="text-center">
@Html.DisplayFor(modelItem => item.PublicScheduleSize)
</td>
<td class="text-center">
@Html.DisplayFor(modelItem => item.PrivateScheduleSize)
</td>
</tr>
}
</tbody>
</table>

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Cada grupo de formulário inclui um <rótulo> que exibe o nome de cada propriedade de classe. Os atributos
Display no modelo FileUpload fornecem os valores de exibição para os rótulos. Por exemplo, o nome de
exibição da propriedade UploadPublicSchedule é definido com [Display(Name="Public Schedule")] e, portanto,
exibe "Agendamento público" no rótulo quando o formulário é renderizado.
Cada grupo de formulário inclui uma validação <span>. Se a entrada do usuário não atender aos atributos de
propriedade definidos na classe FileUpload ou se qualquer uma das verificações de validação do arquivo de
método ProcessFormFile falhar, o modelo não será validado. Quando a validação do modelo falha, uma
mensagem de validação útil é renderizada para o usuário. Por exemplo, a propriedade Title é anotada com
[Required] e [StringLength(60, MinimumLength = 3)] . Se o usuário não fornecer um título, ele receberá uma
mensagem indicando que um valor é necessário. Se o usuário inserir um valor com menos de três caracteres ou
mais de sessenta, ele receberá uma mensagem indicando que o valor tem um comprimento incorreto. Se um
arquivo que não tem nenhum conteúdo for fornecido, uma mensagem aparecerá indicando que o arquivo está
vazio.
Adicionar o modelo de página
Adicione o modelo de página (Index.cshtml.cs) à pasta Schedules:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;
using RazorPagesMovie.Utilities;

namespace RazorPagesMovie.Pages.Schedules
{
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Models.MovieContext _context;

public IndexModel(RazorPagesMovie.Models.MovieContext context)


{
_context = context;
}

[BindProperty]
public FileUpload FileUpload { get; set; }

public IList<Schedule> Schedule { get; private set; }

public async Task OnGetAsync()


{
Schedule = await _context.Schedule.AsNoTracking().ToListAsync();
}

public async Task<IActionResult> OnPostAsync()


{
// Perform an initial check to catch FileUpload class
// attribute violations.
if (!ModelState.IsValid)
{
Schedule = await _context.Schedule.AsNoTracking().ToListAsync();
return Page();
}

var publicScheduleData =
await FileHelpers.ProcessFormFile(FileUpload.UploadPublicSchedule, ModelState);

var privateScheduleData =
await FileHelpers.ProcessFormFile(FileUpload.UploadPrivateSchedule, ModelState);

// Perform a second check to catch ProcessFormFile method


// violations.
if (!ModelState.IsValid)
{
Schedule = await _context.Schedule.AsNoTracking().ToListAsync();
return Page();
}

var schedule = new Schedule()


{
PublicSchedule = publicScheduleData,
PublicScheduleSize = FileUpload.UploadPublicSchedule.Length,
PrivateSchedule = privateScheduleData,
PrivateScheduleSize = FileUpload.UploadPrivateSchedule.Length,
Title = FileUpload.Title,
UploadDT = DateTime.UtcNow
};
_context.Schedule.Add(schedule);
await _context.SaveChangesAsync();

return RedirectToPage("./Index");
}
}
}

O modelo de página ( IndexModel no Index.cshtml.cs) associa a classe FileUpload :

[BindProperty]
public FileUpload FileUpload { get; set; }

O modelo também usa uma lista dos agendamentos ( IList<Schedule> ) para exibir os agendamentos
armazenados no banco de dados na página:

public IList<Schedule> Schedule { get; private set; }

Quando a página for carregada com OnGetAsync , Schedules é preenchido com o banco de dados e usado para
gerar uma tabela HTML de agendamentos carregados:

public async Task OnGetAsync()


{
Schedule = await _context.Schedule.AsNoTracking().ToListAsync();
}

Quando o formulário é enviado para o servidor, o ModelState é verificado. Se for inválido, Schedule é recriado e
a página é renderizada com uma ou mais mensagens de validação informando por que a validação de página
falhou. Se for válido, as propriedades FileUpload serão usadas em OnPostAsync para concluir o upload do
arquivo para as duas versões do agendamento e criar um novo objeto Schedule para armazenar os dados. O
agendamento, em seguida, é salvo no banco de dados:
public async Task<IActionResult> OnPostAsync()
{
// Perform an initial check to catch FileUpload class
// attribute violations.
if (!ModelState.IsValid)
{
Schedule = await _context.Schedule.AsNoTracking().ToListAsync();
return Page();
}

var publicScheduleData =
await FileHelpers.ProcessSchedule(FileUpload.UploadPublicSchedule, ModelState);

var privateScheduleData =
await FileHelpers.ProcessSchedule(FileUpload.UploadPrivateSchedule, ModelState);

// Perform a second check to catch ProcessSchedule method


// violations.
if (!ModelState.IsValid)
{
Schedule = await _context.Schedule.AsNoTracking().ToListAsync();
return Page();
}

var schedule = new Schedule()


{
PublicSchedule = publicScheduleData,
PublicScheduleSize = FileUpload.UploadPublicSchedule.Length,
PrivateSchedule = privateScheduleData,
PrivateScheduleSize = FileUpload.UploadPrivateSchedule.Length,
Title = FileUpload.Title,
UploadDT = DateTime.UtcNow
};

_context.Schedule.Add(schedule);
await _context.SaveChangesAsync();

return RedirectToPage("./Index");
}

Vincular a página do Razor de upload de arquivo


Abra _Layout.cshtml e adicione um link para a barra de navegação para acessar a página de upload de arquivo:

<div class="navbar-collapse collapse">


<ul class="nav navbar-nav">
<li><a asp-page="/Index">Home</a></li>
<li><a asp-page="/Schedules/Index">Schedules</a></li>
<li><a asp-page="/About">About</a></li>
<li><a asp-page="/Contact">Contact</a></li>
</ul>
</div>

Adicionar uma página para confirmar a exclusão de agendamento


Quando o usuário clica para excluir um agendamento, é oferecida uma oportunidade de cancelar a operação.
Adicione uma página de confirmação de exclusão (Delete.cshtml) à pasta Agendamentos:
@page "{id:int}"
@model RazorPagesMovie.Pages.Schedules.DeleteModel

@{
ViewData["Title"] = "Delete Schedule";
}

<h2>Delete Schedule</h2>

<h3>Are you sure you want to delete this?</h3>


<div>
<h4>Schedule</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Schedule.Title)
</dt>
<dd>
@Html.DisplayFor(model => model.Schedule.Title)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Schedule.PublicScheduleSize)
</dt>
<dd>
@Html.DisplayFor(model => model.Schedule.PublicScheduleSize)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Schedule.PrivateScheduleSize)
</dt>
<dd>
@Html.DisplayFor(model => model.Schedule.PrivateScheduleSize)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Schedule.UploadDT)
</dt>
<dd>
@Html.DisplayFor(model => model.Schedule.UploadDT)
</dd>
</dl>

<form method="post">
<input type="hidden" asp-for="Schedule.ID" />
<input type="submit" value="Delete" class="btn btn-default" /> |
<a asp-page="./Index">Back to List</a>
</form>
</div>

O modelo de página (Delete.cshtml.cs) carrega um único agendamento identificado por id nos dados de rota da
solicitação. Adicione o arquivo Delete.cshtml.cs à pasta Agendamentos:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Pages.Schedules
{
public class DeleteModel : PageModel
{
private readonly RazorPagesMovie.Models.MovieContext _context;

public DeleteModel(RazorPagesMovie.Models.MovieContext context)


{
_context = context;
}

[BindProperty]
public Schedule Schedule { get; set; }

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


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

Schedule = await _context.Schedule.SingleOrDefaultAsync(m => m.ID == id);

if (Schedule == null)
{
return NotFound();
}
return Page();
}

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


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

Schedule = await _context.Schedule.FindAsync(id);

if (Schedule != null)
{
_context.Schedule.Remove(Schedule);
await _context.SaveChangesAsync();
}

return RedirectToPage("./Index");
}
}
}

O método OnPostAsync lida com a exclusão do agendamento pelo seu id :


public async Task<IActionResult> OnPostAsync(int? id)
{
if (id == null)
{
return NotFound();
}

Schedule = await _context.Schedule.FindAsync(id);

if (Schedule != null)
{
_context.Schedule.Remove(Schedule);
await _context.SaveChangesAsync();
}

return RedirectToPage("./Index");
}

Após a exclusão com êxito do agendamento, o RedirectToPage envia o usuário para a página Index.cshtml dos
agendamentos.

A página de Razor de agendamentos de trabalho


Quando a página é carregada, os rótulos e entradas para o título do agendamento, o agendamento público e o
agendamento privado são renderizados com um botão de envio:

Selecionar o botão Upload sem preencher nenhum dos campos viola os atributos [Required] no modelo. O
ModelState é inválido. As mensagens de erro de validação são exibidas para o usuário:
Digite duas letras no campo de Título. A mensagem de validação muda para indicar que o título deve ter entre 3 e
60 caracteres:

Quando um ou mais agendamentos são carregados, a seção Agendamentos carregados renderiza os


agendamentos carregados:

O usuário pode clicar no link Excluir para chegar à exibição de confirmação de exclusão, na qual ele tem a
oportunidade de confirmar ou cancelar a operação de exclusão.

Solução de problemas
Para solucionar problemas de informações com o carregamento de IFormFile , consulte Carregamentos de
arquivos no ASP.NET Core: solução de problemas.
Obrigado por concluir esta introdução às Páginas Razor. Agradecemos os comentários. Introdução ao MVC e ao
EF Core é um excelente acompanhamento para este tutorial.

Recursos adicionais
Carregamentos de arquivos no ASP.NET Core
IFormFile

Anterior: validação
Criar um aplicativo Web com o ASP.NET Core MVC
no Windows com o Visual Studio
10/04/2018 • 1 min to read • Edit Online

Este tutorial ensina a usar o desenvolvimento Web do ASP.NET Core MVC com controladores e exibições. As
Páginas Razor é uma nova alternativa no ASP.NET Core 2.0, um modelo de programação baseado em página que
torna a criação da interface do usuário da Web mais fácil e produtiva. É recomendável que você tente o tutorial das
Páginas Razor antes da versão do MVC. O tutorial Páginas do Razor:
É a abordagem preferencial para o desenvolvimento de novos aplicativos.
É mais fácil de acompanhar.
Aborda mais recursos.
Se você escolher este tutorial em vez de a versão Páginas Razor, deixe uma observação explicando o motivo nesta
questão do GitHub.
Há três versões deste tutorial:
Windows: esta série
macOS: Como criar um aplicativo ASP.NET Core MVC com o Visual Studio para Mac
macOS, Linux e Windows: Criar um aplicativo do ASP.NET Core MVC com o Visual Studio Code A série de
tutoriais inclui o seguinte:
1. Introdução
2. Adicionar um controlador
3. Adicionar uma exibição
4. Adicionar um modelo
5. Trabalhar com o SQL Server LocalDB
6. Exibições e métodos do controlador
7. Adicionar pesquisa
8. Adicionar um novo campo
9. Adicionar validação
10. Examinar os métodos Details e Delete
Introdução ao ASP.NET Core MVC e ao Visual
Studio
12/03/2018 • 6 min to read • Edit Online

Por Rick Anderson


Este tutorial ensina a usar o desenvolvimento Web do ASP.NET Core MVC com controladores e exibições. As
Páginas Razor é uma nova alternativa no ASP.NET Core 2.0, um modelo de programação baseado em página
que torna a criação da interface do usuário da Web mais fácil e produtiva. É recomendável que você tente o
tutorial das Páginas Razor antes da versão do MVC. O tutorial Páginas do Razor:
É a abordagem preferencial para o desenvolvimento de novos aplicativos.
É mais fácil de acompanhar.
Aborda mais recursos.
Se você escolher este tutorial em vez de a versão Páginas Razor, deixe uma observação explicando o motivo
nesta questão do GitHub.
Há três versões deste tutorial:
macOS: Como criar um aplicativo ASP.NET Core MVC com o Visual Studio para Mac
Windows: Como criar um aplicativo ASP.NET Core MVC com o Visual Studio
macOS, Linux e Windows: Como criar um aplicativo ASP.NET Core MVC com o Visual Studio Code

Como instalar o Visual Studio e o .NET Core


ASP.NET Core 2.x
ASP.NET Core 1.x
Install the following:
.NET Core 2.0.0 SDK or later.
Visual Studio 2017 version 15.3 or later with the ASP.NET and web development workload.

Como criar um aplicativo Web


No Visual Studio, selecione Arquivo > Novo > Projeto.
Faça as configurações necessárias na caixa de diálogo Novo Projeto:
No painel esquerdo, toque em .NET Core
No painel central, toque em Aplicativo Web ASP.NET Core (.NET Core)
Nomeie o projeto como "MvcMovie" (é importante nomear o projeto como "MvcMovie" para que o
namespace corresponda com o código copiado.)
Toque em OK

ASP.NET Core 2.x


ASP.NET Core 1.x
Faça as configurações necessárias na caixa de diálogo Novo aplicativo Web ASP.NET Core (.NET Core) –
MvcMovie:
Na caixa de lista suspensa do seletor de versão, selecione ASP.NET Core 2.-
Selecione Aplicativo Web (Modelo-Exibir-Controlador)
Toque em OK.
O Visual Studio usou um modelo padrão para o projeto MVC que você acabou de criar. Para que o aplicativo
comece a funcionar agora mesmo, digite um nome de projeto e selecione algumas opções. Este é um projeto
inicial simples e é um bom lugar para começar,
Toque em F5 para executar o aplicativo no modo de depuração ou Ctrl-F5 para executá-lo no modo de não
depuração.

O Visual Studio inicia o IIS Express e executa o aplicativo. Observe que a barra de endereços mostra
localhost:port# e não algo como example.com . Isso ocorre porque localhost é o nome do host padrão do
computador local. Quando o Visual Studio cria um projeto Web, uma porta aleatória é usada para o servidor
Web. Na imagem acima, o número da porta é 5000. A URL no navegador mostra localhost:5000 . Quando
você executar o aplicativo, verá um número de porta diferente.
Iniciar o aplicativo com Ctrl+F5 (modo de não depuração) permite que você faça alterações de código, salve
o arquivo, atualize o navegador e veja as alterações de código. Muitos desenvolvedores preferem usar o
modo de não depuração para iniciar o aplicativo rapidamente e exibir alterações.
Você pode iniciar o aplicativo no modo de não depuração ou de depuração por meio do item de menu
Depurar:

Você pode depurar o aplicativo tocando no botão IIS Express

O modelo padrão fornece os links funcionais Página Inicial, Sobre e Contato. A imagem do navegador acima
não mostra esses links. Dependendo do tamanho do navegador, talvez você precise clicar no ícone de navegação
para mostrá-los.
Se você estava usando o modo de depuração, toque em Shift-F5 para interromper a depuração.
Na próxima parte deste tutorial, saberemos mais sobre o MVC e começaremos a escrever um pouco de código.

Avançar
Adicionando um controlador a um aplicativo
ASP.NET Core MVC com o Visual Studio
31/01/2018 • 11 min to read • Edit Online

Por Rick Anderson


O padrão de arquitetura MVC (Model-View -Controller) separa um aplicativo em três componentes principais:
Model, View e Controller. O padrão MVC ajuda a criar aplicativos que são mais testáveis e fáceis de atualizar
comparado aos aplicativos monolíticos tradicionais. Os aplicativos baseados no MVC contêm:
Models: classes que representam os dados do aplicativo. As classes de modelo usam a lógica de validação
para impor regras de negócio aos dados. Normalmente, os objetos de modelo recuperam e armazenam o
estado do modelo em um banco de dados. Neste tutorial, um modelo Movie recupera dados de filmes de
um banco de dados, fornece-os para a exibição ou atualiza-os. O dados atualizados são gravados em um
banco de dados.
Views: exibições são os componentes que exibem a interface do usuário do aplicativo. Em geral, essa
interface do usuário exibe os dados de modelo.
Controllers: classes que manipulam as solicitações do navegador. Elas recuperam dados de modelo e
chamam modelos de exibição que retornam uma resposta. Em um aplicativo MVC, a exibição mostra
apenas informações; o controlador manipula e responde à entrada e à interação do usuário. Por exemplo, o
controlador manipula os dados de rota e os valores de cadeia de consulta e passa esses valores para o
modelo. O modelo pode usar esses valores para consultar o banco de dados. Por exemplo,
http://localhost:1234/Home/About tem dados de rota de Home (o controlador ) e About (o método de ação
a ser chamado no controlador principal). http://localhost:1234/Movies/Edit/5 é uma solicitação para editar
o filme com ID=5 usando o controlador do filme. Falaremos sobre os dados de rota mais adiante no
tutorial.
O padrão MVC ajuda a criar aplicativos que separam os diferentes aspectos do aplicativo (lógica de entrada,
lógica de negócios e lógica da interface do usuário), ao mesmo tempo que fornece um acoplamento flexível entre
esses elementos. O padrão especifica o local em que cada tipo de lógica deve estar localizado no aplicativo. A
lógica da interface do usuário pertence à exibição. A lógica de entrada pertence ao controlador. A lógica de
negócios pertence ao modelo. Essa separação ajuda a gerenciar a complexidade ao criar um aplicativo, porque
permite que você trabalhe em um aspecto da implementação por vez, sem afetar o código de outro. Por exemplo,
você pode trabalhar no código de exibição sem depender do código da lógica de negócios.
Abrangemos esses conceitos nesta série de tutoriais e mostraremos como usá-los para criar um aplicativo de
filme. O projeto MVC contém pastas para os Controladores e as Exibições.
No Gerenciador de Soluções, clique com o botão direito do mouse em Controladores > Adicionar >
Novo Item
Selecione Classe do Controlador MVC
Na caixa de diálogo Adicionar Novo Item, insira HelloWorldController.

Substitua o conteúdo de Controllers/HelloWorldController.cs pelo seguinte:


using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;

namespace MvcMovie.Controllers
{
public class HelloWorldController : Controller
{
//
// GET: /HelloWorld/

public string Index()


{
return "This is my default action...";
}

//
// GET: /HelloWorld/Welcome/

public string Welcome()


{
return "This is the Welcome action method...";
}
}
}

Cada método public em um controlador pode ser chamado como um ponto de extremidade HTTP. Na amostra
acima, ambos os métodos retornam uma cadeia de caracteres. Observe os comentários que precedem cada
método.
Um ponto de extremidade HTTP é uma URL direcionável no aplicativo Web, como
http://localhost:1234/HelloWorld , e combina o protocolo usado HTTP , o local de rede do servidor Web (incluindo
a porta TCP ) localhost:1234 e o URI de destino HelloWorld .
O primeiro comentário indica que este é um método HTTP GET invocado por meio do acréscimo de
“/HelloWorld/” à URL base. O segundo comentário especifica um método HTTP GET invocado por meio do
acréscimo de “/HelloWorld/Welcome/” à URL. Mais adiante no tutorial, você usará o mecanismo de scaffolding
para gerar métodos HTTP POST .
Execute o aplicativo no modo sem depuração e acrescente “HelloWorld” ao caminho na barra de endereços. O
método Index retorna uma cadeia de caracteres.

O MVC invoca as classes do controlador (e os métodos de ação dentro delas), dependendo da URL de entrada. A
lógica de roteamento de URL padrão usada pelo MVC usa um formato como este para determinar o código a ser
invocado:
/[Controller]/[ActionName]/[Parameters]

Configure o formato de roteamento no método Configure do arquivo Startup.cs.


app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});

Quando você executa o aplicativo e não fornece nenhum segmento de URL, ele usa como padrão o controlador
“Home” e o método “Index” especificado na linha do modelo realçada acima.
O primeiro segmento de URL determina a classe do controlador a ser executada. Portanto,
localhost:xxxx/HelloWorld é mapeado para a classe HelloWorldController . A segunda parte do segmento de URL
determina o método de ação na classe. Portanto, localhost:xxxx/HelloWorld/Index fará com que o método Index
da classe HelloWorldController seja executado. Observe que você precisou apenas navegar para
localhost:xxxx/HelloWorld e o método Index foi chamado por padrão. Isso ocorre porque Index é o método
padrão que será chamado em um controlador se um nome de método não for especificado explicitamente. A
terceira parte do segmento de URL ( id ) refere-se aos dados de rota. Você verá os dados de rota mais adiante
neste tutorial.
Navegue para http://localhost:xxxx/HelloWorld/Welcome . O método Welcome é executado e retorna a cadeia de
caracteres “Este é o método de ação Boas-vindas...”. Para essa URL, o controlador é HelloWorld e Welcome é o
método de ação. Você ainda não usou a parte [Parameters] da URL.

Modifique o código para passar algumas informações de parâmetro da URL para o controlador. Por exemplo,
/HelloWorld/Welcome?name=Rick&numtimes=4 . Altere o método Welcome para incluir dois parâmetros, conforme
mostrado no código a seguir.

// GET: /HelloWorld/Welcome/
// Requires using System.Text.Encodings.Web;
public string Welcome(string name, int numTimes = 1)
{
return HtmlEncoder.Default.Encode($"Hello {name}, NumTimes is: {numTimes}");
}

O código anterior:
Usa o recurso de parâmetro opcional do C# para indicar que o parâmetro numTimes usa 1 como padrão se
nenhum valor é passado para esse parâmetro.
Usa HtmlEncoder.Default.Encode para proteger o aplicativo contra a entrada mal-intencionada (ou seja,
JavaScript).
Usa Cadeias de caracteres interpoladas.
Execute o aplicativo e navegue para:
http://localhost:xxxx/HelloWorld/Welcome?name=Rick&numtimes=4

(Substitua xxxx pelo número da porta.) Você pode tentar valores diferentes para name e numtimes na URL. O
sistema de associação de modelos MVC mapeia automaticamente os parâmetros nomeados da cadeia de consulta
na barra de endereços para os parâmetros no método. Consulte Associação de modelos para obter mais
informações.

Na imagem acima, o segmento de URL ( Parameters ) não é usado e os parâmetros name e numTimes são
transmitidos como cadeias de consulta. O ? (ponto de interrogação) na URL acima é um separador seguido
pelas cadeias de consulta. O caractere & separa as cadeias de consulta.
Substitua o método Welcome pelo seguinte código:

public string Welcome(string name, int ID = 1)


{
return HtmlEncoder.Default.Encode($"Hello {name}, ID: {ID}");
}

Execute o aplicativo e insira a seguinte URL: http://localhost:xxx/HelloWorld/Welcome/3?name=Rick

Agora, o terceiro segmento de URL corresponde ao parâmetro de rota id . O método Welcome contém um
parâmetro id que correspondeu ao modelo de URL no método MapRoute . O ? à direita (em id? ) indica que o
parâmetro id é opcional.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});

Nestes exemplos, o controlador faz a parte “VC” do MVC – ou seja, o trabalho da exibição e do controlador. O
controlador retorna o HTML diretamente. Em geral, você não deseja que os controladores retornem HTML
diretamente, pois isso é muito difícil de codificar e manter. Em vez disso, normalmente, você usa um arquivo de
modelo de exibição do Razor separado para ajudar a gerar a resposta HTML. Faça isso no próximo tutorial.
No Visual Studio, no modo sem depuração (Ctrl+F5), você não precisa compilar o aplicativo após a alteração do
código. Basta salvar o arquivo, atualizar o navegador e você poderá ver as alterações.

Anterior Próximo
Adicionando uma exibição a um aplicativo ASP.NET
Core MVC
31/01/2018 • 15 min to read • Edit Online

Por Rick Anderson


Nesta seção, você modifica a classe HelloWorldController para que ela use os arquivos de modelo de exibição do
Razor para encapsular corretamente o processo de geração de respostas HTML para um cliente.
Crie um arquivo de modelo de exibição usando o Razor. Os modelos de exibição baseados no Razor têm uma
extensão de arquivo .cshtml. Eles fornecem uma maneira elegante de criar a saída HTML usando o C#.
Atualmente, o método Index retorna uma cadeia de caracteres com uma mensagem que é embutida em código
na classe do controlador. Na classe HelloWorldController , substitua o método Index pelo seguinte código:

public IActionResult Index()


{
return View();
}

O código anterior retorna um objeto View . Ele usa um modelo de exibição para gerar uma resposta HTML para o
navegador. Métodos do controlador (também conhecidos como métodos de ação), como o método Index acima,
geralmente retornam um IActionResult (ou uma classe derivada de ActionResult ), não um tipo como cadeia de
caracteres.
Clique com o botão direito do mouse na pasta Exibições e, em seguida, Adicionar > Nova Pasta e nomeie
a pasta HelloWorld.
Clique com o botão direito do mouse na pasta Views/HelloWorld e, em seguida, clique em Adicionar >
Novo Item.
Na caixa de diálogo Adicionar Novo Item – MvcMovie
Na caixa de pesquisa no canto superior direito, insira exibição
Toque em Página de Exibição do MVC
Na caixa Nome, altere o nome, se necessário, para Index.cshtml.
Toque em Adicionar
Substitua o conteúdo do arquivo de exibição Views/HelloWorld/Index.cshtml do Razor pelo seguinte:

@{
ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>Hello from our View Template!</p>

Navegue para http://localhost:xxxx/HelloWorld . O método Index no HelloWorldController não fez muita coisa:
ele executou a instrução return View(); , que especificou que o método deve usar um arquivo de modelo de
exibição para renderizar uma resposta para o navegador. Como você não especificou explicitamente o nome do
arquivo do modelo de exibição, o MVC usou como padrão o arquivo de exibição Index.cshtml na pasta
/Views/HelloWorld. A imagem abaixo mostra a cadeia de caracteres “Olá de nosso modelo de exibição!” embutida
em código na exibição.

Se a janela do navegador for pequena (por exemplo, em um dispositivo móvel), talvez você precise alternar (tocar)
no botão de navegação do Bootstrap no canto superior direito para ver os links Página Inicial, Sobre e Contato.
Alterando exibições e páginas de layout
Toque os links de menu (MvcMovie, Página Inicial e Sobre). Cada página mostra o mesmo layout de menu. O
layout de menu é implementado no arquivo Views/Shared/_Layout.cshtml. Abra o arquivo
Views/Shared/_Layout.cshtml.
Os modelos de layout permitem especificar o layout de contêiner HTML do site em um lugar e, em seguida,
aplicá-lo a várias páginas do site. Localize a linha @RenderBody() . RenderBody é um espaço reservado em que
todas as páginas específicas à exibição criadas são mostradas, encapsuladas na página de layout. Por exemplo, se
você selecionar o link Sobre, a exibição Views/Home/About.cshtml será renderizada dentro do método
RenderBody .

Alterar o título e o link de menu no arquivo de layout


No elemento de título, altere MvcMovie para Movie App . Altere o texto de âncora no modelo de layout de
MvcMovie para Movie App e o controlador de Home para Movies , conforme realçado abaixo:

Observação: a versão do ASP.NET Core 2.0 é ligeiramente diferente. Ela não contém @inject ApplicationInsights
nem @Html.Raw(JavaScriptSnippet.FullScript) .

@inject Microsoft.ApplicationInsights.AspNetCore.JavaScriptSnippet JavaScriptSnippet


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Movie App</title>

<environment names="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment names="Staging,Production">
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-
value="absolute" />
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
@Html.Raw(JavaScriptSnippet.FullScript)
</head>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-
collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a asp-area="" asp-controller="Movies" asp-action="Index" class="navbar-brand">Movie App</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
</ul>
</div>
</div>
</nav>
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>&copy; 2017 - MvcMovie</p>
</footer>
</div>

<environment names="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
</environment>
<environment names="Staging,Production">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
crossorigin="anonymous"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
</script>
<script src="~/js/site.min.js" asp-append-version="true"></script>
</environment>

@RenderSection("Scripts", required: false)


</body>
</html>

AVISO
Ainda não implementamos o controlador Movies e, portanto, se você clicar nesse link, obterá um erro 404 (Não
encontrado).

Salve as alterações e toque no link Sobre. Observe como o título na guia do navegador agora exibe Sobre –
Aplicativo de Filme, em vez de Sobre – Filme Mvc:
Toque no link Contato e observe que o texto do título e de âncora também exibem Aplicativo de Filme.
Conseguimos fazer a alteração uma vez no modelo de layout e fazer com que todas as páginas no site refletissem
o novo texto do link e o novo título.
Examine o arquivo Views/_ViewStart.cshtml:

@{
Layout = "_Layout";
}

O arquivo Views/_ViewStart.cshtml mostra o arquivo Views/Shared/_Layout.cshtml em cada exibição. Use a


propriedade Layout para definir outra exibição de layout ou defina-a como null para que nenhum arquivo de
layout seja usado.
Altere o título da exibição Index .
Abra Views/HelloWorld/Index.cshtml. Há dois lugares para fazer uma alteração:
O texto que é exibido no título do navegador.
O cabeçalho secundário (elemento <h2> ).

Você os tornará ligeiramente diferentes para que possa ver qual parte do código altera qual parte do aplicativo.

@{
ViewData["Title"] = "Movie List";
}

<h2>My Movie List</h2>

<p>Hello from our View Template!</p>

ViewData["Title"] = "Movie List"; no código acima define a propriedade Title do dicionário ViewData como
“Lista de Filmes”. A propriedade Title é usada no elemento HTML <title> na página de layout:

<title>@ViewData["Title"] - Movie App</title>

Salve as alterações e navegue para http://localhost:xxxx/HelloWorld . Observe que o título do navegador, o


cabeçalho primário e os títulos secundários foram alterados. (Se as alterações não forem exibidas no navegador,
talvez o conteúdo armazenado em cache esteja sendo exibido. Pressione Ctrl+F5 no navegador para forçar a
resposta do servidor a ser carregada.) O título do navegador é criado com ViewData["Title"] que definimos no
modelo de exibição Index.cshtml e o “– Aplicativo de Filme” adicional adicionado no arquivo de layout.
Observe também como o conteúdo no modelo de exibição Index.cshtml foi mesclado com o modelo de exibição
Views/Shared/_Layout.cshtml e uma única resposta HTML foi enviada para o navegador. Os modelos de layout
facilitam realmente a realização de alterações que se aplicam a todas as páginas do aplicativo. Para saber mais,
consulte Layout.

Apesar disso, nossos poucos “dados” (nesse caso, a mensagem “Olá de nosso modelo de exibição!”) são
embutidos em código. O aplicativo MVC tem um “V” (exibição) e você tem um “C” (controlador), mas ainda
nenhum “M” (modelo).

Passando dados do controlador para a exibição


As ações do controlador são invocadas em resposta a uma solicitação de URL de entrada. Uma classe de
controlador é o local em que você escreve o código que manipula as solicitações recebidas do navegador. O
controlador recupera dados de uma fonte de dados e decide qual tipo de resposta será enviada novamente para o
navegador. Modelos de exibição podem ser usados em um controlador para gerar e formatar uma resposta HTML
para o navegador.
Os controladores são responsáveis por fornecer os dados necessários para que um modelo de exibição renderize
uma resposta. Uma melhor prática: modelos de exibição não devem executar a lógica de negócios nem interagir
diretamente com um banco de dados. Em vez disso, um modelo de exibição deve funcionar somente com os
dados fornecidos pelo controlador. Manter essa “separação de preocupações” ajuda a manter o código limpo,
testável e com capacidade de manutenção.
Atualmente, o método Welcome na classe HelloWorldController usa um parâmetro name e um ID e, em seguida,
gera os valores diretamente no navegador. Em vez de fazer com que o controlador renderize a resposta como
uma cadeia de caracteres, altere o controlador para que ele use um modelo de exibição. O modelo de exibição
gera uma resposta dinâmica, o que significa que é necessário passar bits de dados apropriados do controlador
para a exibição para gerar a resposta. Faça isso fazendo com que o controlador coloque os dados dinâmicos
(parâmetros) que o modelo de exibição precisa em um dicionário ViewData que pode ser acessado em seguida
pelo modelo de exibição.
Retorne ao arquivo HelloWorldController.cs e altere o método Welcome para adicionar um valor Message e
NumTimes ao dicionário ViewData . O dicionário ViewData é um objeto dinâmico, o que significa que você pode
colocar tudo o que deseja nele; o objeto ViewData não tem nenhuma propriedade definida até que você insira
algo nele. O sistema de associação de modelos MVC mapeia automaticamente os parâmetros nomeados ( name e
numTimes ) da cadeia de consulta na barra de endereços para os parâmetros no método. O arquivo
HelloWorldController.cs completo tem esta aparência:

using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;

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

public IActionResult Welcome(string name, int numTimes = 1)


{
ViewData["Message"] = "Hello " + name;
ViewData["NumTimes"] = numTimes;

return View();
}
}
}

O objeto de dicionário ViewData contém dados que serão passados para a exibição.
Crie um modelo de exibição Boas-vindas chamado Views/HelloWorld/Welcome.cshtml.
Você criará um loop no modelo de exibição Welcome.cshtml que exibe “Olá” NumTimes . Substitua o conteúdo de
Views/HelloWorld/Welcome.cshtml pelo seguinte:

@{
ViewData["Title"] = "Welcome";
}

<h2>Welcome</h2>

<ul>
@for (int i = 0; i < (int)ViewData["NumTimes"]; i++)
{
<li>@ViewData["Message"]</li>
}
</ul>

Salve as alterações e navegue para a seguinte URL:


http://localhost:xxxx/HelloWorld/Welcome?name=Rick&numtimes=4

Os dados são obtidos da URL e passados para o controlador usando o associador de modelo MVC. O controlador
empacota os dados em um dicionário ViewData e passa esse objeto para a exibição. Em seguida, a exibição
renderiza os dados como HTML para o navegador.
Na amostra acima, usamos o dicionário ViewData para passar dados do controlador para uma exibição. Mais
adiante no tutorial, usaremos um modelo de exibição para passar dados de um controlador para uma exibição. A
abordagem de modelo de exibição para passar dados é geralmente a preferida em relação à abordagem do
dicionário ViewData . Consulte ViewModel vs. ViewData vs. ViewBag vs. TempData vs. Session no MVC para
obter mais informações.
Bem, isso foi um tipo de “M” de modelo, mas não o tipo de banco de dados. Vamos ver o que aprendemos e criar
um banco de dados de filmes.

Anterior Próximo
Adicionando um modelo a um aplicativo ASP.NET
Core MVC
31/01/2018 • 16 min to read • Edit Online

Por Rick Anderson e Tom Dykstra


Nesta seção, você adicionará algumas classes para o gerenciamento de filmes em um banco de dados. Essas
classes serão a parte “Model” parte do aplicativo MVC.
Você usa essas classes com o EF Core (Entity Framework Core) para trabalhar com um banco de dados. O EF
Core é uma estrutura ORM (mapeamento relacional de objetos) que simplifica o código de acesso a dados que
você precisa escrever. O EF Core dá suporte a vários mecanismos de banco de dados.
As classes de modelo que serão criadas são conhecidas como classes POCO (de “objetos CLR básicos”) porque
elas não têm nenhuma dependência do EF Core. Elas apenas definem as propriedades dos dados que serão
armazenados no banco de dados.
Neste tutorial, você escreverá as classes de modelo primeiro e o EF Core criará o banco de dados. Uma
abordagem alternativa não abordada aqui é gerar classes de modelo com base em um banco de dados já
existente. Para obter informações sobre essa abordagem, consulte ASP.NET Core – Banco de dados existente.

Adicionar uma classe de modelo de dados


Observação: os modelos do ASP.NET Core 2.0 contêm a pasta Models.
Clique com o botão direito do mouse na pasta Models > Adicionar > Classe. Nomeie a classe Movie e adicione
as seguintes propriedades:

using System;

namespace MvcMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}

O campo ID é necessário para o banco de dados para a chave primária.


Compile o projeto para verificar se não há erros. Agora você tem um Modelo no aplicativo MVC.

Gerando um controlador por scaffolding


No Gerenciador de Soluções, clique com o botão direito do mouse na pasta Controllers > Adicionar >
Controlador.
Se a caixa de diálogo Adicionar Dependências do MVC for exibida:
Atualize o Visual Studio para a última versão. Versões do Visual Studio anteriores a 15.5 mostram essa caixa
de diálogo.
Se não puder atualizar, selecione ADICIONAR e, em seguida, siga as etapas de adição do controlador
novamente.
Na caixa de diálogo Adicionar Scaffold, toque em Controlador MVC com exibições, usando o Entity
Framework > Adicionar.

Preencha a caixa de diálogo Adicionar Controlador:


Classe de modelo: Movie (MvcMovie.Models)
Classe de contexto de dados: selecione o ícone + e adicione o MvcMovie.Models.MvcMovieContext
padrão

Exibições: mantenha o padrão de cada opção marcado


Nome do controlador: mantenha o MoviesController padrão
Toque em Adicionar

O Visual Studio cria:


Uma classe de contexto de banco de dados do Entity Framework Core (Data/MvcMovieContext.cs)
Um controlador de filmes (Controllers/MoviesController.cs)
Arquivos de exibição do Razor para as páginas Criar, Excluir, Detalhes, Editar e Índice (Views/Movies/*.cshtml)
A criação automática do contexto de banco de dados e das exibições e métodos de ação CRUD (criar, ler, atualizar
e excluir) é conhecida como scaffolding. Logo você terá um aplicativo Web totalmente funcional que permitirá que
você gerencie um banco de dados de filmes.
Se você executar o aplicativo e clicar no link Filme do MVC, receberá um erro semelhante ao seguinte:
An unhandled exception occurred while processing the request.

SqlException: Cannot open database "MvcMovieContext-<GUID removed>" requested by the login. The login failed.
Login failed for user 'Rick'.

System.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString

Você precisa criar o banco de dados e usará o recurso Migrações do EF Core para fazer isso. As Migrações
permitem criar um banco de dados que corresponde ao seu modelo de dados e atualizar o esquema de banco de
dados quando o modelo de dados é alterado.

Adicionar ferramentas do EF e executar a migração inicial


Nesta seção, você usará o PMC (Console de Gerenciador de Pacotes) para:
Adicione o pacote de Ferramentas do Entity Framework Core. Esse pacote é necessário adicionar migrações e
atualizar o banco de dados.
Adicione uma migração inicial.
Atualize o banco de dados com a migração inicial.
No menu Ferramentas, selecione Gerenciador de Pacotes NuGet > Console do Gerenciador de Pacotes.

No PMC, insira os seguintes comandos:

Install-Package Microsoft.EntityFrameworkCore.Tools
Add-Migration Initial
Update-Database

Observação: se você receber um erro com o comando Install-Package , abra o Gerenciador de Pacotes NuGet e
pesquise pelo pacote Microsoft.EntityFrameworkCore.Tools . Isso permite que você instale o pacote ou verifique se
ele já está instalado. Observação: consulte a abordagem da CLI caso você tenha problemas com o PMC.
O comando Add-Migration cria um código para criar o esquema de banco de dados inicial. O esquema é baseado
no modelo especificado no DbContext (no arquivo Data/MvcMovieContext.cs). O argumento Initial é usado
para nomear as migrações. Você pode usar qualquer nome, mas, por convenção, escolha um nome que descreve a
migração. Consulte Introdução às migrações para obter mais informações.
O comando Update-Database executa o método Up no arquivo Migrations/<time-stamp>_Initial.cs, que cria o
banco de dados.
Execute as etapas anteriores usando a CLI (interface de linha de comando) em vez do PMC:
Adicione as ferramentas do EF Core ao arquivo .csproj.
Execute os seguintes comandos no console (no diretório do projeto):

dotnet ef migrations add Initial


dotnet ef database update

Testar o aplicativo
Execute o aplicativo e toque no link Filme Mvc.
Toque no link Criar Novo e crie um filme.

Talvez você não possa inserir pontos decimais ou vírgulas no campo Price . Para dar suporte à validação
jQuery para localidades de idiomas diferentes do inglês que usam uma vírgula (",") para ponto decimal e
formatos de data diferentes do inglês dos EUA, você deve tomar medidas para globalizar seu aplicativo.
Consulte https://github.com/aspnet/Docs/issues/4076 e Recursos adicionais para obter mais informações.
Por enquanto, digite apenas números inteiros como 10.
Em algumas localidades, você precisa especificar o formato da data. Consulte o código realçado abaixo.

using System;
using System.ComponentModel.DataAnnotations;

namespace MvcMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}

Falaremos sobre DataAnnotations posteriormente no tutorial.


Tocar em Criar faz com que o formulário seja enviado para o servidor, no qual as informações do filme são salvas
em um banco de dados. O aplicativo redireciona para a URL /Movies, em que as informações do filme recém-
criadas são exibidas.

Crie duas mais entradas de filme adicionais. Experimente os links Editar, Detalhes e Excluir, que estão todos
funcionais.

public void ConfigureServices(IServiceCollection services)


{
// Add framework services.
services.AddMvc();

services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MvcMovieContext")));
}

O código realçado acima mostra o contexto de banco de dados do filme que está sendo adicionado ao contêiner
Injeção de Dependência (No arquivo Startup.cs). services.AddDbContext<MvcMovieContext>(options => especifica o
banco de dados a ser usado e a cadeia de conexão. => é um operador lambda.
Abra o arquivo Controllers/MoviesController.cs e examine o construtor:

public class MoviesController : Controller


{
private readonly MvcMovieContext _context;

public MoviesController(MvcMovieContext context)


{
_context = context;
}

O construtor usa a Injeção de Dependência para injetar o contexto de banco de dados ( MvcMovieContext ) no
controlador. O contexto de banco de dados é usado em cada um dos métodos CRUD no controlador.

Modelos fortemente tipados e a palavra-chave @model


Anteriormente neste tutorial, você viu como um controlador pode passar dados ou objetos para uma exibição
usando o dicionário ViewData . O dicionário ViewData é um objeto dinâmico que fornece uma maneira
conveniente de associação tardia para passar informações para uma exibição.
O MVC também fornece a capacidade de passar objetos de modelo fortemente tipados para uma exibição. Essa
abordagem fortemente tipada permite uma melhor verificação em tempo de compilação do código. O mecanismo
de scaffolding usou essa abordagem (ou seja, passando um modelo fortemente tipado) com a classe
MoviesController e as exibições quando ele criou os métodos e as exibições.

Examine o método Details gerado no arquivo Controllers/MoviesController.cs:

// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}

var movie = await _context.Movie


.SingleOrDefaultAsync(m => m.ID == id);
if (movie == null)
{
return NotFound();
}

return View(movie);
}

O parâmetro id geralmente é passado como dados de rota. Por exemplo,


http://localhost:5000/movies/details/1 define:

O controlador para o controlador movies (o primeiro segmento de URL ).


A ação para details (o segundo segmento de URL ).
A ID como 1 (o último segmento de URL ).
Você também pode passar a id com uma cadeia de consulta da seguinte maneira:
http://localhost:1234/movies/details?id=1

O parâmetro id é definido como um tipo que permite valor nulo ( int? ), caso um valor de ID não seja fornecido.
Um expressão lambda é passada para SingleOrDefaultAsync para selecionar as entidades de filmes que
correspondem ao valor da cadeia de consulta ou de dados da rota.

var movie = await _context.Movie


.SingleOrDefaultAsync(m => m.ID == id);

Se for encontrado um filme, uma instância do modelo Movie será passada para a exibição Details :

return View(movie);

Examine o conteúdo do arquivo Views/Movies/Details.cshtml:

@model MvcMovie.Models.Movie

@{
ViewData["Title"] = "Details";
}

<h2>Details</h2>

<div>
<h4>Movie</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd>
@Html.DisplayFor(model => model.Title)
</dd>
<dt>
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd>
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd>
@Html.DisplayFor(model => model.Genre)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd>
@Html.DisplayFor(model => model.Price)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.ID">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>

Incluindo uma instrução @model na parte superior do arquivo de exibição, você pode especificar o tipo de objeto
esperado pela exibição. Quando você criou o controlador de filmes, o Visual Studio incluiu automaticamente a
seguinte instrução @model na parte superior do arquivo Details.cshtml:

@model MvcMovie.Models.Movie
Esta diretiva @model permite acessar o filme que o controlador passou para a exibição usando um objeto Model
fortemente tipado. Por exemplo, na exibição Details.cshtml, o código passa cada campo de filme para os Auxiliares
de HTML DisplayNameFor e DisplayFor com o objeto Model fortemente tipado. Os métodos Create e Edit e
as exibições também passam um objeto de modelo Movie .
Examine a exibição Index.cshtml e o método Index no controlador Movies. Observe como o código cria um
objeto List quando ele chama o método View . O código passa esta lista Movies do método de ação Index
para a exibição:

// GET: Movies
public async Task<IActionResult> Index()
{
return View(await _context.Movie.ToListAsync());
}

Quando você criou o controlador de filmes, o scaffolding incluiu automaticamente a seguinte instrução @model na
parte superior do arquivo Index.cshtml:

@model IEnumerable<MvcMovie.Models.Movie>

A diretiva @model permite acessar a lista de filmes que o controlador passou para a exibição usando um objeto
Model fortemente tipado. Por exemplo, na exibição Index.cshtml, o código executa um loop pelos filmes com uma
instrução foreach no objeto Model fortemente tipado:
@model IEnumerable<MvcMovie.Models.Movie>

@{
ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</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>

Como o objeto Model é fortemente tipado (como um objeto IEnumerable<Movie> ), cada item no loop é tipado
como Movie . Entre outros benefícios, isso significa que você obtém a verificação em tempo de compilação do
código:
Recursos adicionais
Auxiliares de marcação
Globalização e localização

Anterior – Adicionando uma exibição Próximo – Trabalhando com o SQL


Trabalhando com o SQL Server LocalDB
31/01/2018 • 5 min to read • Edit Online

Por Rick Anderson


O objeto MvcMovieContext cuida da tarefa de se conectar ao banco de dados e mapear objetos Movie para
registros do banco de dados. O contexto de banco de dados é registrado com o contêiner Injeção de Dependência
no método ConfigureServices no arquivo Startup.cs:

public void ConfigureServices(IServiceCollection services)


{
// Add framework services.
services.AddMvc();

services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MvcMovieContext")));
}

O sistema de Configuração do ASP.NET Core lê a ConnectionString . Para o desenvolvimento local, ele obtém a
cadeia de conexão do arquivo appsettings.json:

"ConnectionStrings": {
"MvcMovieContext": "Server=(localdb)\\mssqllocaldb;Database=MvcMovieContext-
2;Trusted_Connection=True;MultipleActiveResultSets=true"
}

Quando você implanta o aplicativo em um servidor de teste ou de produção, você pode usar uma variável de
ambiente ou outra abordagem para definir a cadeia de conexão como um SQL Server real. Consulte
Configuração para obter mais informações.

SQL Server Express LocalDB


O LocalDB é uma versão leve do Mecanismo de Banco de Dados do SQL Server Express, que é direcionado para
o desenvolvimento de programas. O LocalDB é iniciado sob demanda e executado no modo de usuário e,
portanto, não há nenhuma configuração complexa. Por padrão, o banco de dados LocalDB cria arquivos “*.mdf”
no diretório C:/Users/<user>.
No menu Exibir, abra SSOX (Pesquisador de Objetos do SQL Server).
Clique com o botão direito do mouse na tabela Movie > Designer de Exibição
Observe o ícone de chave ao lado de ID . Por padrão, o EF tornará uma propriedade chamada ID a chave
primária.
Clique com o botão direito do mouse na tabela Movie > Dados de Exibição
Propagar o banco de dados
Crie uma nova classe chamada SeedData na pasta Models. Substitua o código gerado pelo seguinte:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;

namespace MvcMovie.Models
{
public static class SeedData
{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new MvcMovieContext(
serviceProvider.GetRequiredService<DbContextOptions<MvcMovieContext>>()))
{
// Look for any movies.
if (context.Movie.Any())
{
return; // DB has been seeded
}

context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-1-11"),
Genre = "Romantic Comedy",
Price = 7.99M
},

new Movie
{
Title = "Ghostbusters ",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M
},

new Movie
{
Title = "Ghostbusters 2",
ReleaseDate = DateTime.Parse("1986-2-23"),
Genre = "Comedy",
Price = 9.99M
},

new Movie
{
Title = "Rio Bravo",
ReleaseDate = DateTime.Parse("1959-4-15"),
Genre = "Western",
Price = 3.99M
}
);
context.SaveChanges();
}
}
}
}

Se houver um filme no BD, o inicializador de semeadura será retornado e nenhum filme será adicionado.
if (context.Movie.Any())
{
return; // DB has been seeded.
}

Adicionar o inicializador de semeadura


ASP.NET Core 2.x
ASP.NET Core 1.x
Adicione o inicializador de semeadura ao método Main no arquivo Program.cs:

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MvcMovie.Models;
using System;

namespace MvcMovie
{
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);

using (var scope = host.Services.CreateScope())


{
var services = scope.ServiceProvider;

try
{
// Requires using MvcMovie.Models;
SeedData.Initialize(services);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred seeding the DB.");
}
}

host.Run();
}

public static IWebHost BuildWebHost(string[] args) =>


WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
}

Testar o aplicativo
Exclua todos os registros no BD. Faça isso com os links Excluir no navegador ou no SSOX.
Force o aplicativo a ser inicializado (chame os métodos na classe Startup ) para que o método de
semeadura seja executado. Para forçar a inicialização, o IIS Express deve ser interrompido e reiniciado. Faça
isso com uma das seguintes abordagens:
Clique com botão direito do mouse no ícone de bandeja do sistema do IIS Express na área de
notificação e toque em Sair ou Parar Site
Se você estiver executando o VS no modo sem depuração, pressione F5 para executar no modo de
depuração
Se você estiver executando o VS no modo de depuração, pare o depurador e pressione F5
O aplicativo mostra os dados propagados.

Anterior Próximo
Exibições e métodos do controlador
31/01/2018 • 15 min to read • Edit Online

Por Rick Anderson


Temos um bom começo para o aplicativo de filme, mas a apresentação não é ideal. Você não deseja ver a hora
(12:00:00 AM na imagem abaixo) e ReleaseDate deve ser escrito em duas palavras.

Abra o arquivo Models/Movie.cs e adicione as linhas realçadas mostradas abaixo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MvcMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }

[Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}

Clique com o botão direito do mouse em uma linha curvada vermelha > Ações Rápidas e Refatorações.
Toque em using System.ComponentModel.DataAnnotations;

O Visual Studio adiciona using System.ComponentModel.DataAnnotations; .


Vamos remover as instruções using que não são necessárias. Elas são exibidas por padrão em uma fonte cinza-
claro. Clique com o botão direito do mouse em qualquer lugar do arquivo Movie.cs > Remover e Classificar
Usos.
O código atualizado:

using System;
using System.ComponentModel.DataAnnotations;

namespace MvcMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }

[Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}

Abordaremos DataAnnotations no próximo tutorial. O atributo Display especifica o que deve ser exibido no nome
de um campo (neste caso, “Release Date” em vez de “ReleaseDate”). O atributo DataType especifica o tipo de
dados (Data) e, portanto, as informações de hora armazenadas no campo não são exibidas.
Procure o controlador Movies e mantenha o ponteiro do mouse pressionado sobre um link Editar para ver a
URL de destino.
Os links Editar, Detalhes e Excluir são gerados pelo Auxiliar de Marcação de Âncora do MVC Core no arquivo
Views/Movies/Index.cshtml.

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

Os Auxiliares de Marcação permitem que o código do servidor participe da criação e renderização de elementos
HTML em arquivos do Razor. No código acima, o AnchorTagHelper gera dinamicamente o valor do atributo href
HTML com base na ID de rota e no método de ação do controlador. Use a opção Exibir Código-fonte em seu
navegador favorito ou as ferramentas do desenvolvedor para examinar a marcação gerada. Uma parte do HTML
gerado é mostrada abaixo:

<td>
<a href="/Movies/Edit/4"> Edit </a> |
<a href="/Movies/Details/4"> Details </a> |
<a href="/Movies/Delete/4"> Delete </a>
</td>

Lembre-se do formato do roteamento definido no arquivo Startup.cs:

app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});

O ASP.NET Core converte http://localhost:1234/Movies/Edit/4 de uma solicitação no método de ação Edit do


controlador Movies com o parâmetro Id igual a 4. (Os métodos do controlador também são conhecidos como
métodos de ação.)
Os Auxiliares de Marcação são um dos novos recursos mais populares do ASP.NET Core. Consulte Recursos
adicionais para obter mais informações.
Abra o controlador Movies e examine os dois métodos de ação Edit . O código a seguir mostra o método
HTTP GET Edit , que busca o filme e popula o formato de edição gerado pelo arquivo Edit.cshtml do Razor.

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}

var movie = await _context.Movie.SingleOrDefaultAsync(m => m.ID == id);


if (movie == null)
{
return NotFound();
}
return View(movie);
}

O código a seguir mostra o método HTTP POST Edit , que processa os valores de filmes postados:

// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (id != movie.ID)
{
return NotFound();
}

if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(movie);
}

O atributo [Bind] é uma maneira de proteger contra o excesso de postagem. Você somente deve incluir as
propriedades do atributo [Bind] que deseja alterar. Consulte Proteger o controlador contra o excesso de
postagem para obter mais informações. ViewModels fornece uma abordagem alternativa para prevenir o excesso
de postagem.
Observe se o segundo método de ação Edit é precedido pelo atributo [HttpPost] .

// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (id != movie.ID)
{
return NotFound();
}

if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(movie);
}

O atributo HttpPost especifica que esse método Edit pode ser invocado somente para solicitações POST. Você
pode aplicar o atributo [HttpGet] ao primeiro método de edição, mas isso não é necessário porque [HttpGet] é
o padrão.
O atributo ValidateAntiForgeryToken é usado para prevenir a falsificação de uma solicitação e é associado a um
token antifalsificação gerado no arquivo de exibição de edição (Views/Movies/Edit.cshtml). O arquivo de exibição
de edição gera o token antifalsificação com o Auxiliar de Marcação de Formulário.

<form asp-action="Edit">

O Auxiliar de Marcação de Formulário gera um token antifalsificação oculto que deve corresponder ao token
antifalsificação gerado [ValidateAntiForgeryToken] no método Edit do controlador Movies. Para obter mais
informações, consulte Falsificação de antissolicitação.
O método HttpGet Edit usa o parâmetro ID de filme, pesquisa o filme usando o método SingleOrDefaultAsync
do Entity Framework e retorna o filme selecionado para a exibição de Edição. Se um filme não for encontrado,
NotFound ( HTTP 404 ) será retornado.
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}

var movie = await _context.Movie.SingleOrDefaultAsync(m => m.ID == id);


if (movie == null)
{
return NotFound();
}
return View(movie);
}

Quando o sistema de scaffolding criou a exibição de Edição, ele examinou a classe Movie e o código criado para
renderizar os elementos <label> e <input> de cada propriedade da classe. O seguinte exemplo mostra a
exibição de Edição que foi gerada pelo sistema de scaffolding do Visual Studio:
@model MvcMovie.Models.Movie

@{
ViewData["Title"] = "Edit";
}

<h2>Edit</h2>

<form asp-action="Edit">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="ID" />
<div class="form-group">
<label asp-for="Title" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Genre" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Price" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
</form>

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

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Observe como o modelo de exibição tem uma instrução @model MvcMovie.Models.Movie na parte superior do
arquivo. @model MvcMovie.Models.Movie especifica que a exibição espera que o modelo de exibição seja do tipo
Movie .

O código com scaffolding usa vários métodos de Auxiliares de Marcação para simplificar a marcação HTML. O –
Auxiliar de Marcação de Rótulo exibe o nome do campo (“Title”, “ReleaseDate”, “Genre” ou “Price”). O Auxiliar de
Marcação de Entrada renderiza um elemento <input> HTML. O Auxiliar de Marcação de Validação exibe todas as
mensagens de validação associadas a essa propriedade.
Execute o aplicativo e navegue para a URL /Movies . Clique em um link Editar. No navegador, exiba a origem da
página. O HTML gerado para o elemento <form> é mostrado abaixo.

<form action="/Movies/Edit/7" method="post">


<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div class="text-danger" />
<input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID"
value="7" />
<div class="form-group">
<label class="control-label col-md-2" for="Genre" />
<div class="col-md-10">
<input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
<span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-
replace="true"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="Price" />
<div class="col-md-10">
<input class="form-control" type="text" data-val="true" data-val-number="The field Price must
be a number." data-val-required="The Price field is required." id="Price" name="Price" value="3.99" />
<span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-
replace="true"></span>
</div>
</div>
<!-- Markup removed for brevity -->
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
<input name="__RequestVerificationToken" type="hidden"
value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-
MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

Os elementos <input> estão em um elemento HTML <form> cujo atributo action está definido para ser postado
para a URL /Movies/Edit/id . Os dados de formulário serão postados com o servidor quando o botão Save
receber um clique. A última linha antes do elemento </form> de fechamento mostra o token XSRF oculto gerado
pelo Auxiliar de Marcação de Formulário.

Processando a solicitação POST


A lista a seguir mostra a versão [HttpPost] do método de ação Edit .
// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (id != movie.ID)
{
return NotFound();
}

if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(movie);
}

O atributo [ValidateAntiForgeryToken] valida o token XSRF oculto gerado pelo gerador de tokens antifalsificação
no Auxiliar de Marcação de Formulário
O sistema de associação de modelos usa os valores de formulário postados e cria um objeto Movie que é
passado como o parâmetro movie . O método ModelState.IsValid verifica se os dados enviados no formulário
podem ser usados para modificar (editar ou atualizar) um objeto Movie . Se os dados forem válidos, eles serão
salvos. Os dados de filmes atualizados (editados) são salvos no banco de dados chamando o método
SaveChangesAsync do contexto de banco de dados. Depois de salvar os dados, o código redireciona o usuário para
o método de ação Index da classe MoviesController , que exibe a coleção de filmes, incluindo as alterações feitas
recentemente.
Antes que o formulário seja postado no servidor, a validação do lado do cliente verifica as regras de validação nos
campos. Se houver erros de validação, será exibida uma mensagem de erro e o formulário não será postado. Se o
JavaScript estiver desabilitado, você não terá a validação do lado do cliente, mas o servidor detectará os valores
postados que não são válidos e os valores de formulário serão exibidos novamente com mensagens de erro. Mais
adiante no tutorial, examinamos a Validação de Modelos mais detalhadamente. O Auxiliar de Marcação de
Validação no modelo de exibição Views/Movies/Edit.cshtml é responsável por exibir as mensagens de erro
apropriadas.
Todos os métodos HttpGet no controlador de filme seguem um padrão semelhante. Eles obtêm um objeto de
filme (ou uma lista de objetos, no caso de Index ) e passam o objeto (modelo) para a exibição. O método Create
passa um objeto de filme vazio para a exibição Create . Todos os métodos que criam, editam, excluem ou, de
outro modo, modificam dados fazem isso na sobrecarga [HttpPost] do método. A modificação de dados em um
método HTTP GET é um risco de segurança. A modificação de dados em um método HTTP GET também viola as
melhores práticas de HTTP e o padrão REST de arquitetura, que especifica que as solicitações GET não devem
alterar o estado do aplicativo. Em outras palavras, a execução de uma operação GET deve ser uma operação
segura que não tem efeitos colaterais e não modifica os dados persistentes.

Recursos adicionais
Globalização e localização
Introdução aos auxiliares de marcação
Criando auxiliares de marcação
Falsificação anti-solicitação
Proteger o controlador contra o excesso de postagem
ViewModels
Auxiliar de marcação de formulário
Auxiliar de marcação de entrada
Auxiliar de marcação de rótulo
Selecionar o auxiliar de marcação
Auxiliar de marcação de validação

Anterior Próximo
Adicionando uma pesquisa a um aplicativo ASP.NET
Core MVC
08/05/2018 • 12 min to read • Edit Online

Por Rick Anderson


Nesta seção, você adiciona a funcionalidade de pesquisa ao método de ação Index que permite pesquisar filmes
por gênero ou nome.
Atualize o método Index pelo seguinte código:

public async Task<IActionResult> Index(string searchString)


{
var movies = from m in _context.Movie
select m;

if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}

return View(await movies.ToListAsync());


}

A primeira linha do método de ação Index cria uma consulta LINQ para selecionar os filmes:

var movies = from m in _context.Movie


select m;

A consulta é somente definida neste ponto; ela não foi executada no banco de dados.
Se o parâmetro searchString contiver uma cadeia de caracteres, a consulta de filmes será modificada para filtrar
o valor da cadeia de caracteres de pesquisa:

if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}

O código s => s.Title.Contains() acima é uma Expressão Lambda. Lambdas são usados em consultas LINQ
baseadas em método como argumentos para métodos de operadores de consulta padrão, como o método Where
ou Contains (usado no código acima). As consultas LINQ não são executadas quando são definidas ou no
momento em que são modificadas com uma chamada a um método, como Where , Contains ou OrderBy . Em vez
disso, a execução da consulta é adiada. Isso significa que a avaliação de uma expressão é atrasada até que seu
valor realizado seja, de fato, iterado ou o método ToListAsync seja chamado. Para obter mais informações sobre a
execução de consulta adiada, consulte Execução da consulta.
Observação: o método Contains é executado no banco de dados, não no código C# mostrado acima. A
diferenciação de maiúsculas e minúsculas na consulta depende do banco de dados e do agrupamento. No SQL
Server, Contains é mapeado para SQL LIKE, que não diferencia maiúsculas de minúsculas. No SQLite, com o
agrupamento padrão, ele diferencia maiúsculas de minúsculas.
Navegue para /Movies/Index . Acrescente uma cadeia de consulta, como ?searchString=Ghost , à URL. Os filmes
filtrados são exibidos.

Se você alterar a assinatura do método Index para que ele tenha um parâmetro chamado id , o parâmetro id
corresponderá o espaço reservado {id} opcional com as rotas padrão definidas em Startup.cs.

app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});

Renomeie rapidamente o parâmetro searchString para id com o comando rename. Clique com o botão direito
do mouse em searchString > Renomear.
Os destinos de renomeação são realçados.

Altere o parâmetro para id e todas as ocorrências de searchString altere para id .

O método Index anterior:


public async Task<IActionResult> Index(string searchString)
{
var movies = from m in _context.Movie
select m;

if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}

return View(await movies.ToListAsync());


}

O método Index atualizado com o parâmetro id :

public async Task<IActionResult> Index(string id)


{
var movies = from m in _context.Movie
select m;

if (!String.IsNullOrEmpty(id))
{
movies = movies.Where(s => s.Title.Contains(id));
}

return View(await movies.ToListAsync());


}

Agora você pode passar o título de pesquisa como dados de rota (um segmento de URL ), em vez de como um
valor de cadeia de consulta.

No entanto, você não pode esperar que os usuários modifiquem a URL sempre que desejarem pesquisar um
filme. Portanto, agora você adicionará uma interface do usuário para ajudá-los a filtrar os filmes. Se você tiver
alterado a assinatura do método Index para testar como passar o parâmetro ID associado à rota, altere-o
novamente para que ele use um parâmetro chamado searchString :
public async Task<IActionResult> Index(string searchString)
{
var movies = from m in _context.Movie
select m;

if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}

return View(await movies.ToListAsync());


}

Abra o arquivo Views/Movies/Index.cshtml e, em seguida, adicione a marcação <form> realçada abaixo:

ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
<a asp-action="Create">Create New</a>
</p>

<form asp-controller="Movies" asp-action="Index">


<p>
Title: <input type="text" name="SearchString">
<input type="submit" value="Filter" />
</p>
</form>

<table class="table">
<thead>

A marcação <form> HTML usa o Auxiliar de Marcação de Formulário. Portanto, quando você enviar o formulário,
a cadeia de caracteres de filtro será enviada para a ação Index do controlador de filmes. Salve as alterações e, em
seguida, teste o filtro.
Não há nenhuma sobrecarga [HttpPost] do método Index que poderia ser esperada. Isso não é necessário,
porque o método não está alterando o estado do aplicativo, apenas filtrando os dados.
Você poderá adicionar o método [HttpPost] Index a seguir.

[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}

O parâmetro notUsed é usado para criar uma sobrecarga para o método Index . Falaremos sobre isso mais
adiante no tutorial.
Se você adicionar esse método, o chamador de ação fará uma correspondência com o método [HttpPost] Index
e o método [HttpPost] Index será executado conforme mostrado na imagem abaixo.

No entanto, mesmo se você adicionar esta versão [HttpPost] do método Index , haverá uma limitação na forma
de como isso tudo foi implementado. Imagine que você deseja adicionar uma pesquisa específica como Favoritos
ou enviar um link para seus amigos para que eles possam clicar para ver a mesma lista filtrada de filmes. Observe
que a URL da solicitação HTTP POST é a mesma que a URL da solicitação GET (localhost:xxxxx/Movies/Index) –
não há nenhuma informação de pesquisa na URL. As informações de cadeia de caracteres de pesquisa são
enviadas ao servidor como um valor de campo de formulário. Verifique isso com as ferramentas do
Desenvolvedor do navegador ou a excelente ferramenta Fiddler. A imagem abaixo mostra as ferramentas do
Desenvolvedor do navegador Chrome:
Veja o parâmetro de pesquisa e o token XSRF no corpo da solicitação. Observe, conforme mencionado no tutorial
anterior, que o Auxiliar de Marcação de Formulário gera um token antifalsificação XSRF. Não modificaremos os
dados e, portanto, não precisamos validar o token no método do controlador.
Como o parâmetro de pesquisa está no corpo da solicitação e não na URL, não é possível capturar essas
informações de pesquisa para adicionar como Favoritos ou compartilhar com outras pessoas. Corrigiremos isso
especificando que a solicitação deve ser HTTP GET .
Observe como o IntelliSense nos ajuda a atualizar a marcação.
Observe a fonte diferenciada na marcação <form> . Essa fonte diferenciada indica que a marcação tem o suporte
de Auxiliares de Marcação.

Agora, quando você enviar uma pesquisa, a URL conterá a cadeia de consulta da pesquisa. A pesquisa também irá
para o método de ação HttpGet Index , mesmo se você tiver um método HttpPost Index .

A seguinte marcação mostra a alteração para a marcação form :

<form asp-controller="Movies" asp-action="Index" method="get">

Adicionando uma pesquisa por gênero


Adicione a seguinte classe MovieGenreViewModel à pasta Models:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace MvcMovie.Models
{
public class MovieGenreViewModel
{
public List<Movie> movies;
public SelectList genres;
public string movieGenre { get; set; }
}
}

O modelo de exibição do gênero de filme conterá:


Uma lista de filmes.
Uma SelectList que contém a lista de gêneros. Isso permitirá que o usuário selecione um gênero na lista.
movieGenre , que contém o gênero selecionado.

Substitua o método Index em MoviesController.cs pelo seguinte código:

// Requires using Microsoft.AspNetCore.Mvc.Rendering;


public async Task<IActionResult> Index(string movieGenre, string searchString)
{
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;

var movies = from m in _context.Movie


select m;

if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}

if (!String.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}

var movieGenreVM = new MovieGenreViewModel();


movieGenreVM.genres = new SelectList(await genreQuery.Distinct().ToListAsync());
movieGenreVM.movies = await movies.ToListAsync();

return View(movieGenreVM);
}

O código a seguir é uma consulta LINQ que recupera todos os gêneros do banco de dados.

// Use LINQ to get list of genres.


IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;

A SelectList de gêneros é criada com a projeção dos gêneros distintos (não desejamos que nossa lista de
seleção tenha gêneros duplicados).
movieGenreVM.genres = new SelectList(await genreQuery.Distinct().ToListAsync())

Adicionando uma pesquisa por gênero à exibição Índice


Atualize Index.cshtml da seguinte maneira:
@model MvcMovie.Models.MovieGenreViewModel

@{
ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
<a asp-action="Create">Create New</a>
</p>

<form asp-controller="Movies" asp-action="Index" method="get">


<p>
<select asp-for="movieGenre" asp-items="Model.genres">
<option value="">All</option>
</select>

Title: <input type="text" name="SearchString">


<input type="submit" value="Filter" />
</p>
</form>

<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.movies[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.movies)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</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>
Examine a expressão lambda usada no seguinte Auxiliar de HTML:
@Html.DisplayNameFor(model => model.movies[0].Title)

No código anterior, o Auxiliar de HTML DisplayNameFor inspeciona a propriedade Title referenciada na


expressão lambda para determinar o nome de exibição. Como a expressão lambda é inspecionada em vez de
avaliada, você não recebe uma violação de acesso quando model , model.movies ou model.movies[0] é null ou
vazio. Quando a expressão lambda é avaliada (por exemplo, @Html.DisplayFor(modelItem => item.Title) ), os
valores da propriedade do modelo são avaliados.
Teste o aplicativo pesquisando por gênero, título do filme e por ambos.

A N T E R IO R P R Ó X IM O
Adicionando um Novo Campo
31/01/2018 • 7 min to read • Edit Online

Por Rick Anderson


Nesta seção, você usará as Migrações do Entity Framework Code First para adicionar um novo campo ao modelo
e migrar essa alteração ao banco de dados.
Ao usar o EF Code First para criar um banco de dados automaticamente, o Code First adiciona uma tabela ao
banco de dados para ajudar a acompanhar se o esquema do banco de dados está sincronizado com as classes de
modelo com base nas quais ele foi gerado. Se ele não estiver sincronizado, o EF gerará uma exceção. Isso facilita a
descoberta de problemas de código/banco de dados inconsistente.

Adicionando uma propriedade de classificação ao modelo de filme


Abra o arquivo Models/Movie.cs e adicione uma propriedade Rating :

public class Movie


{
public int ID { get; set; }
public string Title { get; set; }

[Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
public string Rating { get; set; }
}

Compile o aplicativo (Ctrl+Shift+B ).


Como você adicionou um novo campo à classe Movie , você também precisa atualizar a lista de permissões de
associação para que essa nova propriedade seja incluída. Em MoviesController.cs, atualize o atributo [Bind] dos
métodos de ação Create e Edit para incluir a propriedade Rating :

[Bind("ID,Title,ReleaseDate,Genre,Price,Rating")]

Você também precisa atualizar os modelos de exibição para exibir, criar e editar a nova propriedade Rating na
exibição do navegador.
Edite o arquivo /Views/Movies/Index.cshtml e adicione um campo Rating :
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.movies[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Price)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Rating)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.movies)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rating)
</td>
<td>

Atualize /Views/Movies/Create.cshtml com um campo Rating . Copie/cole o “grupo de formulário” anterior e


permita que o IntelliSense ajude você a atualizar os campos. O IntelliSense funciona com os Auxiliares de
Marcação. Observação: na versão RTM do Visual Studio 2017, você precisa instalar o Razor Language Services
para o IntelliSense do Razor. Isso será corrigido na próxima versão.
O aplicativo não funcionará até que atualizemos o BD para incluir o novo campo. Se você executá-lo agora, obterá
o seguinte SqlException :
SqlException: Invalid column name 'Rating'.

Você está vendo este erro porque a classe de modelo Movie atualizada é diferente do esquema da tabela Movie
do banco de dados existente. (Não há nenhuma coluna Classificação na tabela de banco de dados.)
Existem algumas abordagens para resolver o erro:
1. Faça com que o Entity Framework remova automaticamente e recrie o banco de dados com base no novo
esquema de classe de modelo. Essa abordagem é muito conveniente no início do ciclo de desenvolvimento,
quando você está fazendo o desenvolvimento ativo em um banco de dados de teste; ela permite que você
desenvolva rapidamente o modelo e o esquema de banco de dados juntos. No entanto, a desvantagem é
que você perde os dados existentes no banco de dados – portanto, você não deseja usar essa abordagem
em um banco de dados de produção! Muitas vezes, o uso de um inicializador para propagar um banco de
dados com os dados de teste automaticamente é uma maneira produtiva de desenvolver um aplicativo.
2. Modifique explicitamente o esquema do banco de dados existente para que ele corresponda às classes de
modelo. A vantagem dessa abordagem é que você mantém os dados. Faça essa alteração manualmente ou
criando um script de alteração de banco de dados.
3. Use as Migrações do Code First para atualizar o esquema de banco de dados.
Para este tutorial, usaremos as Migrações do Code First.
Atualize a classe SeedData para que ela forneça um valor para a nova coluna. Uma alteração de amostra é
mostrada abaixo, mas é recomendável fazer essa alteração em cada new Movie .

new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-1-11"),
Genre = "Romantic Comedy",
Rating = "R",
Price = 7.99M
},

Compile a solução.
No menu Ferramentas, selecione Gerenciador de Pacotes NuGet > Console do Gerenciador de Pacotes.
No PMC, insira os seguintes comandos:

Add-Migration Rating
Update-Database

O comando Add-Migration informa a estrutura de migração para examinar o atual modelo Movie com o atual
esquema de BD Movie e criar o código necessário para migrar o BD para o novo modelo. O nome “Classificação”
é arbitrário e é usado para nomear o arquivo de migração. É útil usar um nome significativo para o arquivo de
migração.
Se você excluir todos os registros do BD, o inicializador propagará o BD e incluirá o campo Rating . Faça isso com
os links Excluir no navegador ou no SSOX.
Execute o aplicativo e verifique se você pode criar/editar/exibir filmes com um campo Rating . Você também deve
adicionar o campo Rating aos modelos de exibição Edit , Details e Delete .

Anterior Próximo
Adicionando uma validação
08/05/2018 • 18 min to read • Edit Online

Por Rick Anderson


Nesta seção, você adicionará a lógica de validação ao modelo Movie e garantirá que as regras de validação são
impostas sempre que um usuário criar ou editar um filme.

Mantendo o processo DRY


Um dos princípios de design do MVC é o DRY (“Don't Repeat Yourself”). O ASP.NET MVC incentiva você a
especificar a funcionalidade ou o comportamento somente uma vez e, em seguida, refleti-lo em qualquer lugar de
um aplicativo. Isso reduz a quantidade de código que você precisa escrever e faz com que o código escrito seja
menos propenso a erros e mais fácil de testar e manter.
O suporte de validação fornecido pelo MVC e pelo Entity Framework Core Code First é um bom exemplo do
princípio DRY em ação. Especifique as regras de validação de forma declarativa em um único lugar (na classe de
modelo) e as regras são impostas em qualquer lugar no aplicativo.

Adicionando regras de validação ao modelo de filme


Abra o arquivo Movie.cs. DataAnnotations fornece um conjunto interno de atributos de validação que são
aplicados de forma declarativa a qualquer classe ou propriedade. (Também contém atributos de formatação como
DataType , que ajudam com a formatação e não fornecem nenhuma validação.)

Atualize a classe Movie para aproveitar os atributos de validação Required , StringLength , RegularExpression e
Range internos.

public class Movie


{
public int ID { get; set; }

[StringLength(60, MinimumLength = 3)]


[Required]
public string Title { get; set; }

[Display(Name = "Release Date")]


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

[Range(1, 100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]
[Required]
[StringLength(30)]
public string Genre { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]
[StringLength(5)]
[Required]
public string Rating { get; set; }
}

Os atributos de validação especificam o comportamento que você deseja impor nas propriedades de modelo às
quais eles são aplicados. Os atributos Required e MinimumLength indicam que uma propriedade deve ter um
valor; porém, nada impede que um usuário insira um espaço em branco para atender a essa validação. O atributo
RegularExpression é usado para limitar quais caracteres podem ser inseridos. No código acima, Genre e Rating
devem usar apenas letras (espaço em branco, números e caracteres especiais não são permitidos). O atributo
Range restringe um valor a um intervalo especificado. O atributo StringLength permite definir o tamanho
máximo de uma propriedade de cadeia de caracteres e, opcionalmente, seu tamanho mínimo. Os tipos de valor
(como decimal , int , float , DateTime ) são inerentemente necessários e não precisam do atributo [Required] .
Ter as regras de validação automaticamente impostas pelo ASP.NET ajuda a tornar o aplicativo mais robusto.
Também garante que você não se esqueça de validar algo e inadvertidamente permita dados incorretos no banco
de dados.

Interface do usuário do erro de validação no MVC


Execute o aplicativo e navegue para o controlador Movies.
Toque no link Criar Novo para adicionar um novo filme. Preencha o formulário com alguns valores inválidos.
Assim que a validação do lado do cliente do jQuery detecta o erro, ela exibe uma mensagem de erro.
OBSERVAÇÃO
Talvez você não consiga inserir casas decimais ou vírgulas no campo Price . Para dar suporte à validação do jQuery para
localidades de idiomas diferentes do inglês que usam uma vírgula (“,”) para um ponto decimal e formatos de data diferentes
do inglês dos EUA, você deve tomar medidas para globalizar o aplicativo. Veja Problema 4076 do GitHub para obter
instruções sobre como adicionar casas decimais.

Observe como o formulário renderizou automaticamente uma mensagem de erro de validação apropriada em
cada campo que contém um valor inválido. Os erros são impostos no lado do cliente (usando o JavaScript e o
jQuery) e no lado do servidor (caso um usuário tenha o JavaScript desabilitado).
Uma vantagem significativa é que você não precisa alterar uma única linha de código na classe MoviesController
ou na exibição Create.cshtml para habilitar essa interface do usuário de validação. O controlador e as exibições
criados anteriormente neste tutorial selecionaram automaticamente as regras de validação especificadas com
atributos de validação nas propriedades da classe de modelo Movie . Teste a validação usando o método de ação
Edit e a mesma validação é aplicada.

Os dados de formulário não serão enviados para o servidor enquanto houver erros de validação do lado do
cliente. Verifique isso colocando um ponto de interrupção no método HTTP Post usando a ferramenta Fiddler ou
as ferramentas do Desenvolvedor F12.

Como funciona a validação


Talvez você esteja se perguntando como a interface do usuário de validação foi gerada sem atualizações do
código no controlador ou nas exibições. O código a seguir mostra os dois métodos Create .

// GET: Movies/Create
public IActionResult Create()
{
return View();
}

// POST: Movies/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("ID,Title,ReleaseDate,Genre,Price, Rating")] Movie movie)
{
if (ModelState.IsValid)
{
_context.Add(movie);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(movie);
}

O primeiro método de ação (HTTP GET) Create exibe o formulário Criar inicial. A segunda versão ( [HttpPost] )
manipula a postagem de formulário. O segundo método Create (a versão [HttpPost] ) chama
ModelState.IsValid para verificar se o filme tem erros de validação. A chamada a esse método avalia os atributos
de validação que foram aplicados ao objeto. Se o objeto tiver erros de validação, o método Create exibirá o
formulário novamente. Se não houver erros, o método salvará o novo filme no banco de dados. Em nosso
exemplo de filme, o formulário não é postado no servidor quando há erros de validação detectados no lado do
cliente; o segundo método Create nunca é chamado quando há erros de validação do lado do cliente. Se você
desabilitar o JavaScript no navegador, a validação do cliente será desabilitada e você poderá testar o método
Create HTTP POST ModelState.IsValid detectando erros de validação.
Defina um ponto de interrupção no método [HttpPost] Create e verifique se o método nunca é chamado; a
validação do lado do cliente não enviará os dados de formulário quando forem detectados erros de validação. Se
você desabilitar o JavaScript no navegador e, em seguida, enviar o formulário com erros, o ponto de interrupção
será atingido. Você ainda pode obter uma validação completa sem o JavaScript.
A imagem a seguir mostra como desabilitar o JavaScript no navegador FireFox.

A imagem a seguir mostra como desabilitar o JavaScript no navegador Chrome.


Depois de desabilitar o JavaScript, poste os dados inválidos e execute o depurador em etapas.

Veja abaixo uma parte do modelo de exibição Create.cshtml gerada por scaffolding anteriormente no tutorial. Ela
é usada pelos métodos de ação mostrados acima para exibir o formulário inicial e exibi-lo novamente em caso de
erro.
<form asp-action="Create">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />

<div asp-validation-summary="ModelOnly" class="text-danger"></div>


<div class="form-group">
<label asp-for="Title" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
</div>

@*Markup removed for brevity.*@


</div>
</form>

O Auxiliar de Marcação de Entrada usa os atributos de DataAnnotations e produz os atributos HTML necessários
para a Validação do jQuery no lado do cliente. O Auxiliar de Marcação de Validação exibe erros de validação.
Consulte Validação para obter mais informações.
O que é realmente interessante nessa abordagem é que o controlador nem o modelo de exibição Create sabem
nada sobre as regras de validação reais que estão sendo impostas ou as mensagens de erro específicas exibidas.
As regras de validação e as cadeias de caracteres de erro são especificadas somente na classe Movie . Essas
mesmas regras de validação são aplicadas automaticamente à exibição Edit e a outros modelos de exibição que
podem ser criados e que editam o modelo.
Quando você precisar alterar a lógica de validação, faça isso exatamente em um lugar, adicionando atributos de
validação ao modelo (neste exemplo, a classe Movie ). Você não precisa se preocupar se diferentes partes do
aplicativo estão inconsistentes com a forma como as regras são impostas – toda a lógica de validação será
definida em um lugar e usada em todos os lugares. Isso mantém o código muito limpo e torna-o mais fácil de
manter e desenvolver. Além disso, isso significa que você respeitará totalmente o princípio DRY.

Usando atributos DataType


Abra o arquivo Movie.cs e examine a classe Movie . O namespace System.ComponentModel.DataAnnotations fornece
atributos de formatação, além do conjunto interno de atributos de validação. Já aplicamos um valor de
enumeração DataType à data de lançamento e aos campos de preço. O código a seguir mostra as propriedades
ReleaseDate e Price com o atributo DataType apropriado.

[Display(Name = "Release Date")]


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

[Range(1, 100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }

Os atributos DataType fornecem dicas apenas para que o mecanismo de exibição formate os dados (e fornece
atributos como <a> para as URLs e <a href="mailto:EmailAddress.com"> para o email). Use o atributo
RegularExpression para validar o formato dos dados. O atributo DataType é usado para especificar um tipo de
dados mais específico do que o tipo intrínseco de banco de dados; eles não são atributos de validação. Nesse caso,
apenas desejamos acompanhar a data, não a hora. A Enumeração DataType fornece muitos tipos de dados, como
Date, Time, PhoneNumber, Currency, EmailAddress e muito mais. O atributo DataType também pode permitir
que o aplicativo forneça automaticamente recursos específicos a um tipo. Por exemplo, um link mailto: pode ser
criado para DataType.EmailAddress e um seletor de data pode ser fornecido para DataType.Date em navegadores
que dão suporte a HTML5. Os atributos DataType emitem atributos data- HTML 5 (pronunciados “data dash”)
que são reconhecidos pelos navegadores HTML 5. Os atributos DataType não fornecem nenhuma validação.
DataType.Date não especifica o formato da data exibida. Por padrão, o campo de dados é exibido de acordo com
os formatos padrão com base nas CultureInfo do servidor.
O atributo DisplayFormat é usado para especificar explicitamente o formato de data:

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]


public DateTime ReleaseDate { get; set; }

A configuração ApplyFormatInEditMode especifica que a formatação também deve ser aplicada quando o valor é
exibido em uma caixa de texto para edição. (Talvez você não deseje isso em alguns campos – por exemplo, para
valores de moeda, provavelmente, você não deseja exibir o símbolo de moeda na caixa de texto para edição.)
Você pode usar o atributo DisplayFormat por si só, mas geralmente é uma boa ideia usar o atributo DataType . O
atributo DataType transmite a semântica dos dados, ao invés de apresentar como renderizá-lo em uma tela e
oferece os seguintes benefícios que você não obtém com DisplayFormat:
O navegador pode habilitar os recursos do HTML5 (por exemplo, mostrar um controle de calendário, o
símbolo de moeda apropriado à localidade, links de email, etc.)
Por padrão, o navegador renderizará os dados usando o formato correto de acordo com a localidade.
O atributo DataType pode permitir que o MVC escolha o modelo de campo correto para renderizar os
dados (o DisplayFormat , se usado por si só, usa o modelo de cadeia de caracteres).

OBSERVAÇÃO
A validação do jQuery não funciona com os atributos Range e DateTime . Por exemplo, o seguinte código sempre exibirá
um erro de validação do lado do cliente, mesmo quando a data estiver no intervalo especificado:

[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]

Você precisará desabilitar a validação de data do jQuery para usar o atributo Range com DateTime . Geralmente,
não é uma boa prática compilar datas rígidas nos modelos e, portanto, o uso do atributo Range e de DateTime
não é recomendado.
O seguinte código mostra como combinar atributos em uma linha:
public class Movie
{
public int ID { get; set; }

[StringLength(60, MinimumLength = 3)]


public string Title { get; set; }

[Display(Name = "Release Date"), DataType(DataType.Date)]


public DateTime ReleaseDate { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$"), Required, StringLength(30)]


public string Genre { get; set; }

[Range(1, 100), DataType(DataType.Currency)]


public decimal Price { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$"), StringLength(5)]
public string Rating { get; set; }
}

Na próxima parte da série, examinaremos o aplicativo e faremos algumas melhorias nos métodos Details e
Delete gerados automaticamente.

Recursos adicionais
Trabalhando com formulários
Globalização e localização
Introdução aos auxiliares de marcação
Criando auxiliares de marcação

A N T E R IO R P R Ó X IM O
Examinando os métodos Details e Delete
31/01/2018 • 6 min to read • Edit Online

Por Rick Anderson


Abra o controlador Movie e examine o método Details :

// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}

var movie = await _context.Movie


.SingleOrDefaultAsync(m => m.ID == id);
if (movie == null)
{
return NotFound();
}

return View(movie);
}

O mecanismo de scaffolding MVC que criou este método de ação adiciona um comentário mostrando uma
solicitação HTTP que invoca o método. Nesse caso, é uma solicitação GET com três segmentos de URL, o
controlador Movies , o método Details e um valor id . Lembre-se que esses segmentos são definidos em
Startup.cs.

app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});

O EF facilita a pesquisa de dados usando o método SingleOrDefaultAsync . Um recurso de segurança importante


interno do método é que o código verifica se o método de pesquisa encontrou um filme antes de tentar fazer algo
com ele. Por exemplo, um hacker pode introduzir erros no site alterando a URL criada pelos links de
http://localhost:xxxx/Movies/Details/1 para algo como http://localhost:xxxx/Movies/Details/12345 (ou algum
outro valor que não representa um filme real). Se você não marcou um filme nulo, o aplicativo gerará uma exceção.
Examine os métodos Delete e DeleteConfirmed .
// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}

var movie = await _context.Movie


.SingleOrDefaultAsync(m => m.ID == id);
if (movie == null)
{
return NotFound();
}

return View(movie);
}

// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var movie = await _context.Movie.SingleOrDefaultAsync(m => m.ID == id);
_context.Movie.Remove(movie);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}

Observe que o método HTTP GET Delete não exclui o filme especificado, mas retorna uma exibição do filme em que
você pode enviar (HttpPost) a exclusão. A execução de uma operação de exclusão em resposta a uma solicitação
GET (ou, de fato, a execução de uma operação de edição, criação ou qualquer outra operação que altera dados)
abre uma falha de segurança.
O método [HttpPost] que exclui os dados é chamado DeleteConfirmed para fornecer ao método HTTP POST um
nome ou uma assinatura exclusiva. As duas assinaturas de método são mostradas abaixo:

// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{

// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{

O CLR (Common Language Runtime) exige que os métodos sobrecarregados tenham uma assinatura de
parâmetro exclusiva (mesmo nome de método, mas uma lista diferente de parâmetros). No entanto, aqui você
precisa de dois métodos Delete – um para GET e outro para POST – que têm a mesma assinatura de parâmetro.
(Ambos precisam aceitar um único inteiro como parâmetro.)
Há duas abordagens para esse problema e uma delas é fornecer aos métodos nomes diferentes. Foi isso o que o
mecanismo de scaffolding fez no exemplo anterior. No entanto, isso apresenta um pequeno problema: o ASP.NET
mapeia os segmentos de URL para os métodos de ação por nome e se você renomear um método, o roteamento
normalmente não conseguirá encontrar esse método. A solução é o que você vê no exemplo, que é adicionar o
atributo ActionName("Delete") ao método DeleteConfirmed . Esse atributo executa o mapeamento para o sistema de
roteamento, de modo que uma URL que inclui /Delete/ para uma solicitação POST encontre o método
DeleteConfirmed .
Outra solução alternativa comum para métodos que têm nomes e assinaturas idênticos é alterar artificialmente a
assinatura do método POST para incluir um parâmetro extra (não utilizado). Foi isso o que fizemos em uma
postagem anterior quando adicionamos o parâmetro notUsed . Você pode fazer o mesmo aqui para o método
[HttpPost] Delete :

// POST: Movies/Delete/6
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(int id, bool notUsed)

Publicar no Azure
Confira as instruções sobre como publicar este aplicativo no Azure usando o Visual Studio em Publicar um
aplicativo Web ASP.NET Core no Serviço de Aplicativo do Azure usando o Visual Studio. O aplicativo também pode
ser publicado a partir da linha de comando.
Obrigado por concluir esta introdução ao ASP.NET Core MVC. Agradecemos todos os comentários deixados.
Introdução ao MVC e ao EF Core é um excelente acompanhamento para este tutorial.

Anterior
Criar APIs Web com o ASP.NET Core
08/05/2018 • 6 min to read • Edit Online

Por Scott Addie


Exibir ou baixar código de exemplo (como baixar)
Este documento explica como criar uma API Web no ASP.NET Core e quando é mais adequado usar cada recurso.

Derivar a classe por meio do ControllerBase


Herde da classe ControllerBase em um controlador destinado a servir como uma API Web. Por exemplo:
[!code-csharp]
[!code-csharp]
A classe ControllerBase fornece acesso a várias propriedades e métodos. No exemplo anterior, alguns desses
métodos incluem BadRequest e CreatedAtAction. Esses métodos são invocados em métodos de ação para retornar
os códigos de status HTTP 400 e 201, respectivamente. A propriedade ModelState, também fornecida pelo
ControllerBase , é acessada para executar a validação do modelo de solicitação.

Anotar classe com o ApiControllerAttribute


O ASP.NET Core 2.1 apresenta o atributo [ApiController] para denotar uma classe do controlador API Web. Por
exemplo:
[!code-csharp]
Esse atributo é geralmente associado ao ControllerBase para obter acesso a propriedades e métodos úteis.
ControllerBase fornece acesso aos métodos como NotFound e File.

Outra abordagem é criar uma classe de base de controlador personalizada anotada com o atributo
[ApiController] :

[!code-csharp]
As seções a seguir descrevem os recursos de conveniência adicionados pelo atributo.
Respostas automáticas do HTTP 400
Os erros de validação disparam uma resposta HTTP 400 automaticamente. O código a seguir se torna
desnecessário em suas ações:
[!code-csharp]
Esse comportamento padrão é desabilitado com o código a seguir no Startup.ConfigureServices:
[!code-csharp]
Inferência de parâmetro de origem de associação
Um atributo de origem de associação define o local no qual o valor do parâmetro de uma ação é encontrado. Os
seguintes atributos da origem da associação existem:
ATRIBUTO FONTE DE ASSOCIAÇÃO

[FromBody] Corpo da solicitação

[FromForm] Dados do formulário no corpo da solicitação

[FromHeader] Cabeçalho da solicitação

[FromQuery] Parâmetro de cadeia de caracteres de consulta de solicitação

[FromRoute] Dados de rota da solicitação atual

[FromServices] O serviço de solicitação inserido como um parâmetro de ação

OBSERVAÇÃO
Não use [FromRoute] , se os valores puderem conter %2f (que é / ), porque %2f não ficará sem escape para / . Use
[FromQuery] , se o valor puder conter %2f .

Sem o atributo [ApiController] , os atributos da origem da associação são definidos explicitamente. No exemplo a
seguir, o atributo [FromQuery] indica que o valor do parâmetro discontinuedOnly é fornecido na cadeia de
caracteres de consulta da URL de solicitação:
[!code-csharp]
Regras de inferência são aplicadas para as fontes de dados padrão dos parâmetros de ação. Essas regras
configuram as origens da associação que você provavelmente aplicaria manualmente aos parâmetros de ação. Os
atributos da origem da associação se comportam da seguinte maneira:
[FromBody] é inferido para parâmetros de tipo complexo. Uma exceção a essa regra é qualquer tipo complexo,
interno com um significado especial, como IFormCollection e CancellationToken. O código de inferência da
origem da associação ignora esses tipos especiais. Quando uma ação tiver mais de um parâmetro especificado
explicitamente (via [FromBody] ) ou inferido como limite do corpo da solicitação, uma exceção será lançada. Por
exemplo, as seguintes assinaturas de ação causam uma exceção:
[!code-csharp]
[FromForm ] é inferido para os parâmetros de tipo de ação IFormFile e IFormFileCollection. Ele não é inferido
para qualquer tipo simples ou definido pelo usuário.
[FromRoute] é inferido para qualquer nome de parâmetro de ação correspondente a um parâmetro no modelo
de rota. Quando várias rotas correspondem a um parâmetro de ação, qualquer valor de rota é considerado
[FromRoute] .
[FromQuery] é inferido para todos os outros parâmetros de ação.

As regras de inferência padrão são desabilitadas com o código a seguir em Startup.ConfigureServices:


[!code-csharp]
Inferência de solicitação de várias partes/dados de formulário
Quando um parâmetro de ação é anotado com o atributo [FromForm], o tipo de conteúdo de solicitação
multipart/form-data é inferido.

O comportamento padrão é desabilitado com o código a seguir em Startup.ConfigureServices:


[!code-csharp]
Requisito de roteamento de atributo
O roteamento de atributo se torna um requisito. Por exemplo:
[!code-csharp]
As ações são inacessíveis por meio de rotas convencionais definidas em UseMvc ou UseMvcWithDefaultRoute em
Startup.Configure.

Recursos adicionais
Tipos de retorno de ação do controlador
Formatadores personalizados
Formatar dados de resposta
Páginas de Ajuda usando o Swagger
Roteamento para ações do controlador
Criar uma API Web com o ASP.NET Core MVC e o
Visual Studio Code no Linux, macOS e Windows
02/02/2018 • 16 min to read • Edit Online

Por Rick Anderson e Mike Wasson


Neste tutorial, compile uma API Web para gerenciar uma lista de itens de "tarefas pendentes". Uma interface do
usuário não é construída.
Há três versões deste tutorial:
macOS, Linux, Windows: API Web com o Visual Studio Code (Este tutorial)
macOS: API Web com o Visual Studio para Mac
Windows: API Web com o Visual Studio para Windows

Visão geral
Este tutorial cria a seguinte API:

API DESCRIÇÃO CORPO DA SOLICITAÇÃO CORPO DA RESPOSTA

GET /api/todo Obter todos os itens de Nenhum Matriz de itens de tarefas


tarefas pendentes pendentes

GET /api/todo/{id} Obter um item por ID Nenhum Item de tarefas pendentes

POST /api/todo Adicionar um novo item Item de tarefas pendentes Item de tarefas pendentes

PUT /api/todo/{id} Atualizar um item existente Item de tarefas pendentes Nenhum

DELETE /api/todo/{id} Excluir um item Nenhum Nenhum

O diagrama a seguir mostra o design básico do aplicativo.

O cliente é tudo o que consome a API Web (aplicativo móvel, navegador, etc.). Este tutorial não cria um
cliente. Postman ou curl são usados como clientes para testar o aplicativo.
Um modelo é um objeto que representa os dados no aplicativo. Nesse caso, o único modelo é um item de
tarefas pendentes. Modelos são representados como classes c#, também conhecidos como Plain Old C#
Objeto (POCOs).
Um controlador é um objeto que manipula solicitações HTTP e cria a resposta HTTP. Este aplicativo tem um
único controlador.
Para manter o tutorial simples, o aplicativo não usa um banco de dados persistente. O aplicativo de exemplo
armazena os itens de tarefas pendentes em um banco de dados em memória.

Configurar o ambiente de desenvolvimento


Baixe e instale:
SDK do .NET Core 2.0.0 ou posterior.
Visual Studio Code
Extensão do C# do Visual Studio Code

Criar o projeto
Em um console, execute os seguintes comandos:

mkdir TodoApi
cd TodoApi
dotnet new webapi

Abra a pasta TodoApi no Visual Studio Code (VS Code) e selecione o arquivo Startup.cs.
Selecione Sim para a mensagem de Aviso “Os ativos necessários para compilar e depurar estão ausentes em
'TodoApi'. Deseja adicioná-los?”
Selecione Restaurar para a mensagem Informações “Há dependências não resolvidas”.
Pressione Depurar (F5) para compilar e executar o programa. Em um navegador, navegue para
http://localhost:5000/api/values. O seguinte é exibido:
["value1","value2"]

Consulte Ajuda do Visual Studio Code para obter dicas sobre como usar o VS Code.

Adicionar suporte ao Entity Framework Core


A criação de um novo projeto no .NET Core 2.0 adiciona o provedor “Microsoft.AspNetCore.All” ao arquivo
TodoApi.csproj. Não é necessário instalar o provedor de banco de dados Entity Framework Core InMemory
separadamente. Este provedor de banco de dados permite que o Entity Framework Core seja usado com um banco
de dados em memória.

<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" />
</ItemGroup>

<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.1" />
</ItemGroup>

</Project>
Adicionar uma classe de modelo
Um modelo é um objeto que representa os dados em seu aplicativo. Nesse caso, o único modelo é um item de
tarefas pendentes.
Adicione uma pasta chamada Models. Você pode colocar as classes de modelo em qualquer lugar no projeto, mas a
pasta Models é usada por convenção.
Adicione uma classe TodoItem com o seguinte código:

namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
}

O banco de dados gera o Id quando um TodoItem é criado.

Criar o contexto de banco de dados


O contexto de banco de dados é a classe principal que coordena a funcionalidade do Entity Framework para
determinado modelo de dados. Você cria essa classe derivando-a da classe
Microsoft.EntityFrameworkCore.DbContext .

Adicione uma classe TodoContext à pasta Models:

using Microsoft.EntityFrameworkCore;

namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}

public DbSet<TodoItem> TodoItems { get; set; }

}
}

Registrar o contexto de banco de dados


Nesta etapa, o contexto do banco de dados é registrado com o contêiner injeção de dependência. Os serviços
(como o contexto do banco de dados) que são registrados com o contêiner de injeção de dependência (DI) estão
disponíveis para os controladores.
Registre o contexto de banco de dados com o contêiner de serviço usando o suporte interno para injeção de
dependência. Substitua o conteúdo do arquivo Startup.cs pelo seguinte código:
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using TodoApi.Models;

namespace TodoApi
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));
services.AddMvc();
}

public void Configure(IApplicationBuilder app)


{
app.UseMvc();
}
}
}

O código anterior:
Remove o código que não é usado.
Especifica que um banco de dados em memória é injetado no contêiner de serviço.

Adicionar um controlador
Na pasta Controllers, crie uma classe chamada TodoController . Adicione o seguinte código:

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using TodoApi.Models;
using System.Linq;

namespace TodoApi.Controllers
{
[Route("api/[controller]")]
public class TodoController : Controller
{
private readonly TodoContext _context;

public TodoController(TodoContext context)


{
_context = context;

if (_context.TodoItems.Count() == 0)
{
_context.TodoItems.Add(new TodoItem { Name = "Item1" });
_context.SaveChanges();
}
}
}
}

O código anterior:
Define uma classe de controlador vazia. Nas próximas seções, os métodos serão adicionados para implementar
a API.
O construtor usa a Injeção de Dependência para injetar o contexto de banco de dados ( TodoContext ) no
controlador. O contexto de banco de dados é usado em cada um dos métodos CRUD no controlador.
O construtor adiciona um item no banco de dados em memória, caso ele não exista.

Obtendo itens de tarefas pendentes


Para obter os itens de tarefas pendentes, adicione os métodos a seguir à classe TodoController .

[HttpGet]
public IEnumerable<TodoItem> GetAll()
{
return _context.TodoItems.ToList();
}

[HttpGet("{id}", Name = "GetTodo")]


public IActionResult GetById(long id)
{
var item = _context.TodoItems.FirstOrDefault(t => t.Id == id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}

Esses métodos de implementam os dois métodos GET:


GET /api/todo
GET /api/todo/{id}

Esta é uma resposta HTTP de exemplo para o método GetAll :

[
{
"id": 1,
"name": "Item1",
"isComplete": false
}
]

Mais adiante no tutorial, mostrarei como a resposta HTTP pode ser visualizada com o Postman ou o curl.
Roteamento e caminhos de URL
O atributo [HttpGet] especifica um método HTTP GET. O caminho da URL de cada método é construído da
seguinte maneira:
Use a cadeia de caracteres de modelo no atributo Route do controlador:

namespace TodoApi.Controllers
{
[Route("api/[controller]")]
public class TodoController : Controller
{
private readonly TodoContext _context;

Substitua [controller] com o nome do controlador, que é o nome de classe do controlador menos o sufixo
"Controlador". Para esta amostra, o nome de classe do controlador é TodoController e o nome da raiz é “todo”.
O roteamento do ASP.NET Core não diferencia maiúsculas de minúsculas.
Se o atributo [HttpGet] tiver um modelo de rota (como [HttpGet("/products")] ), acrescente isso ao caminho.
Esta amostra não usa um modelo. Consulte Roteamento de atributo com atributos Http[verb] para obter mais
informações.
No método GetById :

[HttpGet("{id}", Name = "GetTodo")]


public IActionResult GetById(long id)
{
var item = _context.TodoItems.FirstOrDefault(t => t.Id == id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}

"{id}" é uma variável de espaço reservado para a ID do item todo . Quando GetById é invocado, ele atribui o
valor “ id}” na URL ao parâmetro id do método.
{
Name = "GetTodo" cria uma rota nomeada. Rotas nomeadas:
permitem que o aplicativo crie um link HTTP usando o nome da rota.
Serão explicadas posteriormente no tutorial.
Valores de retorno
O método GetAll retorna um IEnumerable . O MVC serializa automaticamente o objeto em JSON e grava o JSON
no corpo da mensagem de resposta. O código de resposta para esse método é 200, supondo que não haja
nenhuma exceção sem tratamento. (Exceções sem tratamento são convertidas em erros 5xx.)
Por outro lado, o método GetById retorna o tipo IActionResult mais geral, que representa uma ampla variedade
de tipos de retorno. GetById tem dois tipos retornados diferentes:
Se nenhum item corresponder à ID solicitada, o método retornará um erro 404. Retornar NotFound retorna
uma resposta HTTP 404.
Caso contrário, o método retornará 200 com um corpo de resposta JSON. Retornar ObjectResult retorna
uma resposta HTTP 200.
Iniciar o aplicativo
No VS Code, pressione F5 para iniciar o aplicativo. Navegue para http://localhost:5000/api/todo (o controlador
Todo que acabamos de criar ).

Implementar as outras operações CRUD


Nas próximas seções, os métodos Create , Update e Delete serão adicionados ao controlador.
Create
Adicione o seguinte método Create .
[HttpPost]
public IActionResult Create([FromBody] TodoItem item)
{
if (item == null)
{
return BadRequest();
}

_context.TodoItems.Add(item);
_context.SaveChanges();

return CreatedAtRoute("GetTodo", new { id = item.Id }, item);


}

O código anterior é um método HTTP POST, indicado pelo atributo [HttpPost] . O atributo [FromBody] informa ao
MVC para obter o valor do item de tarefas pendentes do corpo da solicitação HTTP.
O método CreatedAtRoute :
Retorna uma resposta 201. HTTP 201 é a resposta padrão para um método HTTP POST que cria um novo
recurso no servidor.
Adiciona um cabeçalho Local à resposta. O cabeçalho Location especifica o URI do item de tarefas pendentes
recém-criado. Consulte 10.2.2 201 criado.
Usa a rota chamada "GetTodo" para criar a URL. A rota chamada "GetTodo" é definida em GetById :

[HttpGet("{id}", Name = "GetTodo")]


public IActionResult GetById(long id)
{
var item = _context.TodoItems.FirstOrDefault(t => t.Id == id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}

Usar o Postman para enviar uma solicitação Create


Defina o método HTTP como POST
Selecione o botão de opção Corpo
Selecione o botão de opção bruto
Definir o tipo como JSON
No editor de chave-valor, insira um item de tarefas pendentes, como

{
"name":"walk dog",
"isComplete":true
}

Selecione Enviar
Selecione a guia Cabeçalhos no painel inferior e copie o cabeçalho Location:

O URI do cabeçalho Local pode ser usado para acessar o novo item.
Atualização
Adicione o seguinte método Update :
[HttpPut("{id}")]
public IActionResult Update(long id, [FromBody] TodoItem item)
{
if (item == null || item.Id != id)
{
return BadRequest();
}

var todo = _context.TodoItems.FirstOrDefault(t => t.Id == id);


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

todo.IsComplete = item.IsComplete;
todo.Name = item.Name;

_context.TodoItems.Update(todo);
_context.SaveChanges();
return new NoContentResult();
}

Update é semelhante a Create , mas usa HTTP PUT. A resposta é 204 (Sem conteúdo). De acordo com a
especificação HTTP, uma solicitação PUT exige que o cliente envie a entidade atualizada inteira, não apenas os
deltas. Para dar suporte a atualizações parciais, use HTTP PATCH.

Excluir
Adicione o seguinte método Delete :
[HttpDelete("{id}")]
public IActionResult Delete(long id)
{
var todo = _context.TodoItems.FirstOrDefault(t => t.Id == id);
if (todo == null)
{
return NotFound();
}

_context.TodoItems.Remove(todo);
_context.SaveChanges();
return new NoContentResult();
}

A resposta Delete é 204 (Sem conteúdo).


Teste Delete :

Ajuda do Visual Studio Code


Introdução
Depuração
Terminal integrado
Atalhos de teclado
Atalhos de teclado do Mac
Atalhos de teclado do Linux
Atalhos de teclado do Windows

Próximas etapas
Páginas de ajuda da API Web ASP.NET Core usando o Swagger
Roteamento para ações do controlador
Para obter informações sobre como implantar uma API, incluindo o Serviço de Aplicativo do Azure, confira
Host e implantação.
Exibir ou baixar o código de exemplo. Consulte como baixar.
Postman
Criar uma API Web com o ASP.NET Core MVC e o
Visual Studio para Mac
08/02/2018 • 16 min to read • Edit Online

Por Rick Anderson e Mike Wasson


Neste tutorial, crie uma API Web para gerenciar uma lista de itens de "tarefas pendentes". A interface do usuário
não é construída.
Há três versões deste tutorial:
macOS: API Web com o Visual Studio para Mac (este tutorial)
Windows: API Web com o Visual Studio para Windows
macOS, Linux, Windows: API Web com o Visual Studio Code

Visão geral
Este tutorial cria a seguinte API:

API DESCRIÇÃO CORPO DA SOLICITAÇÃO CORPO DA RESPOSTA

GET /api/todo Obter todos os itens de Nenhum Matriz de itens de tarefas


tarefas pendentes pendentes

GET /api/todo/{id} Obter um item por ID Nenhum Item de tarefas pendentes

POST /api/todo Adicionar um novo item Item de tarefas pendentes Item de tarefas pendentes

PUT /api/todo/{id} Atualizar um item existente Item de tarefas pendentes Nenhum

DELETE /api/todo/{id} Excluir um item Nenhum Nenhum

O diagrama a seguir mostra o design básico do aplicativo.

O cliente é tudo o que consome a API Web (aplicativo móvel, navegador, etc.). Este tutorial não cria um
cliente. Postman ou curl são usados como clientes para testar o aplicativo.
Um modelo é um objeto que representa os dados no aplicativo. Nesse caso, o único modelo é um item de
tarefas pendentes. Modelos são representados como classes c#, também conhecidos como Plain Old C#
Objeto (POCOs).
Um controlador é um objeto que manipula solicitações HTTP e cria a resposta HTTP. Este aplicativo tem um
único controlador.
Para manter o tutorial simples, o aplicativo não usa um banco de dados persistente. O aplicativo de exemplo
armazena os itens de tarefas pendentes em um banco de dados em memória.
Consulte Introdução ao ASP.NET Core MVC no Mac ou Linux para obter um exemplo que usa um banco de
dados persistente.

Pré-requisitos
Instale o seguinte:
SDK do .NET Core 2.0.0 ou posterior
Visual Studio para Mac

Criar o projeto
No Visual Studio, selecione Arquivo > Nova Solução.

Selecione Aplicativo .NET Core > API Web ASP.NET Core > Avançar.
Digite TodoApi para o Nome do Projeto e, em seguida, selecione Criar.

Iniciar o aplicativo
No Visual Studio, selecione Executar > Iniciar Com Depuração para iniciar o aplicativo. O Visual Studio inicia
um navegador e navega para http://localhost:5000 . Você obtém um erro de HTTP 404 (Não Encontrado). Altere a
URL para http://localhost:port/api/values . Os dados de ValuesController serão exibidos:
["value1","value2"]

Adicionar suporte ao Entity Framework Core


Instalar o provedor de banco de dados Entity Framework Core InMemory. Este provedor de banco de dados
permite que o Entity Framework Core seja usado com um banco de dados em memória.
No menu Projeto, selecione Adicionar pacotes do NuGet.
Como alternativa, clique com o botão direito do mouse em Dependências e, em seguida, selecione
Adicionar Pacotes.
Digite EntityFrameworkCore.InMemory na caixa de pesquisa.
Selecione Microsoft.EntityFrameworkCore.InMemory e, em seguida, selecione Adicionar Pacote.
Adicionar uma classe de modelo
Um modelo é um objeto que representa os dados em seu aplicativo. Nesse caso, o único modelo é um item de
tarefas pendentes.
Adicione uma pasta chamada Models. No Gerenciador de Soluções, clique com o botão direito do mouse no
projeto. Selecione Adicionar > Nova Pasta. Nomeie a pasta como Modelos.

Observação: Você pode colocar as classes de modelo em qualquer lugar no seu projeto, mas a pasta Modelos é
usada por convenção.
Adicione uma classe TodoItem . Clique com o botão direito do mouse na pasta Modelos e selecione Adicionar >
Novo Arquivo > Geral > Classe Vazia. Nomeie a classe TodoItem e, em seguida, selecione Novo.
Substitua o código gerado por:

namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
}

O banco de dados gera o Id quando um TodoItem é criado.


Criar o contexto de banco de dados
O contexto de banco de dados é a classe principal que coordena a funcionalidade do Entity Framework para
determinado modelo de dados. Você cria essa classe derivando-a da classe
Microsoft.EntityFrameworkCore.DbContext .

Adicione uma classe denominada TodoContext à pasta Modelos.

using Microsoft.EntityFrameworkCore;

namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}

public DbSet<TodoItem> TodoItems { get; set; }

}
}

Registrar o contexto de banco de dados


Nesta etapa, o contexto do banco de dados é registrado com o contêiner injeção de dependência. Os serviços
(como o contexto do banco de dados) que são registrados com o contêiner de injeção de dependência (DI) estão
disponíveis para os controladores.
Registre o contexto de banco de dados com o contêiner de serviço usando o suporte interno para injeção de
dependência. Substitua o conteúdo do arquivo Startup.cs pelo seguinte código:
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using TodoApi.Models;

namespace TodoApi
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));
services.AddMvc();
}

public void Configure(IApplicationBuilder app)


{
app.UseMvc();
}
}
}

O código anterior:
Remove o código que não é usado.
Especifica que um banco de dados em memória é injetado no contêiner de serviço.

Adicionar um controlador
No Gerenciador de Soluções, na pasta Controladores, adicione a classe TodoController .
Substitua o código gerado pelo código a seguir (e adicione chaves de fechamento):

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using TodoApi.Models;
using System.Linq;

namespace TodoApi.Controllers
{
[Route("api/[controller]")]
public class TodoController : Controller
{
private readonly TodoContext _context;

public TodoController(TodoContext context)


{
_context = context;

if (_context.TodoItems.Count() == 0)
{
_context.TodoItems.Add(new TodoItem { Name = "Item1" });
_context.SaveChanges();
}
}
}
}

O código anterior:
Define uma classe de controlador vazia. Nas próximas seções, os métodos serão adicionados para implementar
a API.
O construtor usa a Injeção de Dependência para injetar o contexto de banco de dados ( TodoContext ) no
controlador. O contexto de banco de dados é usado em cada um dos métodos CRUD no controlador.
O construtor adiciona um item no banco de dados em memória, caso ele não exista.

Obtendo itens de tarefas pendentes


Para obter os itens de tarefas pendentes, adicione os métodos a seguir à classe TodoController .

[HttpGet]
public IEnumerable<TodoItem> GetAll()
{
return _context.TodoItems.ToList();
}

[HttpGet("{id}", Name = "GetTodo")]


public IActionResult GetById(long id)
{
var item = _context.TodoItems.FirstOrDefault(t => t.Id == id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}

Esses métodos de implementam os dois métodos GET:


GET /api/todo
GET /api/todo/{id}

Esta é uma resposta HTTP de exemplo para o método GetAll :

[
{
"id": 1,
"name": "Item1",
"isComplete": false
}
]

Mais adiante no tutorial, mostrarei como a resposta HTTP pode ser visualizada com o Postman ou o curl.
Roteamento e caminhos de URL
O atributo [HttpGet] especifica um método HTTP GET. O caminho da URL de cada método é construído da
seguinte maneira:
Use a cadeia de caracteres de modelo no atributo Route do controlador:

namespace TodoApi.Controllers
{
[Route("api/[controller]")]
public class TodoController : Controller
{
private readonly TodoContext _context;

Substitua [controller] com o nome do controlador, que é o nome de classe do controlador menos o sufixo
"Controlador". Para esta amostra, o nome de classe do controlador é TodoController e o nome da raiz é “todo”.
O roteamento do ASP.NET Core não diferencia maiúsculas de minúsculas.
Se o atributo [HttpGet] tiver um modelo de rota (como [HttpGet("/products")] ), acrescente isso ao caminho.
Esta amostra não usa um modelo. Consulte Roteamento de atributo com atributos Http[verb] para obter mais
informações.
No método GetById :

[HttpGet("{id}", Name = "GetTodo")]


public IActionResult GetById(long id)
{
var item = _context.TodoItems.FirstOrDefault(t => t.Id == id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}

"{id}" é uma variável de espaço reservado para a ID do item todo . Quando GetById é invocado, ele atribui o
valor “{id}” na URL ao parâmetro id do método.
Name = "GetTodo" cria uma rota nomeada. Rotas nomeadas:
permitem que o aplicativo crie um link HTTP usando o nome da rota.
Serão explicadas posteriormente no tutorial.
Valores de retorno
O método GetAll retorna um IEnumerable . O MVC serializa automaticamente o objeto em JSON e grava o JSON
no corpo da mensagem de resposta. O código de resposta para esse método é 200, supondo que não haja
nenhuma exceção sem tratamento. (Exceções sem tratamento são convertidas em erros 5xx.)
Por outro lado, o método GetById retorna o tipo IActionResult mais geral, que representa uma ampla variedade
de tipos de retorno. GetById tem dois tipos retornados diferentes:
Se nenhum item corresponder à ID solicitada, o método retornará um erro 404. Retornar NotFound retorna
uma resposta HTTP 404.
Caso contrário, o método retornará 200 com um corpo de resposta JSON. Retornar ObjectResult retorna
uma resposta HTTP 200.
Iniciar o aplicativo
No Visual Studio, selecione Executar > Iniciar Com Depuração para iniciar o aplicativo. O Visual Studio inicia
um navegador e navega para http://localhost:port , em que porta é um número da porta escolhido
aleatoriamente. Você obtém um erro de HTTP 404 (Não Encontrado). Altere a URL para
http://localhost:port/api/values . Os dados de ValuesController serão exibidos:

["value1","value2"]

Navegue até o controlador Todo no http://localhost:port/api/todo :

[{"key":1,"name":"Item1","isComplete":false}]

Implementar as outras operações de CRUD


Adicionaremos os métodos Create , Update e Delete ao controlador. Essas são variações de um mesmo tema e,
portanto, mostrarei apenas o código e realçarei as principais diferenças. Compile o projeto depois de adicionar ou
alterar o código.
Create

[HttpPost]
public IActionResult Create([FromBody] TodoItem item)
{
if (item == null)
{
return BadRequest();
}

_context.TodoItems.Add(item);
_context.SaveChanges();

return CreatedAtRoute("GetTodo", new { id = item.Id }, item);


}

Este é um método HTTP POST, indicado pelo atributo [HttpPost] . O atributo [FromBody] informa ao MVC para
obter o valor do item de tarefas pendentes do corpo da solicitação HTTP.
O método CreatedAtRoute retorna uma resposta 201, que é a resposta padrão para um método HTTP POST que
cria um novo recurso no servidor. CreatedAtRoute também adiciona um cabeçalho Local à resposta. O cabeçalho
Location especifica o URI do item de tarefas pendentes recém-criado. Consulte 10.2.2 201 criado.
Use o Postman para enviar uma solicitação de criação
Inicie o aplicativo (Executar > Iniciar Com Depuração).
Inicie o Postman.
Defina o método HTTP como POST
Selecione o botão de opção Corpo
Selecione o botão de opção bruto
Definir o tipo como JSON
No editor de chave-valor, insira um item de tarefas pendentes, como

{
"name":"walk dog",
"isComplete":true
}

Selecione Enviar
Selecione a guia Cabeçalhos no painel inferior e copie o cabeçalho Location:

Use o URI do cabeçalho Location para acessar o recurso que você acabou de criar. Lembre-se de que o método
GetById criou a rota nomeada "GetTodo" :

[HttpGet("{id}", Name = "GetTodo")]


public IActionResult GetById(string id)

Atualização

[HttpPut("{id}")]
public IActionResult Update(long id, [FromBody] TodoItem item)
{
if (item == null || item.Id != id)
{
return BadRequest();
}

var todo = _context.TodoItems.FirstOrDefault(t => t.Id == id);


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

todo.IsComplete = item.IsComplete;
todo.Name = item.Name;

_context.TodoItems.Update(todo);
_context.SaveChanges();
return new NoContentResult();
}

Update é semelhante a Create , mas usa HTTP PUT. A resposta é 204 (Sem conteúdo). De acordo com a
especificação HTTP, uma solicitação PUT exige que o cliente envie a entidade atualizada inteira, não apenas os
deltas. Para dar suporte a atualizações parciais, use HTTP PATCH.

{
"key": 1,
"name": "walk dog",
"isComplete": true
}
Excluir

[HttpDelete("{id}")]
public IActionResult Delete(long id)
{
var todo = _context.TodoItems.FirstOrDefault(t => t.Id == id);
if (todo == null)
{
return NotFound();
}

_context.TodoItems.Remove(todo);
_context.SaveChanges();
return new NoContentResult();
}

A resposta é 204 (Sem conteúdo).


Próximas etapas
Roteamento para ações do controlador
Para obter informações sobre como implantar a API, consulte Host e implantação.
Exibir ou baixar código de exemplo (como baixar)
Postman
Fiddler
28/02/2018 • 16 min to read • Edit Online

#Criar uma API Web com o ASP.NET Core e o Visual Studio para Windows
Por Rick Anderson e Mike Wasson
Este tutorial compilará uma API Web para gerenciar uma lista de itens de “tarefas pendentes”. Uma interface
do usuário (UI) não será criada.
Há três versões deste tutorial:
Windows: API Web com o Visual Studio para Windows (este tutorial)
macOS: API Web com o Visual Studio para Mac
macOS, Linux, Windows: API Web com o Visual Studio Code

Visão geral
Este tutorial cria a seguinte API:

API DESCRIÇÃO CORPO DA SOLICITAÇÃO CORPO DA RESPOSTA

GET /api/todo Obter todos os itens de Nenhum Matriz de itens de tarefas


tarefas pendentes pendentes

GET /api/todo/{id} Obter um item por ID Nenhum Item de tarefas pendentes

POST /api/todo Adicionar um novo item Item de tarefas pendentes Item de tarefas pendentes

PUT /api/todo/{id} Atualizar um item Item de tarefas pendentes Nenhum


existente

DELETE /api/todo/{id} Excluir um item Nenhum Nenhum

O diagrama a seguir mostra o design básico do aplicativo.

O cliente é tudo o que consome a API Web (aplicativo móvel, navegador, etc.). Este tutorial não cria um
cliente. Postman ou curl são usados como clientes para testar o aplicativo.
Um modelo é um objeto que representa os dados no aplicativo. Nesse caso, o único modelo é um item
de tarefas pendentes. Modelos são representados como classes c#, também conhecidos como Plain Old
C# Objeto (POCOs).
Um controlador é um objeto que manipula solicitações HTTP e cria a resposta HTTP. Este aplicativo tem
um único controlador.
Para manter o tutorial simples, o aplicativo não usa um banco de dados persistente. O aplicativo de
exemplo armazena os itens de tarefas pendentes em um banco de dados em memória.

Pré-requisitos
Install the following:
.NET Core 2.0.0 SDK or later.
Visual Studio 2017 version 15.3 or later with the ASP.NET and web development workload.
Consulte este PDF para o ASP.NET Core versão 1.1.

Criar o projeto
No Visual Studio, selecione o menu Arquivo, > Novo > Projeto.
Selecione o modelo de projeto Aplicativo Web ASP.NET Core (.NET Core). Nomeie o projeto TodoApi e
selecione OK.

Na caixa de diálogo Novo aplicativo Web ASP.NET Core – TodoApi, selecione o modelo API Web.
Selecione OK. Não selecione Habilitar Suporte ao Docker.

Iniciar o aplicativo
No Visual Studio, pressione CTRL + F5 para iniciar o aplicativo. O Visual Studio inicia um navegador e navega
para http://localhost:port/api/values``http://localhost:port/api/values , em que port é um número da porta
escolhido aleatoriamente. O Chrome, Microsoft Edge e Firefox exibem a seguinte saída:

["value1","value2"]

Adicionar uma classe de modelo


Um modelo é um objeto que representa os dados no aplicativo. Nesse caso, o único modelo é um item de
tarefas pendentes.
Adicione uma pasta denominada "Modelos". No Gerenciador de Soluções, clique com o botão direito do
mouse no projeto. Selecione Adicionar > Nova Pasta. Nomeie a pasta Models.
Observação: as classes de modelo entram em qualquer lugar no projeto. A pasta Modelos é usada por
convenção para as classes de modelo.
Adicione uma classe TodoItem . Clique com o botão direito do mouse na pasta Modelos e selecione Adicionar
> Classe. Nomeie a classe TodoItem e, em seguida, selecione Adicionar.
Atualize a classe TodoItem com o código a seguir:
namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
}

O banco de dados gera o Id quando um TodoItem é criado.


Criar o contexto de banco de dados
O contexto de banco de dados é a classe principal que coordena a funcionalidade do Entity Framework para
um determinado modelo de dados. Essa classe é criada derivando-a da classe
Microsoft.EntityFrameworkCore.DbContext .

Adicione uma classe TodoContext . Clique com o botão direito do mouse na pasta Modelos e selecione
Adicionar > Classe. Nomeie a classe TodoContext e, em seguida, selecione Adicionar.
Substitua a classe pelo código a seguir:

using Microsoft.EntityFrameworkCore;

namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}

public DbSet<TodoItem> TodoItems { get; set; }

}
}

Registrar o contexto de banco de dados


Nesta etapa, o contexto do banco de dados é registrado com o contêiner injeção de dependência. Os serviços
(como o contexto do banco de dados) que são registrados com o contêiner de injeção de dependência (DI)
estão disponíveis para os controladores.
Registre o contexto de banco de dados com o contêiner de serviço usando o suporte interno para injeção de
dependência. Substitua o conteúdo do arquivo Startup.cs pelo seguinte código:
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using TodoApi.Models;

namespace TodoApi
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));
services.AddMvc();
}

public void Configure(IApplicationBuilder app)


{
app.UseMvc();
}
}
}

O código anterior:
Remove o código que não é usado.
Especifica que um banco de dados em memória é injetado no contêiner de serviço.
Adicionar um controlador
No Gerenciador de Soluções, clique com o botão direito do mouse na pasta Controladores. Selecione
Adicionar > Novo Item. Na caixa de diálogo Adicionar Novo Item, selecione o modelo Classe do
Controlador de API Web. Nomeie a classe TodoController .

Substitua a classe pelo código a seguir:


using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using TodoApi.Models;
using System.Linq;

namespace TodoApi.Controllers
{
[Route("api/[controller]")]
public class TodoController : Controller
{
private readonly TodoContext _context;

public TodoController(TodoContext context)


{
_context = context;

if (_context.TodoItems.Count() == 0)
{
_context.TodoItems.Add(new TodoItem { Name = "Item1" });
_context.SaveChanges();
}
}
}
}

O código anterior:
Define uma classe de controlador vazia. Nas próximas seções, os métodos serão adicionados para
implementar a API.
O construtor usa a Injeção de Dependência para injetar o contexto de banco de dados ( TodoContext ) no
controlador. O contexto de banco de dados é usado em cada um dos métodos CRUD no controlador.
O construtor adiciona um item no banco de dados em memória, caso ele não exista.

Obtendo itens de tarefas pendentes


Para obter os itens de tarefas pendentes, adicione os métodos a seguir à classe TodoController .

[HttpGet]
public IEnumerable<TodoItem> GetAll()
{
return _context.TodoItems.ToList();
}

[HttpGet("{id}", Name = "GetTodo")]


public IActionResult GetById(long id)
{
var item = _context.TodoItems.FirstOrDefault(t => t.Id == id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}

Esses métodos de implementam os dois métodos GET:


GET /api/todo
GET /api/todo/{id}

Esta é uma resposta HTTP de exemplo para o método GetAll :


[
{
"id": 1,
"name": "Item1",
"isComplete": false
}
]

Mais adiante no tutorial, mostrarei como a resposta HTTP pode ser visualizada com o Postman ou o curl.
Roteamento e caminhos de URL
O atributo [HttpGet] especifica um método HTTP GET. O caminho da URL de cada método é construído da
seguinte maneira:
Use a cadeia de caracteres de modelo no atributo Route do controlador:

namespace TodoApi.Controllers
{
[Route("api/[controller]")]
public class TodoController : Controller
{
private readonly TodoContext _context;

Substitua [controller] com o nome do controlador, que é o nome de classe do controlador menos o sufixo
"Controlador". Para esta amostra, o nome de classe do controlador é TodoController e o nome da raiz é
“todo”. O roteamento do ASP.NET Core não diferencia maiúsculas de minúsculas.
Se o atributo [HttpGet] tiver um modelo de rota (como [HttpGet("/products")] ), acrescente isso ao
caminho. Esta amostra não usa um modelo. Consulte Roteamento de atributo com atributos Http[verb] para
obter mais informações.
No método GetById :

[HttpGet("{id}", Name = "GetTodo")]


public IActionResult GetById(long id)
{
var item = _context.TodoItems.FirstOrDefault(t => t.Id == id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}

"{id}" é uma variável de espaço reservado para a ID do item todo . Quando GetById é invocado, ele atribui o
valor “{id}” na URL ao parâmetro id do método.
Name = "GetTodo" cria uma rota nomeada. Rotas nomeadas:
permitem que o aplicativo crie um link HTTP usando o nome da rota.
Serão explicadas posteriormente no tutorial.
Valores de retorno
O método GetAll retorna um IEnumerable . O MVC serializa automaticamente o objeto em JSON e grava o
JSON no corpo da mensagem de resposta. O código de resposta para esse método é 200, supondo que não
haja nenhuma exceção sem tratamento. (Exceções sem tratamento são convertidas em erros 5xx.)
Por outro lado, o método GetById retorna o tipo IActionResult mais geral, que representa uma ampla
variedade de tipos de retorno. GetById tem dois tipos retornados diferentes:
Se nenhum item corresponder à ID solicitada, o método retornará um erro 404. Retornar NotFound
retorna uma resposta HTTP 404.
Caso contrário, o método retornará 200 com um corpo de resposta JSON. Retornar ObjectResult
retorna uma resposta HTTP 200.
Iniciar o aplicativo
No Visual Studio, pressione CTRL + F5 para iniciar o aplicativo. O Visual Studio inicia um navegador e navega
para http://localhost:port/api/values``http://localhost:port/api/values , em que port é um número da porta
escolhido aleatoriamente. Navegue até o controlador Todo no http://localhost:port/api/todo .

Implementar as outras operações CRUD


Nas próximas seções, os métodos Create , Update e Delete serão adicionados ao controlador.
Create
Adicione o seguinte método Create .

[HttpPost]
public IActionResult Create([FromBody] TodoItem item)
{
if (item == null)
{
return BadRequest();
}

_context.TodoItems.Add(item);
_context.SaveChanges();

return CreatedAtRoute("GetTodo", new { id = item.Id }, item);


}

O código anterior é um método HTTP POST, indicado pelo atributo [HttpPost] . O atributo [FromBody]
informa ao MVC para obter o valor do item de tarefas pendentes do corpo da solicitação HTTP.
O método CreatedAtRoute :
Retorna uma resposta 201. HTTP 201 é a resposta padrão para um método HTTP POST que cria um novo
recurso no servidor.
Adiciona um cabeçalho Local à resposta. O cabeçalho Location especifica o URI do item de tarefas
pendentes recém-criado. Consulte 10.2.2 201 criado.
Usa a rota chamada "GetTodo" para criar a URL. A rota chamada "GetTodo" é definida em GetById :

[HttpGet("{id}", Name = "GetTodo")]


public IActionResult GetById(long id)
{
var item = _context.TodoItems.FirstOrDefault(t => t.Id == id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}

Usar o Postman para enviar uma solicitação Create


Defina o método HTTP como POST
Selecione o botão de opção Corpo
Selecione o botão de opção bruto
Definir o tipo como JSON
No editor de chave-valor, insira um item de tarefas pendentes, como

{
"name":"walk dog",
"isComplete":true
}

Selecione Enviar
Selecione a guia Cabeçalhos no painel inferior e copie o cabeçalho Location:

O URI do cabeçalho Local pode ser usado para acessar o novo item.
Atualização
Adicione o seguinte método Update :
[HttpPut("{id}")]
public IActionResult Update(long id, [FromBody] TodoItem item)
{
if (item == null || item.Id != id)
{
return BadRequest();
}

var todo = _context.TodoItems.FirstOrDefault(t => t.Id == id);


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

todo.IsComplete = item.IsComplete;
todo.Name = item.Name;

_context.TodoItems.Update(todo);
_context.SaveChanges();
return new NoContentResult();
}

Update é semelhante a Create , mas usa HTTP PUT. A resposta é 204 (Sem conteúdo). De acordo com a
especificação HTTP, uma solicitação PUT exige que o cliente envie a entidade atualizada inteira, não apenas os
deltas. Para dar suporte a atualizações parciais, use HTTP PATCH.

Excluir
Adicione o seguinte método Delete :
[HttpDelete("{id}")]
public IActionResult Delete(long id)
{
var todo = _context.TodoItems.FirstOrDefault(t => t.Id == id);
if (todo == null)
{
return NotFound();
}

_context.TodoItems.Remove(todo);
_context.SaveChanges();
return new NoContentResult();
}

A resposta Delete é 204 (Sem conteúdo).


Teste Delete :

Próximas etapas
Páginas de ajuda da API Web ASP.NET Core usando o Swagger
Roteamento para ações do controlador
Para obter informações sobre como implantar uma API, incluindo o Serviço de Aplicativo do Azure, confira
Host e implantação.
Exibir ou baixar o código de exemplo. Consulte como baixar.
Postman
Criando serviços de back-end para aplicativos móveis
nativos
07/03/2018 • 14 min to read • Edit Online

Por Steve Smith


Os aplicativos móveis podem se comunicar com facilidade com os serviços de back-end do ASP.NET Core.
Exibir ou baixar o código de exemplo dos serviços de back-end

Exemplo do aplicativo móvel nativo


Este tutorial demonstra como criar serviços de back-end usando o ASP.NET Core MVC para dar suporte a
aplicativos móveis nativos. Ele usa o aplicativo Xamarin Forms ToDoRest como seu cliente nativo, que inclui
clientes nativos separados para dispositivos Android, iOS, Universal do Windows e Windows Phone. Siga o tutorial
com links para criar o aplicativo nativo (e instale as ferramentas do Xamarin gratuitas necessárias), além de baixar a
solução de exemplo do Xamarin. A amostra do Xamarin inclui um projeto de serviços da API Web ASP.NET 2, que
substitui o aplicativo ASP.NET Core deste artigo (sem nenhuma alteração exigida pelo cliente).

Recursos
O aplicativo ToDoRest é compatível com listagem, adição, exclusão e atualização de itens de tarefas pendentes.
Cada item tem uma ID, um Nome, Observações e uma propriedade que indica se ele já foi Concluído.
A exibição principal dos itens, conforme mostrado acima, lista o nome de cada item e indica se ele foi concluído
com uma marca de seleção.
Tocar no ícone + abre uma caixa de diálogo de adição de itens:

Tocar em um item na tela da lista principal abre uma caixa de diálogo de edição, na qual o Nome do item,
Observações e configurações de Concluído podem ser modificados, ou o item pode ser excluído:
Esta amostra é configurada por padrão para usar os serviços de back-end hospedados em developer.xamarin.com,
que permitem operações somente leitura. Para testá-la por conta própria no aplicativo ASP.NET Core criado na
próxima seção em execução no computador, você precisará atualizar a constante RestUrl do aplicativo. Navegue
para o projeto ToDoREST e abra o arquivo Constants.cs. Substitua o RestUrl por uma URL que inclui o endereço IP
do computador (não localhost ou 127.0.0.1, pois esse endereço é usado no emulador do dispositivo, não no
computador). Inclua o número da porta também (5000). Para testar se os serviços funcionam com um dispositivo,
verifique se você não tem um firewall ativo bloqueando o acesso a essa porta.

// URL of REST service (Xamarin ReadOnly Service)


//public static string RestUrl = "http://developer.xamarin.com:8081/api/todoitems{0}";

// use your machine's IP address


public static string RestUrl = "http://192.168.1.207:5000/api/todoitems/{0}";

Criando o projeto ASP.NET Core


Crie um novo aplicativo Web do ASP.NET Core no Visual Studio. Escolha o modelo de Web API sem autenticação.
Nomeie o projeto como ToDoApi.
O aplicativo deve responder a todas as solicitações feitas através da porta 5000. Atualize o Program.cs para incluir
.UseUrls("http://*:5000") para ficar assim:

var host = new WebHostBuilder()


.UseKestrel()
.UseUrls("http://*:5000")
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();

OBSERVAÇÃO
Execute o aplicativo diretamente, em vez de por trás do IIS Express, que ignora solicitações não local por padrão. Execute
dotnet run em um prompt de comando ou escolha o perfil de nome do aplicativo no menu suspenso de destino de
depuração na barra de ferramentas do Visual Studio.

Adicione uma classe de modelo para representar itens pendentes. Marque os campos obrigatórios usando o
atributo [Required] :
using System.ComponentModel.DataAnnotations;

namespace ToDoApi.Models
{
public class ToDoItem
{
[Required]
public string ID { get; set; }

[Required]
public string Name { get; set; }

[Required]
public string Notes { get; set; }

public bool Done { get; set; }


}
}

Os métodos da API exigem alguma maneira de trabalhar com dados. Use a mesma interface IToDoRepository nos
usos de exemplo originais do Xamarin:

using System.Collections.Generic;
using ToDoApi.Models;

namespace ToDoApi.Interfaces
{
public interface IToDoRepository
{
bool DoesItemExist(string id);
IEnumerable<ToDoItem> All { get; }
ToDoItem Find(string id);
void Insert(ToDoItem item);
void Update(ToDoItem item);
void Delete(string id);
}
}

Para esta amostra, a implementação apenas usa uma coleção particular de itens:

using System.Collections.Generic;
using System.Linq;
using ToDoApi.Interfaces;
using ToDoApi.Models;

namespace ToDoApi.Services
{
public class ToDoRepository : IToDoRepository
{
private List<ToDoItem> _toDoList;

public ToDoRepository()
{
InitializeData();
}

public IEnumerable<ToDoItem> All


{
get { return _toDoList; }
}

public bool DoesItemExist(string id)


{
return _toDoList.Any(item => item.ID == id);
return _toDoList.Any(item => item.ID == id);
}

public ToDoItem Find(string id)


{
return _toDoList.FirstOrDefault(item => item.ID == id);
}

public void Insert(ToDoItem item)


{
_toDoList.Add(item);
}

public void Update(ToDoItem item)


{
var todoItem = this.Find(item.ID);
var index = _toDoList.IndexOf(todoItem);
_toDoList.RemoveAt(index);
_toDoList.Insert(index, item);
}

public void Delete(string id)


{
_toDoList.Remove(this.Find(id));
}

private void InitializeData()


{
_toDoList = new List<ToDoItem>();

var todoItem1 = new ToDoItem


{
ID = "6bb8a868-dba1-4f1a-93b7-24ebce87e243",
Name = "Learn app development",
Notes = "Attend Xamarin University",
Done = true
};

var todoItem2 = new ToDoItem


{
ID = "b94afb54-a1cb-4313-8af3-b7511551b33b",
Name = "Develop apps",
Notes = "Use Xamarin Studio/Visual Studio",
Done = false
};

var todoItem3 = new ToDoItem


{
ID = "ecfa6f80-3671-4911-aabe-63cc442c1ecf",
Name = "Publish apps",
Notes = "All app stores",
Done = false,
};

_toDoList.Add(todoItem1);
_toDoList.Add(todoItem2);
_toDoList.Add(todoItem3);
}
}
}

Configure a implementação em Startup.cs:


public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();

services.AddSingleton<IToDoRepository,ToDoRepository>();
}

Neste ponto, você está pronto para criar o ToDoItemsController.

DICA
Saiba mais sobre como criar APIs Web em Criando sua primeira API Web com ASP.NET Core MVC e Visual Studio.

Criando o controlador
Adicione um novo controlador ao projeto, ToDoItemsController. Ele deve herdar de
Microsoft.AspNetCore.Mvc.Controller. Adicione um atributo Route para indicar que o controlador manipulará as
solicitações feitas para caminhos que começam com api/todoitems . O token [controller] na rota é substituído
pelo nome do controlador (com a omissão do sufixo Controller ) e é especialmente útil para rotas globais. Saiba
mais sobre o roteamento.
O controlador requer um IToDoRepository para a função; solicite uma instância desse tipo usando o construtor do
controlador. No tempo de execução, esta instância será fornecida com suporte do framework parainjeção de
dependência.

using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using ToDoApi.Interfaces;
using ToDoApi.Models;

namespace ToDoApi.Controllers
{
[Route("api/[controller]")]
public class ToDoItemsController : Controller
{
private readonly IToDoRepository _toDoRepository;

public ToDoItemsController(IToDoRepository toDoRepository)


{
_toDoRepository = toDoRepository;
}

Essa API é compatível com quatro verbos HTTP diferentes para executar operações CRUD (Criar, Ler, Atualizar,
Excluir) na fonte de dados. A mais simples delas é a operação Read, que corresponde a uma solicitação HTTP GET.
Lendo itens
A solicitação de uma lista de itens é feita com uma solicitação GET ao método List . O atributo [HttpGet] no
método List indica que esta ação só deve lidar com as solicitações GET. A rota para esta ação é a rota especificada
no controlador. Você não precisa necessariamente usar o nome da ação como parte da rota. Você precisa garantir
que cada ação tem uma rota exclusiva e não ambígua. Os atributos de roteamento podem ser aplicados nos níveis
de método e controlador para criar rotas específicas.
[HttpGet]
public IActionResult List()
{
return Ok(_toDoRepository.All);
}

O método List retorna um código de resposta OK 200 e todos os itens de tarefas, serializados como JSON.
Você pode testar o novo método de API usando uma variedade de ferramentas, como Postman. Veja abaixo:

Criando itens
Por convenção, a criação de novos itens de dados é mapeada para o verbo HTTP POST. O método Create tem um
atributo [HttpPost] aplicado a ele e aceita uma instância ToDoItem . Como o argumento item será enviado no
corpo de POST, este parâmetro será decorado com o atributo [FromBody] .
Dentro do método, o item é verificado quanto à validade e existência anterior no armazenamento de dados e, se
nenhum problema ocorrer, ele será adicionado usando o repositório. A verificação de ModelState.IsValid executa a
validação do modelo e deve ser feita em todos os métodos de API que aceitam a entrada do usuário.
[HttpPost]
public IActionResult Create([FromBody] ToDoItem item)
{
try
{
if (item == null || !ModelState.IsValid)
{
return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
}
bool itemExists = _toDoRepository.DoesItemExist(item.ID);
if (itemExists)
{
return StatusCode(StatusCodes.Status409Conflict, ErrorCode.TodoItemIDInUse.ToString());
}
_toDoRepository.Insert(item);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotCreateItem.ToString());
}
return Ok(item);
}

A amostra usa uma enumeração que contém códigos de erro que são passados para o cliente móvel:

public enum ErrorCode


{
TodoItemNameAndNotesRequired,
TodoItemIDInUse,
RecordNotFound,
CouldNotCreateItem,
CouldNotUpdateItem,
CouldNotDeleteItem
}

Teste a adição de novos itens usando Postman escolhendo o verbo POST fornecendo o novo objeto no formato
JSON no corpo da solicitação. Você também deve adicionar um cabeçalho de solicitação que especifica um
Content-Type de application/json .
O método retorna o item recém-criado na resposta.
Atualizando itens
A modificação de registros é feita com as solicitações HTTP PUT. Além desta mudança, o método Edit é quase
idêntico ao Create . Observe que, se o registro não for encontrado, a ação Edit retornará uma resposta NotFound
(404).
[HttpPut]
public IActionResult Edit([FromBody] ToDoItem item)
{
try
{
if (item == null || !ModelState.IsValid)
{
return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
}
var existingItem = _toDoRepository.Find(item.ID);
if (existingItem == null)
{
return NotFound(ErrorCode.RecordNotFound.ToString());
}
_toDoRepository.Update(item);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotUpdateItem.ToString());
}
return NoContent();
}

Para testar com Postman, altere o verbo para PUT. Especifique os dados do objeto atualizado no corpo da
solicitação.

Este método retornará uma resposta NoContent (204) quando obtiver êxito, para manter a consistência com a API
já existente.
Excluindo itens
A exclusão de registros é feita por meio da criação de solicitações de exclusão para o serviço e por meio do envio
do ID do item a ser excluído. Assim como as atualizações, as solicitações de itens que não existem receberão
respostas NotFound . Caso contrário, uma solicitação bem-sucedida receberá uma resposta NoContent (204).

[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
try
{
var item = _toDoRepository.Find(id);
if (item == null)
{
return NotFound(ErrorCode.RecordNotFound.ToString());
}
_toDoRepository.Delete(id);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotDeleteItem.ToString());
}
return NoContent();
}

Observe que, ao testar a funcionalidade de exclusão, nada é necessário no Corpo da solicitação.

Convenções de Web API comuns


À medida que você desenvolve serviços de back-end para seu aplicativo, desejará criar um conjunto consistente de
convenções ou políticas para lidar com preocupações paralelas. Por exemplo, no serviço mostrado acima, as
solicitações de registros específicos que não foram encontrados receberam uma resposta NotFound , em vez de
uma resposta BadRequest . Da mesma forma, os comandos feitos para esse serviço que passaram tipos associados
a um modelo sempre verificaram ModelState.IsValid e retornaram um BadRequest para tipos de modelo
inválidos.
Depois de identificar uma diretiva comum para suas APIs, você geralmente pode encapsulá-la em um filtro. Saiba
mais sobre como encapsular políticas comuns da API em aplicativos ASP.NET Core MVC.
Páginas de ajuda da API Web ASP.NET Core usando
o Swagger
31/01/2018 • 16 min to read • Edit Online

Por Shayne Boyer e Scott Addie


Compreender os vários métodos de uma API pode ser um desafio para um desenvolvedor durante a criação de
um aplicativo de consumo.
A geração de boa documentação e páginas de ajuda para a API Web, usando Swagger com a implementação do
.NET Core Swashbuckle.AspNetCore, é tão fácil quanto adicionar alguns pacotes NuGet e modificar o Startup.cs.
O Swashbuckle.AspNetCore é um projeto de software livre para geração de documentos do Swagger para
APIs Web ASP.NET Core.
O Swagger é uma representação legível por computador de uma API RESTful que habilita o suporte para
documentação interativa, geração de SDK do cliente e detectabilidade.
Este tutorial se baseia no exemplo Criando sua primeira API Web com ASP.NET Core MVC e Visual Studio. Se
você quiser acompanhar, baixe a amostra em
https://github.com/aspnet/Docs/tree/master/aspnetcore/tutorials/first-web-api/sample.

Guia de Introdução
Há três componentes principais bo Swashbuckle:
Swashbuckle.AspNetCore.Swagger : um modelo de objeto de Swagger e middleware para expor objetos
SwaggerDocument como pontos de extremidade JSON.
Swashbuckle.AspNetCore.SwaggerGen : um gerador de Swagger cria objetos SwaggerDocument diretamente
dos modelos, controladores e rotas. Normalmente, ele é combinado com o middleware de ponto de
extremidade do Swagger para expor automaticamente o JSON do Swagger.
Swashbuckle.AspNetCore.SwaggerUI : uma versão inserida da ferramenta de interface do usuário Swagger,
que interpreta JSON do Swagger a fim de criar uma experiência rica e personalizável para descrever a
funcionalidade da API Web. Ela inclui o agente de teste interno para os métodos públicos.

Pacotes NuGet
O Swashbuckle pode ser adicionado com as seguintes abordagens:
Visual Studio
Visual Studio para Mac
Visual Studio Code
CLI do .NET Core
Da janela Console do Gerenciador de Pacotes:

Install-Package Swashbuckle.AspNetCore

Da caixa de diálogo Gerenciar Pacotes NuGet:


Clique com o botão direito do mouse no projeto em Gerenciador de Soluções > Gerenciar Pacotes
NuGet
Defina a Origem do pacote para "nuget.org"
Insira "Swashbuckle.AspNetCore" na caixa de pesquisa
Selecione o pacote "Swashbuckle.AspNetCore" na guia Procurar e clique em Instalar

Adicionar e configurar o Swagger para o middleware


Adicione o gerador de Swagger à coleção de serviços no método ConfigureServices de Startup.cs:

public void ConfigureServices(IServiceCollection services)


{
services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));
services.AddMvc();

// Register the Swagger generator, defining one or more Swagger documents


services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
});
}

Adicione a seguinte instrução using da classe Info :

using Swashbuckle.AspNetCore.Swagger;

No método Configure de Startup.cs, habilite o middleware para servir o documento JSON gerado e o
SwaggerUI:

public void Configure(IApplicationBuilder app)


{
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();

// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});

app.UseMvc();
}

Inicie o aplicativo e navegue até http://localhost:<random_port>/swagger/v1/swagger.json . O documento gerado


que descreve os pontos de extremidade é exibido.
Observação: Microsoft Edge, Google Chrome e Firefox exibem documentos JSON nativamente. Há extensões
para o Chrome que formatam o documento para facilitar a leitura. O exemplo a seguir é reduzido para fins de
brevidade.
{
"swagger": "2.0",
"info": {
"version": "v1",
"title": "API V1"
},
"basePath": "/",
"paths": {
"/api/Todo": {
"get": {
"tags": [
"Todo"
],
"operationId": "ApiTodoGet",
"consumes": [],
"produces": [
"text/plain",
"application/json",
"text/json"
],
"responses": {
"200": {
"description": "Success",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/TodoItem"
}
}
}
}
},
"post": {
...
}
},
"/api/Todo/{id}": {
"get": {
...
},
"put": {
...
},
"delete": {
...
},
"definitions": {
"TodoItem": {
"type": "object",
"properties": {
"id": {
"format": "int64",
"type": "integer"
},
"name": {
"type": "string"
},
"isComplete": {
"default": false,
"type": "boolean"
}
}
}
},
"securityDefinitions": {}
}
Este documento orienta sobre a interface do usuário do Swagger, que pode ser exibida navegando para
http://localhost:<random_port>/swagger :

Cada método de ação pública em TodoController pode ser testado da interface do usuário. Clique em um nome
de método para expandir a seção. Adicione quaisquer parâmetros necessários e clique em "Experimente!".
Personalização e extensibilidade
O Swagger fornece opções para documentar o modelo de objeto e personalizar a interface do usuário para
corresponder ao seu tema.
Informações e descrição da API
A ação de configuração passada para o método AddSwaggerGen pode ser usada para adicionar informações como
o autor, a licença e a descrição:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info
{
Version = "v1",
Title = "ToDo API",
Description = "A simple example ASP.NET Core Web API",
TermsOfService = "None",
Contact = new Contact { Name = "Shayne Boyer", Email = "", Url = "https://twitter.com/spboyer" },
License = new License { Name = "Use under LICX", Url = "https://example.com/license" }
});
});

A imagem a seguir representa a interface do usuário do Swagger exibindo as informações de versão:

Comentários XML
Comentários XML podem ser habilitados com as seguintes abordagens:
Visual Studio
Visual Studio para Mac
Visual Studio Code
CLI do .NET Core
Clique com o botão direito do mouse no projeto no Gerenciador de Soluções e selecione Propriedades
Verifique a caixa Arquivo de documentação XML sob a seção Saída da guia Build:

A habilitação de comentários XML fornece informações de depuração para os membros e os tipos públicos não
documentados. Os membros e os tipos não documentados são indicados pela mensagem de aviso: Comentário
XML ausente para o membro ou o tipo publicamente visível.
Configure o Swagger para usar o arquivo XML gerado. Para sistemas operacionais Linux ou não Windows,
caminhos e nomes de arquivo podem diferenciar maiúsculas de minúsculas. Por exemplo, um arquivo
ToDoApi.XML pode ser encontrado no Windows, mas não em CentOS.

public void ConfigureServices(IServiceCollection services)


{
services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));
services.AddMvc();

// Register the Swagger generator, defining one or more Swagger documents


services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info
{
Version = "v1",
Title = "ToDo API",
Description = "A simple example ASP.NET Core Web API",
TermsOfService = "None",
Contact = new Contact { Name = "Shayne Boyer", Email = "", Url = "https://twitter.com/spboyer" },
License = new License { Name = "Use under LICX", Url = "https://example.com/license" }
});

// Set the comments path for the Swagger JSON and UI.
var basePath = AppContext.BaseDirectory;
var xmlPath = Path.Combine(basePath, "TodoApi.xml");
c.IncludeXmlComments(xmlPath);
});
}

No código anterior, ApplicationBasePath obtém o caminho base do aplicativo. O caminho base é usado para
localizar o arquivo de comentários XML. TodoApi.xml funciona apenas para este exemplo, desde que o nome do
arquivo de comentários XML gerado seja baseado no nome do aplicativo.
Adicionar comentários de barra tripla ao método aprimora a interface do usuário do Swagger adicionando a
descrição ao cabeçalho da seção:

/// <summary>
/// Deletes a specific TodoItem.
/// </summary>
/// <param name="id"></param>
[HttpDelete("{id}")]
public IActionResult Delete(long id)
{
var todo = _context.TodoItems.FirstOrDefault(t => t.Id == id);
if (todo == null)
{
return NotFound();
}

_context.TodoItems.Remove(todo);
_context.SaveChanges();
return new NoContentResult();
}
A interface do usuário é controlada pelo arquivo JSON gerado, que também contém estes comentários:

"delete": {
"tags": [
"Todo"
],
"summary": "Deletes a specific TodoItem.",
"operationId": "ApiTodoByIdDelete",
"consumes": [],
"produces": [],
"parameters": [
{
"name": "id",
"in": "path",
"description": "",
"required": true,
"type": "integer",
"format": "int64"
}
],
"responses": {
"200": {
"description": "Success"
}
}
}

Adicione uma marca à documentação do método de ação Create . Ele suplementa as informações especificadas
na marca <summary> e fornece uma interface do usuário do Swagger mais robusta. O conteúdo de marca
<remarks> pode consistir em texto, JSON ou XML.
/// <summary>
/// Creates a TodoItem.
/// </summary>
/// <remarks>
/// Sample request:
///
/// POST /Todo
/// {
/// "id": 1,
/// "name": "Item1",
/// "isComplete": true
/// }
///
/// </remarks>
/// <param name="item"></param>
/// <returns>A newly-created TodoItem</returns>
/// <response code="201">Returns the newly-created item</response>
/// <response code="400">If the item is null</response>
[HttpPost]
[ProducesResponseType(typeof(TodoItem), 201)]
[ProducesResponseType(typeof(TodoItem), 400)]
public IActionResult Create([FromBody] TodoItem item)
{
if (item == null)
{
return BadRequest();
}

_context.TodoItems.Add(item);
_context.SaveChanges();

return CreatedAtRoute("GetTodo", new { id = item.Id }, item);


}

Observe os aprimoramentos de interface do usuário com esses comentários adicionais.

Anotações de dados
Decore o modelo com atributos encontrados em System.ComponentModel.DataAnnotations para ajudar a controlar
os componentes de interface do usuário do Swagger.
Adicione o atributo [Required] à propriedade Name da classe TodoItem :
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }

[Required]
public string Name { get; set; }

[DefaultValue(false)]
public bool IsComplete { get; set; }
}
}

A presença desse atributo altera o comportamento da interface do usuário e altera o esquema JSON subjacente:

"definitions": {
"TodoItem": {
"required": [
"name"
],
"type": "object",
"properties": {
"id": {
"format": "int64",
"type": "integer"
},
"name": {
"type": "string"
},
"isComplete": {
"default": false,
"type": "boolean"
}
}
}
},

Adicione o atributo [Produces("application/json")] ao controlador da API. A finalidade dele é declarar que as


ações do controlador dão suporte a retornar um tipo de conteúdo de application/json:

namespace TodoApi.Controllers
{
[Produces("application/json")]
[Route("api/[controller]")]
public class TodoController : Controller
{
private readonly TodoContext _context;

A lista suspensa Tipo de Conteúdo de Resposta seleciona esse tipo de conteúdo como o padrão para ações
GET do controlador:
À medida que aumenta o uso de anotações de dados na API Web, a interface do usuário e as páginas de ajuda da
API se tornam mais descritivas e úteis.
Descrevendo os tipos de resposta
Os desenvolvedores de consumo estão mais preocupados com o que é retornado — especificamente, tipos de
resposta e códigos de erro (se não padrão). Eles são manipulados nos comentários XML e anotações de dados.
A ação Create retorna 201 Created em caso de êxito ou 400 Bad Request quando o corpo da solicitação postada
é nulo. Sem a documentação adequada na interface do usuário do Swagger, o consumidor não tem
conhecimento desses resultados esperados. Esse problema é corrigido adicionando as linhas destacadas no
exemplo a seguir:

/// <summary>
/// Creates a TodoItem.
/// </summary>
/// <remarks>
/// Sample request:
///
/// POST /Todo
/// {
/// "id": 1,
/// "name": "Item1",
/// "isComplete": true
/// }
///
/// </remarks>
/// <param name="item"></param>
/// <returns>A newly-created TodoItem</returns>
/// <response code="201">Returns the newly-created item</response>
/// <response code="400">If the item is null</response>
[HttpPost]
[ProducesResponseType(typeof(TodoItem), 201)]
[ProducesResponseType(typeof(TodoItem), 400)]
public IActionResult Create([FromBody] TodoItem item)
{
if (item == null)
{
return BadRequest();
}

_context.TodoItems.Add(item);
_context.SaveChanges();

return CreatedAtRoute("GetTodo", new { id = item.Id }, item);


}

A interface do usuário do Swagger agora documenta claramente os códigos de resposta HTTP esperados:
Personalizando a interface do usuário
A interface do usuário de estoque é funcional e também apresentável; no entanto, ao criar páginas de
documentação para sua API, você deseja que ela represente sua marca ou tema. A realização dessa tarefa com os
componentes de Swashbuckle requer a adição de recursos para servir arquivos estáticos e, em seguida, criar a
estrutura de pastas para hospedar esses arquivos.
Se você usar o .NET Framework como destino, adicione o pacote NuGet Microsoft.AspNetCore.StaticFiles ao
projeto:

<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.0" />

Habilite o middleware de arquivos estáticos:

public void Configure(IApplicationBuilder app)


{
app.UseStaticFiles();

// Enable middleware to serve generated Swagger as a JSON endpoint.


app.UseSwagger();

// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});

app.UseMvc();
}

Adquira o conteúdo da pasta dist do repositório GitHub da interface do usuário do Swagger. Essa pasta contém
os ativos necessários para a página da interface do usuário do Swagger.
Crie uma pasta swagger/wwwroot/ui e copie para ela o conteúdo da pasta dist.
Criar um arquivo wwwroot/swagger/ui/css/custom.css com o CSS a seguir para personalizar o cabeçalho da
página:
.swagger-section #header
{
border-bottom: 1px solid #000000;
font-style: normal;
font-weight: 400;
font-family: "Segoe UI Light","Segoe WP Light","Segoe UI","Segoe WP",Tahoma,Arial,sans-serif;
background-color: black;
}

.swagger-section #header h1
{
text-align: center;
font-size: 20px;
color: white;
}

Referencie custom.css no arquivo index.html:

<link href='css/custom.css' media='screen' rel='stylesheet' type='text/css' />

Navegue até a página index.html em http://localhost:<random_port>/swagger/ui/index.html . Digite


http://localhost:<random_port>/swagger/v1/swagger.json na caixa de texto do cabeçalho e clique no botão
Explorar. A página resultante será semelhante ao seguinte:
Há muito mais que você pode fazer com a página. Consulte as funcionalidades completas para os recursos de
interface do usuário no repositório GitHub da interface do usuário do Swagger.
Trabalhar com os dados no ASP.NET Core
10/04/2018 • 1 min to read • Edit Online

Introdução às Páginas Razor e ao Entity Framework Core usando o Visual Studio


Introdução a Páginas do Razor e ao EF
Operações Create, Read, Update e Delete
Classificar, filtrar, paginar e agrupar
Migrações
Criar um modelo de dados complexo
Ler dados relacionados
Atualizar dados relacionados
Tratar conflitos de simultaneidade
Introdução ao ASP.NET Core MVC e ao Entity Framework Core usando o Visual Studio
Introdução
Operações Create, Read, Update e Delete
Classificar, filtrar, paginar e agrupar
Migrações
Criar um modelo de dados complexo
Ler dados relacionados
Atualizar dados relacionados
Tratar conflitos de simultaneidade
Herança
Tópicos avançados
ASP.NET Core com o EF Core – novo banco de dados (site de documentação do Entity Framework Core)
ASP.NET Core com o EF Core – banco de dados existente (site de documentação do Entity Framework Core)
Introdução ao ASP.NET Core e ao Entity Framework 6
Armazenamento do Azure
Adicionando o Armazenamento do Azure Usando o Visual Studio Connected Services
Introdução ao Armazenamento de Blobs do Azure e ao Visual Studio Connected Services
Introdução ao Armazenamento de Filas e ao Visual Studio Connected Services
Introdução ao Armazenamento de Tabelas do Azure e ao Visual Studio Connected Services
Páginas Razor do ASP.NET Core com EF Core – série
de tutoriais
10/04/2018 • 1 min to read • Edit Online

Esta série de tutoriais ensina a criar aplicativos Web de páginas Razor do ASP.NET Core que usam o EF (Entity
Framework) Core para o acesso a dados. Os tutoriais exigem o Visual Studio 2017.
1. Introdução
2. Operações Create, Read, Update e Delete
3. Classificação, filtragem, paginação e agrupamento
4. Migrações
5. Criar um modelo de dados complexo
6. Lendo dados relacionados
7. Atualizando dados relacionados
8. Tratar conflitos de simultaneidade
Introdução às Páginas do Razor e ao Entity
Framework Core usando o Visual Studio (1 de 8)
14/02/2018 • 31 min to read • Edit Online

Por Tom Dykstra e Rick Anderson


O aplicativo Web de exemplo Contoso University demonstra como criar aplicativos Web ASP.NET Core 2.0 MVC
usando o EF (Entity Framework) Core 2.0 e o Visual Studio 2017.
O aplicativo de exemplo é um site de uma Contoso University fictícia. Ele inclui funcionalidades como admissão de
alunos, criação de cursos e atribuições de instrutor. Esta página é a primeira de uma série de tutoriais que explica
como criar o aplicativo de exemplo Contoso University.
Baixe ou exiba o aplicativo concluído. Instruções de download.

Pré-requisitos
Install the following:
.NET Core 2.0.0 SDK or later.
Visual Studio 2017 version 15.3 or later with the ASP.NET and web development workload.
Familiaridade com as Páginas do Razor. Os novos programadores devem concluir a Introdução às Páginas do
Razor antes de começar esta série.

Solução de problemas
Caso tenha um problema que não consiga resolver, em geral, você poderá encontrar a solução comparando o
código com o estágio concluído ou o projeto concluído. Para obter uma lista de erros comuns e como resolvê-los,
consulte a seção Solução de problemas do último tutorial da série. Caso não encontre o que precisa na seção, poste
uma pergunta no StackOverflow.com sobre o ASP.NET Core ou o EF Core.

DICA
Esta série de tutoriais baseia-se no que foi feito nos tutoriais anteriores. Considere a possibilidade de salvar uma cópia do
projeto após a conclusão bem-sucedida de cada tutorial. Caso tenha problemas, comece novamente no tutorial anterior em
vez de voltar ao início. Como alternativa, você pode baixar um estágio concluído e começar novamente usando o estágio
concluído.

O aplicativo Web Contoso University


O aplicativo criado nesses tutoriais é um site básico de universidade.
Os usuários podem exibir e atualizar informações de alunos, cursos e instrutores. Veja a seguir algumas das telas
criadas no tutorial.
O estilo de interface do usuário deste site é próximo ao que é gerado pelos modelos internos. O foco do tutorial é o
EF Core com as Páginas do Razor, não a interface do usuário.

Criar um aplicativo Web das Páginas do Razor


No menu Arquivo do Visual Studio, selecione Novo > Projeto.
Crie um novo Aplicativo Web ASP.NET Core. Nomeie o projeto ContosoUniversity. É importante nomear o
projeto ContosoUniversity para que os namespaces sejam correspondentes quando o código for
copiado/colado.

Selecione ASP.NET Core 2.0 na lista suspensa e selecione Aplicativo Web.


Pressione F5 para executar o aplicativo no modo de depuração ou Ctrl-F5 para executar sem anexar o depurador

Configurar o estilo do site


Algumas alterações configuram o menu do site, o layout e a home page.
Abra Pages/_Layout.cshtml e faça as seguintes alterações:
Altere cada ocorrência de "ContosoUniversity" para "Contoso University". Há três ocorrências.
Adicione entradas de menu para Alunos, Cursos, Instrutores e Departamentos e exclua a entrada de
menu Contato.
As alterações são realçadas. (Toda a marcação não é exibida.)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Contoso University</title>

<environment include="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-
value="absolute" />
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-
collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a asp-page="/Index" class="navbar-brand">Contoso University</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a asp-page="/Index">Home</a></li>
<li><a asp-page="/About">About</a></li>
<li><a asp-page="/Students/Index">Students</a></li>
<li><a asp-page="/Courses/Index">Courses</a></li>
<li><a asp-page="/Instructors/Index">Instructors</a></li>
<li><a asp-page="/Departments/Index">Departments</a></li>
</ul>
</div>
</div>
</nav>
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>&copy; 2017 - Contoso University</p>
</footer>
</div>

Em Pages/Index.cshtml, substitua o conteúdo do arquivo pelo seguinte código para substituir o texto sobre o
ASP.NET e MVC pelo texto sobre este aplicativo:
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}

<div class="jumbotron">
<h1>Contoso University</h1>
</div>
<div class="row">
<div class="col-md-4">
<h2>Welcome to Contoso University</h2>
<p>
Contoso University is a sample application that
demonstrates how to use Entity Framework Core in an
ASP.NET Core Razor Pages web app.
</p>
</div>
<div class="col-md-4">
<h2>Build it from scratch</h2>
<p>You can build the application by following the steps in a series of tutorials.</p>
<p><a class="btn btn-default"
href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro">
See the tutorial &raquo;</a></p>
</div>
<div class="col-md-4">
<h2>Download it</h2>
<p>You can download the completed project from GitHub.</p>
<p><a class="btn btn-default"
href="https://github.com/aspnet/Docs/tree/master/aspnetcore/data/ef-rp/intro/samples/cu-final">
See project source code &raquo;</a></p>
</div>
</div>

Pressione CTRL+F5 para executar o projeto. A home page é exibida com as guias criadas nos seguintes tutoriais:
Criar o modelo de dados
Crie classes de entidade para o aplicativo Contoso University. Comece com as três seguintes entidades:

Há uma relação um-para-muitos entre as entidades Student e Enrollment . Há uma relação um-para-muitos entre
as entidades Course e Enrollment . Um aluno pode se registrar em qualquer quantidade de cursos. Um curso pode
ter qualquer quantidade de alunos registrados.
Nas seções a seguir, é criada uma classe para cada uma dessas entidades.
A entidade Student

Crie uma pasta Models. Na pasta Models, crie um arquivo de classe chamado Student.cs com o seguinte código:

using System;
using System.Collections.Generic;

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }

public ICollection<Enrollment> Enrollments { get; set; }


}
}

A propriedade ID se torna a coluna de chave primária da tabela de BD (banco de dados) que corresponde a essa
classe. Por padrão, o EF Core interpreta uma propriedade nomeada ID ou classnameID como a chave primária.
A propriedade Enrollments é uma propriedade de navegação. As propriedades de navegação vinculam-se a outras
entidades que estão relacionadas a essa entidade. Nesse caso, a propriedade Enrollments de uma Student entity
armazena todas as entidades Enrollment relacionadas a essa Student . Por exemplo, se uma linha Aluno no BD
tiver duas linhas Registro relacionadas, a propriedade de navegação Enrollments conterá duas entidades
Enrollment . Uma linha Enrollment relacionada é uma linha que contém o valor de chave primária do aluno na
coluna StudentID . Por exemplo, suponha que o aluno com ID=1 tenha duas linhas na tabela Enrollment . A tabela
Enrollment tem duas linhas com StudentID = 1. StudentID é uma chave estrangeira na tabela Enrollment que
especifica o aluno na tabela Student .
Se uma propriedade de navegação puder armazenar várias entidades, a propriedade de navegação deverá ser um
tipo de lista, como ICollection<T> . ICollection<T> pode ser especificado ou um tipo como List<T> ou
HashSet<T> . Quando ICollection<T> é usado, o EF Core cria uma coleção HashSet<T> por padrão. As
propriedades de navegação que armazenam várias entidades são provenientes de relações muitos para muitos e
um-para-muitos.
A entidade Enrollment
Na pasta Models, crie Enrollment.cs com o seguinte código:

namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}

public class Enrollment


{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }

public Course Course { get; set; }


public Student Student { get; set; }
}
}

A propriedade EnrollmentID é a chave primária. Essa entidade usa o padrão classnameID em vez de ID como a
entidade Student . Normalmente, os desenvolvedores escolhem um padrão e o usam em todo o modelo de dados.
Em um tutorial posterior, o uso de uma ID sem nome de classe é mostrado para facilitar a implementação da
herança no modelo de dados.
A propriedade Grade é um enum . O ponto de interrogação após a declaração de tipo Grade indica que a
propriedade Grade permite valor nulo. Uma nota nula é diferente de uma nota zero – nulo significa que uma nota
não é conhecida ou que ainda não foi atribuída.
A propriedade StudentID é uma chave estrangeira e a propriedade de navegação correspondente é Student . Uma
entidade Enrollment está associada a uma entidade Student e, portanto, a propriedade contém uma única
entidade Student . A entidade Student é distinta da propriedade de navegação Student.Enrollments , que contém
várias entidades Enrollment .
A propriedade CourseID é uma chave estrangeira e a propriedade de navegação correspondente é Course . Uma
entidade Enrollment está associada a uma entidade Course .
O EF Core interpreta uma propriedade como uma chave estrangeira se ela é nomeada
<navigation property name><primary key property name> . Por exemplo, StudentID para a propriedade de navegação
Student , pois a chave primária da entidade Student é ID . Propriedades de chave estrangeira também podem ser
nomeadas <primary key property name> . Por exemplo, CourseID , pois a chave primária da entidade Course é
CourseID .

A entidade Course

Na pasta Models, crie Course.cs com o seguinte código:


using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }

public ICollection<Enrollment> Enrollments { get; set; }


}
}

A propriedade Enrollments é uma propriedade de navegação. Uma entidade Course pode estar relacionada a
qualquer quantidade de entidades Enrollment .
O atributo DatabaseGenerated permite que o aplicativo especifique a chave primária em vez de fazer com que ela
seja gerada pelo BD.

Criar o contexto de BD SchoolContext


A classe principal que coordena a funcionalidade do EF Core de um modelo de dados é a classe de contexto de BD.
O contexto de dados é derivado de Microsoft.EntityFrameworkCore.DbContext . O contexto de dados especifica quais
entidades são incluídas no modelo de dados. Neste projeto, a classe é chamada SchoolContext .
Na pasta do projeto, crie uma pasta chamada Dados.
Na pasta Dados, crie SchoolContext.cs com o seguinte código:

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}

public DbSet<Course> Courses { get; set; }


public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
}
}

Esse código cria uma propriedade DbSet para cada conjunto de entidades. Na terminologia do EF Core:
Um conjunto de entidades normalmente corresponde a uma tabela de BD.
Uma entidade corresponde a uma linha da tabela.
DbSet<Enrollment> e DbSet<Course> podem ser omitidos. O EF Core inclui-os de forma implícita porque a entidade
Student referencia a entidade Enrollment e a entidade Enrollment referencia a entidade Course . Para este
tutorial, mantenha DbSet<Enrollment> e DbSet<Course> no SchoolContext .
Quando o BD é criado, o EF Core cria tabelas que têm nomes iguais aos nomes de propriedade DbSet . Nomes de
propriedade para coleções são normalmente plurais (Students em vez de Student). Os desenvolvedores não
concordam sobre se os nomes de tabela devem estar no plural. Para esses tutoriais, o comportamento padrão é
substituído pela especificação de nomes singulares de tabela no DbContext. Para especificar nomes singulares de
tabela, adicione o seguinte código realçado:

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}

public DbSet<Course> Courses { get; set; }


public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
}
}
}

Registrar o contexto com a injeção de dependência


O ASP.NET Core inclui a injeção de dependência. Serviços (como o contexto de BD do EF Core) são registrados
com injeção de dependência durante a inicialização do aplicativo. Os componentes que exigem esses serviços
(como as Páginas do Razor) recebem esses serviços por meio de parâmetros do construtor. O código de construtor
que obtém uma instância de contexto do BD é mostrado mais adiante no tutorial.
Para registrar SchoolContext como um serviço, abra Startup.cs e adicione as linhas realçadas ao método
ConfigureServices .

public void ConfigureServices(IServiceCollection services)


{
services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddMvc();
}

O nome da cadeia de conexão é passado para o contexto com a chamada de um método em um objeto
DbContextOptionsBuilder . Para o desenvolvimento local, o sistema de configuração do ASP.NET Core lê a cadeia de
conexão do arquivo appsettings.json.
Adicione instruções using aos namespaces ContosoUniversity.Data e Microsoft.EntityFrameworkCore . Compile o
projeto.

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;

Abra o arquivo appsettings.json e adicione uma cadeia de conexão, conforme mostrado no seguinte código:
{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity1;ConnectRetryCount=0;Trusted_Connection=True;MultipleActiveR
esultSets=true"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}

A cadeia de conexão anterior usa ConnectRetryCount=0 para impedir o travamento do SQLClient.


SQL Server Express LocalDB
A cadeia de conexão especifica um BD LocalDB do SQL Server. LocalDB é uma versão leve do Mecanismo de
Banco de Dados do SQL Server Express destinado ao desenvolvimento de aplicativos, e não ao uso em produção.
O LocalDB é iniciado sob demanda e executado no modo de usuário e, portanto, não há nenhuma configuração
complexa. Por padrão, o LocalDB cria arquivos .mdf de BD no diretório C:/Users/<user> .

Adicionar um código para inicializar o BD com os dados de teste


O EF Core cria um BD vazio. Nesta seção, um método Seed é escrito para populá-lo com os dados de teste.
Na pasta Dados, crie um novo arquivo de classe chamado DbInitializer.cs e adicione o seguinte código:

using ContosoUniversity.Models;
using System;
using System.Linq;

namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
context.Database.EnsureCreated();

// Look for any students.


if (context.Students.Any())
{
return; // DB has been seeded
}

var students = new Student[]


{
new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2005-09-
01")},
new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-01")},
new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-01")},
new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-01")},
new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-01")}
};
foreach (Student s in students)
{
context.Students.Add(s);
}
context.SaveChanges();
var courses = new Course[]
{
new Course{CourseID=1050,Title="Chemistry",Credits=3},
new Course{CourseID=4022,Title="Microeconomics",Credits=3},
new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
new Course{CourseID=1045,Title="Calculus",Credits=4},
new Course{CourseID=3141,Title="Trigonometry",Credits=4},
new Course{CourseID=2021,Title="Composition",Credits=3},
new Course{CourseID=2042,Title="Literature",Credits=4}
};
foreach (Course c in courses)
{
context.Courses.Add(c);
}
context.SaveChanges();

var enrollments = new Enrollment[]


{
new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
new Enrollment{StudentID=3,CourseID=1050},
new Enrollment{StudentID=4,CourseID=1050},
new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
new Enrollment{StudentID=6,CourseID=1045},
new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
};
foreach (Enrollment e in enrollments)
{
context.Enrollments.Add(e);
}
context.SaveChanges();
}
}
}

O código verifica se há alunos no BD. Se não houver nenhum aluno no BD, o BD será propagado com os dados de
teste. Ele carrega os dados de teste em matrizes em vez de em coleções List<T> para otimizar o desempenho.
O método EnsureCreated cria o BD automaticamente para o contexto de BD. Se o BD existir, EnsureCreated
retornará sem modificar o BD.
Em Program.cs, modifique o método Main para fazer o seguinte:
Obtenha uma instância de contexto de BD do contêiner de injeção de dependência.
Chame o método de semente passando a ele o contexto.
Descarte o contexto quando o método de semente for concluído.
O código a seguir mostra o arquivo Program.cs atualizado.
// Unused usings removed
using System;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Data;

namespace ContosoUniversity
{
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);

using (var scope = host.Services.CreateScope())


{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<SchoolContext>();
DbInitializer.Initialize(context);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
}
}

host.Run();
}

public static IWebHost BuildWebHost(string[] args) =>


WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
}

A primeira vez que o aplicativo é executado, o BD é criado e propagado com os dados de teste. Quando o modelo
de dados é atualizado:
Exclua o BD.
Atualize o método de semente.
Execute o aplicativo e um novo BD propagado será criado.
Nos próximos tutoriais, o BD é atualizado quando o modelo de dados é alterado, sem excluir nem recriar o BD.

Adicionar ferramentas de scaffolding


Nesta seção, o PMC (Console do Gerenciador de Pacotes) é usado para adicionar o pacote de geração de código da
Web do Visual Studio. Esse pacote é necessário para executar o mecanismo de scaffolding.
No menu Ferramentas, selecione Gerenciador de pacotes NuGet > Console do Gerenciador de pacotes.
No PMC (Console do Gerenciador de Pacotes), Insira os seguintes comandos:

Install-Package Microsoft.VisualStudio.Web.CodeGeneration.Design
Install-Package Microsoft.VisualStudio.Web.CodeGeneration.Utils
O comando anterior adiciona os pacotes NuGet ao arquivo *.csproj:

<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Utils" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
</ItemGroup>
</Project>

Gerar o modelo por scaffolding


Abra uma janela de comando no diretório do projeto (o diretório que contém os arquivos Program.cs, Startup.cs
e .csproj).
Execute os seguintes comandos:

dotnet restore
dotnet aspnet-codegenerator razorpage -m Student -dc SchoolContext -udl -outDir Pages\Students --
referenceScriptLibraries

Se o seguinte erro for gerado:

Unhandled Exception: System.IO.FileNotFoundException:


Could not load file or assembly
'Microsoft.VisualStudio.Web.CodeGeneration.Utils,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'.
The system cannot find the file specified.

Execute o comando novamente e deixe um comentário na parte inferior da página.


Se você obtiver o erro:

No executable found matching command "dotnet-aspnet-codegenerator"

Abra uma janela de comando no diretório do projeto (o diretório que contém os arquivos Program.cs, Startup.cs e
.csproj).
Compile o projeto. O build gera erros, como o seguinte:
1>Pages\Students\Index.cshtml.cs(26,38,26,45): error CS1061: 'SchoolContext' does not contain a definition for
'Student'

Altere _context.Student globalmente para _context.Students (ou seja, adicione um "s" a Student ). 7 ocorrências
foram encontradas e atualizadas. Esperamos corrigir este bug na próxima versão.
A tabela a seguir detalha os parâmetros dos geradores de código do ASP.NET Core:

PARÂMETRO DESCRIÇÃO

-m O nome do modelo.
PARÂMETRO DESCRIÇÃO

-dc O contexto de dados.

-udl Use o layout padrão.

-outDir O caminho da pasta de saída relativa para criar as exibições.

--referenceScriptLibraries Adiciona _ValidationScriptsPartial para editar e criar


páginas

Use a opção h para obter ajuda sobre o comando aspnet-codegenerator razorpage :

dotnet aspnet-codegenerator razorpage -h

Testar o aplicativo
Execute o aplicativo e selecione o link Alunos. Dependendo da largura do navegador, o link Alunos é exibido na
parte superior da página. Se o link Alunos não estiver visível, clique no ícone de navegação no canto superior
direito.

Teste os links Criar, Editar e Detalhes.

Exibir o BD
Quando o aplicativo é iniciado, DbInitializer.Initialize chama EnsureCreated . EnsureCreated detecta se o BD
existe e cria um, se necessário. Se não houver nenhum aluno no BD, o método Initialize adicionará alunos.
Abra o SSOX (Pesquisador de Objetos do SQL Server) no menu Exibir do Visual Studio. No SSOX, clique em
(localdb)\MSSQLLocalDB > Bancos de Dados > ContosoUniversity1.
Expanda o nó Tabelas.
Clique com o botão direito do mouse na tabela Aluno e clique em Exibir Dados para ver as colunas criadas e as
linhas inseridas na tabela.
Os arquivos .mdf e .ldf do BD estão localizados na pasta C:\Users\.
EnsureCreated é chamado na inicialização do aplicativo, que permite que o seguinte fluxo de trabalho:
Exclua o BD.
Altere o esquema de BD (por exemplo, adicione um campo EmailAddress ).
Execute o aplicativo.
EnsureCreated cria um BD com a coluna EmailAddress .

Convenções
A quantidade de código feita para que o EF Core crie um BD completo é mínima, devido ao uso de convenções ou
de suposições feitas pelo EF Core.
Os nomes de propriedades DbSet são usadas como nomes de tabela. Para entidades não referenciadas por
uma propriedade DbSet , os nomes de classe de entidade são usados como nomes de tabela.
Os nomes de propriedade de entidade são usados para nomes de coluna.
As propriedades de entidade que são nomeadas ID ou classnameID são reconhecidas como propriedades de
chave primária.
Uma propriedade é interpretada como uma propriedade de chave estrangeira se ela é nomeada (por
exemplo, StudentID para a propriedade de navegação Student , pois a chave primária da entidade Student
é ID ). As propriedades de chave estrangeira podem ser nomeadas (por exemplo, EnrollmentID , pois a
chave primária da entidade Enrollment é EnrollmentID ).

O comportamento convencional pode ser substituído. Por exemplo, os nomes de tabela podem ser especificados
de forma explícita, conforme mostrado anteriormente neste tutorial. Os nomes de coluna podem ser definidos de
forma explícita. As chaves primária e estrangeira podem ser definidas de forma explícita.

Código assíncrono
A programação assíncrona é o modo padrão do ASP.NET Core e EF Core.
Um servidor Web tem um número limitado de threads disponíveis e, em situações de alta carga, todos os threads
disponíveis podem estar em uso. Quando isso acontece, o servidor não pode processar novas solicitações até que
os threads são liberados. Com um código síncrono, muitos threads podem ser vinculados enquanto realmente não
são fazendo nenhum trabalho porque estão aguardando a conclusão da E/S. Com um código assíncrono, quando
um processo está aguardando a conclusão da E/S, seu thread é liberado para o servidor para ser usado para
processar outras solicitações. Como resultado, o código assíncrono permite que os recursos do servidor sejam
usados com mais eficiência, e o servidor fica capacitado a manipular mais tráfego sem atrasos.
O código assíncrono introduz uma pequena quantidade de sobrecarga em tempo de execução. Para situações de
baixo tráfego, o impacto no desempenho é insignificante, enquanto para situações de alto tráfego, a melhoria de
desempenho potencial é significativa.
No código a seguir, a palavra-chave async , o valor retornado Task<T> , a palavra-chave await e o método
ToListAsync fazem o código ser executado de forma assíncrona.

public async Task OnGetAsync()


{
Student = await _context.Students.ToListAsync();
}

A palavra-chave async instrui o compilador a:


Gerar retornos de chamada para partes do corpo do método.
Criar automaticamente o objeto Task que é retornado. Para obter mais informações, consulte Tipo de
retorno de Tarefa.
O tipo de retorno implícito Task representa um trabalho em andamento.
A palavra-chave await faz com que o compilador divida o método em duas partes. A primeira parte
termina com a operação que é iniciada de forma assíncrona. A segunda parte é colocada em um método de
retorno de chamada que é chamado quando a operação é concluída.
ToListAsync é a versão assíncrona do método de extensão ToList .
Algumas coisas a serem consideradas ao escrever um código assíncrono que usa o EF Core:
Somente instruções que fazem com que consultas ou comandos sejam enviados ao BD são executadas de
forma assíncrona. Isso inclui ToListAsync , SingleOrDefaultAsync , FirstOrDefaultAsync e SaveChangesAsync .
Isso não inclui instruções que apenas alteram um IQueryable , como
var students = context.Students.Where(s => s.LastName == "Davolio") .

Um contexto do EF Core não é thread-safe: não tente realizar várias operações em paralelo.
Para aproveitar os benefícios de desempenho do código assíncrono, verifique se os pacotes de biblioteca
(como para paginação) usam o código assíncrono se eles chamam métodos do EF Core que enviam
consultas ao BD.
Para obter mais informações sobre a programação assíncrona no .NET, consulte Visão geral da programação
assíncrona.
No próximo tutorial, as operações CRUD (criar, ler, atualizar e excluir) básicas são examinadas.

Avançar
Criar, ler, atualizar e excluir – EF Core com as Páginas
do Razor (2 de 8)
14/02/2018 • 21 min to read • Edit Online

Por Tom Dykstra, Jon P Smith e Rick Anderson


O aplicativo Web Contoso University demonstra como criar aplicativos Web das Páginas do Razor usando o EF
Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o primeiro tutorial.
Neste tutorial, o código CRUD (criar, ler, atualizar e excluir) gerado por scaffolding é examinado e personalizado.
Observação: para minimizar a complexidade e manter o foco destes tutoriais no EF Core, o código do EF Core é
usado nos modelos de página das Páginas do Razor. Alguns desenvolvedores usam um padrão de repositório ou
de camada de serviço para criar uma camada de abstração entre a interface do usuário (Páginas do Razor) e a
camada de acesso a dados.
Neste tutorial, as Páginas Criar, Editar, Excluir e Detalhes do Razor na pasta Student são modificadas.
O código gerado por scaffolding usa o seguinte padrão para as páginas Criar, Editar e Excluir:
Obtenha e exiba os dados solicitados com o método HTTP GET OnGetAsync .
Salve as alterações nos dados com o método HTTP POST OnPostAsync .
As páginas Índice e Detalhes obtêm e exibem os dados solicitados com o método HTTP GET OnGetAsync

Substitua SingleOrDefaultAsync com FirstOrDefaultAsync


O código gerado usa SingleOrDefaultAsync para buscar a entidade solicitada. FirstOrDefaultAsync é mais
eficiente na busca de uma entidade:
A menos que o código precise verificar se não há mais de uma entidade retornada da consulta.
SingleOrDefaultAsync busca mais dados e faz o trabalho desnecessário.
SingleOrDefaultAsync gera uma exceção se há mais de uma entidade que se ajusta à parte do filtro.
FirstOrDefaultAsync não gera uma exceção se há mais de uma entidade que se ajusta à parte do filtro.

Substitua SingleOrDefaultAsync globalmente com FirstOrDefaultAsync . SingleOrDefaultAsync é usado em cinco


locais:
OnGetAsync na página Detalhes.
OnGetAsync e OnPostAsync nas páginas Editar e Excluir.
FindAsync
Em grande parte do código gerado por scaffolding, FindAsync pode ser usado no lugar de FirstOrDefaultAsync
ou SingleOrDefaultAsync .
FindAsync :
Encontra uma entidade com o PK (chave primária). Se uma entidade com o PK está sendo controlada pelo
contexto, ela é retornada sem uma solicitação para o BD.
É simples e conciso.
É otimizado para pesquisar uma única entidade.
Pode ter benefícios de desempenho em algumas situações, mas raramente entram em jogo para cenários da
Web normais.
Usa FirstAsync em vez de SingleAsync de forma implícita. Mas se você deseja incluir outras entidades,
Localizar não é mais apropriado. Isso significa que talvez seja necessário abandonar Localizar e passar para
uma consulta à medida que o aplicativo avança.

Personalizar a página Detalhes


Navegue para a página Pages/Students . Os links Editar, Detalhes e Excluir são gerados pelo Auxiliar de
Marcação de Âncora no arquivo Pages/Students/Index.cshtml.

<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>

Selecione um link Detalhes. A URL tem o formato http://localhost:5000/Students/Details?id=2 . A ID do Aluno é


passada com uma cadeia de caracteres de consulta ( ?id=2 ).
Atualize as Páginas Editar, Detalhes e Excluir do Razor para que elas usem o modelo de rota "{id:int}" . Altere a
diretiva de página de cada uma dessas páginas de @page para @page "{id:int}" .
Uma solicitação para a página com o modelo de rota "{id:int}" que não inclui um valor inteiro de rota retorna um
erro HTTP 404 (não encontrado). Por exemplo, http://localhost:5000/Students/Details retorna um erro 404. Para
tornar a ID opcional, acrescente ? à restrição de rota:

@page "{id:int?}"

Execute o aplicativo, clique em um link Detalhes e verifique se a URL está passando a ID como dados de rota (
http://localhost:5000/Students/Details/2 ).

Não altere @page globalmente para @page "{id:int}" , pois isso desfaz os links para as páginas Início e Criar.
Adicionar dados relacionados
O código gerado por scaffolding para a página Índice de Alunos não inclui a propriedade Enrollments . Nesta
seção, o conteúdo da coleção Enrollments é exibido na página Detalhes.
O método OnGetAsync de Pages/Students/Details.cshtml.cs usa o método FirstOrDefaultAsync para recuperar
uma única entidade Student . Adicione o seguinte código realçado:
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}

Student = await _context.Students


.Include(s => s.Enrollments)
.ThenInclude(e => e.Course)
.AsNoTracking()
.FirstOrDefaultAsync (m => m.ID == id);

if (Student == null)
{
return NotFound();
}
return Page();
}

Os métodos Include e ThenInclude fazem com que o contexto carregue a propriedade de navegação
Student.Enrollments e, dentro de cada registro, a propriedade de navegação Enrollment.Course . Esses métodos
são examinados em detalhes no tutorial sobre leitura de dados relacionados.
O método AsNoTracking melhora o desempenho em cenários em que as entidades retornadas não são
atualizadas no contexto atual. AsNoTracking é abordado mais adiante neste tutorial.
Exibir registros relacionados na página Detalhes
Abra Pages/Students/Details.cshtml. Adicione o seguinte código realçado para exibir uma lista de registros:
@page "{id:int}"
@model ContosoUniversity.Pages.Students.DetailsModel

@{
ViewData["Title"] = "Details";
}

<h2>Details</h2>

<div>
<h4>Student</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Student.LastName)
</dt>
<dd>
@Html.DisplayFor(model => model.Student.LastName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Student.FirstMidName)
</dt>
<dd>
@Html.DisplayFor(model => model.Student.FirstMidName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Student.EnrollmentDate)
</dt>
<dd>
@Html.DisplayFor(model => model.Student.EnrollmentDate)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Student.Enrollments)
</dt>
<dd>
<table class="table">
<tr>
<th>Course Title</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Student.Enrollments)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Course.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
</dd>
</dl>
</div>
<div>
<a asp-page="./Edit" asp-route-id="@Model.Student.ID">Edit</a> |
<a asp-page="./Index">Back to List</a>
</div>

Se o recuo do código estiver incorreto depois que o código for colado, pressione CTRL -K-D para corrigi-lo.
O código anterior percorre as entidades na propriedade de navegação Enrollments . Para cada registro, ele exibe o
nome do curso e a nota. O título do curso é recuperado da entidade Course, que é armazenada na propriedade de
navegação Course da entidade Enrollments.
Execute o aplicativo, selecione a guia Alunos e clique no link Detalhes de um aluno. A lista de cursos e notas do
aluno selecionado é exibida.

Atualizar a página Criar


Atualize o método OnPostAsync em Pages/Students/Create.cshtml.cs com o seguinte código:

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

var emptyStudent = new Student();

if (await TryUpdateModelAsync<Student>(
emptyStudent,
"student", // Prefix for form value.
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
_context.Students.Add(emptyStudent);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}

return null;
}

TryUpdateModelAsync
Examine o código [TryUpdateModelAsync](https://docs.microsoft.com/
dotnet/api/microsoft.aspnetcore.mvc.controllerbase.tryupdatemodelasync?view=aspnetcore-
2.0#Microsoft_AspNetCore_Mvc_ControllerBase_TryUpdateModelAsync_System_Object_System_Type_System_
String_):

var emptyStudent = new Student();

if (await TryUpdateModelAsync<Student>(
emptyStudent,
"student", // Prefix for form value.
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{

No código anterior, TryUpdateModelAsync<Student> tenta atualizar o objeto emptyStudent usando os valores de


formulário postados da propriedade PageContext no PageModel. TryUpdateModelAsync atualiza apenas as
propriedades listadas ( s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate ).
Na amostra anterior:
O segundo argumento ( "student", // Prefix ) é o prefixo usado para pesquisar valores. Não diferencia
maiúsculas de minúsculas.
Os valores de formulário postados são convertidos nos tipos no modelo Student usando a associação de
modelos.
Excesso de postagem
O uso de TryUpdateModel para atualizar campos com valores postados é uma melhor prática de segurança porque
ele impede o excesso de postagem. Por exemplo, suponha que a entidade Student inclua uma propriedade
Secret que esta página da Web não deve atualizar nem adicionar:

public class Student


{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public string Secret { get; set; }
}

Mesmo que o aplicativo não tenha um campo Secret na Página criar/atualizar do Razor, um invasor pode definir
o valor Secret por excesso de postagem. Um invasor pode usar uma ferramenta como o Fiddler ou escrever um
JavaScript para postar um valor de formulário Secret . O código original não limita os campos que o associador
de modelos usa quando ele cria uma instância Student.
Seja qual for o valor que o invasor especificou para o campo de formulário Secret , ele será atualizado no BD. A
imagem a seguir mostra a ferramenta Fiddler adicionando o campo Secret (com o valor "OverPost") aos valores
de formulário postados.

O valor "OverPost" foi adicionado com êxito à propriedade Secret da linha inserida. O designer do aplicativo
nunca desejou que a propriedade Secret fosse definida com a página Criar.
Modelo de exibição
Um modelo de exibição normalmente contém um subconjunto das propriedades incluídas no modelo usado pelo
aplicativo. O modelo de aplicativo costuma ser chamado de modelo de domínio. O modelo de domínio
normalmente contém todas as propriedades necessárias para a entidade correspondente no BD. O modelo de
exibição contém apenas as propriedades necessárias para a camada de interface do usuário (por exemplo, a
página Criar). Além do modelo de exibição, alguns aplicativos usam um modelo de associação ou modelo de
entrada para passar dados entre a classe de modelo de página das Páginas do Razor e o navegador. Considere o
seguinte modelo de exibição Student :
using System;

namespace ContosoUniversity.Models
{
public class StudentVM
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
}
}

Os modelos de exibição fornecem uma maneira alternativa para impedir o excesso de postagem. O modelo de
exibição contém apenas as propriedades a serem exibidas ou atualizadas.
O seguinte código usa o modelo de exibição StudentVM para criar um novo aluno:

[BindProperty]
public StudentVM StudentVM { get; set; }

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

var entry = _context.Add(new Student());


entry.CurrentValues.SetValues(StudentVM);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}

O método SetValues define os valores desse objeto lendo os valores de outro objeto PropertyValues. SetValues
usa a correspondência de nomes de propriedade. O tipo de modelo de exibição não precisa estar relacionado ao
tipo de modelo, apenas precisa ter as propriedades correspondentes.
O uso de StudentVM exige a atualização de CreateVM.cshtml para usar StudentVM em vez de Student .
Nas Páginas do Razor, a classe derivada PageModel é o modelo de exibição.

Atualizar a página Editar


Atualize o modelo da página Editar:
public class EditModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public EditModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

[BindProperty]
public Student Student { get; set; }

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


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

Student = await _context.Students.FindAsync(id);

if (Student == null)
{
return NotFound();
}
return Page();
}

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


{
if (!ModelState.IsValid)
{
return Page();
}

var studentToUpdate = await _context.Students.FindAsync(id);

if (await TryUpdateModelAsync<Student>(
studentToUpdate,
"student",
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}

return Page();
}
}

As alterações de código são semelhantes à página Criar, com algumas exceções:


OnPostAsync tem um parâmetro id opcional.
O aluno atual é buscado do BD, em vez de criar um aluno vazio.
FirstOrDefaultAsync foi substituído por FindAsync. FindAsync é uma boa opção ao selecionar uma entidade
da chave primária. Consulte FindAsync para obter mais informações.
Testar as páginas Editar e Criar
Crie e edite algumas entidades de aluno.

Estados da entidade
O contexto de BD controla se as entidades em memória estão em sincronia com suas linhas correspondentes no
BD. As informações de sincronização de contexto do BD determinam o que acontece quando SaveChanges é
chamado. Por exemplo, quando uma nova entidade é passada para o método Add , o estado da entidade é
definido como Added . Quando SaveChanges é chamado, o contexto de BD emite um comando SQL INSERT.
Uma entidade pode estar em um dos seguintes estados:
Added : a entidade ainda não existe no BD. O método SaveChanges emite uma instrução INSERT.
Unchanged : nenhuma alteração precisa ser salva com essa entidade. Uma entidade tem esse status quando
é lida do BD.
Modified : alguns ou todos os valores de propriedade da entidade foram modificados. O método
SaveChanges emite uma instrução UPDATE.
Deleted : a entidade foi marcada para exclusão. O método SaveChanges emite uma instrução DELETE.
Detached : a entidade não está sendo controlada pelo contexto de BD.

Em um aplicativo da área de trabalho, em geral, as alterações de estado são definidas automaticamente. Uma
entidade é lida, as alterações são feitas e o estado da entidade é alterado automaticamente para Modified . A
chamada a SaveChanges gera uma instrução SQL UPDATE que atualiza apenas as propriedades alteradas.
Em um aplicativo Web, o DbContext que lê uma entidade e exibe os dados é descartado depois que uma página é
renderizada. Quando o método OnPostAsync de uma página é chamado, é feita uma nova solicitação da Web e
com uma nova instância do DbContext . A nova leitura da entidade nesse novo contexto simula o processamento
da área de trabalho.

Atualizar a página Excluir


Nesta seção, o código é adicionado para implementar uma mensagem de erro personalizada quando há falha na
chamada a SaveChanges . Adicione uma cadeia de caracteres para que ela contenha as possíveis mensagens de
erro:

public class DeleteModel : PageModel


{
private readonly ContosoUniversity.Data.SchoolContext _context;

public DeleteModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

[BindProperty]
public Student Student { get; set; }
public string ErrorMessage { get; set; }

Substitua o método OnGetAsync pelo seguinte código:


public async Task<IActionResult> OnGetAsync(int? id, bool? saveChangesError = false)
{
if (id == null)
{
return NotFound();
}

Student = await _context.Students


.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);

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

if (saveChangesError.GetValueOrDefault())
{
ErrorMessage = "Delete failed. Try again";
}

return Page();
}

O código anterior contém o parâmetro opcional saveChangesError . saveChangesError indica se o método foi
chamado após uma falha ao excluir o objeto de aluno. A operação de exclusão pode falhar devido a problemas de
rede temporários. Erros de rede transitórios serão mais prováveis de ocorrerem na nuvem. saveChangesError é
falso quando a página Excluir OnGetAsync é chamada na interface do usuário. Quando OnGetAsync é chamado por
OnPostAsync (devido à falha da operação de exclusão), o parâmetro saveChangesError é verdadeiro.

O método OnPostAsync nas páginas Excluir


Substitua OnPostAsync pelo seguinte código:

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


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

var student = await _context.Students


.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);

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

try
{
_context.Students.Remove(student);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction("./Delete",
new { id = id, saveChangesError = true });
}
}
O código anterior recupera a entidade selecionada e, em seguida, chama o método Remove para definir o status
da entidade como Deleted . Quando SaveChanges é chamado, um comando SQL DELETE é gerado. Se Remove
falhar:
A exceção de BD é capturada.
O método OnGetAsync das páginas Excluir é chamado com saveChangesError=true .
Atualizar a Página Excluir do Razor
Adicione a mensagem de erro realçada a seguir à Página Excluir do Razor.

@page "{id:int}"
@model ContosoUniversity.Pages.Students.DeleteModel

@{
ViewData["Title"] = "Delete";
}

<h2>Delete</h2>

<p class="text-danger">@Model.ErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>


<div>

Exclusão de teste.

Erros comuns
Student/Home ou outros links não funcionam:
Verifique se a Página do Razor contém a diretiva @page correta. Por exemplo, a Página Aluno/Home do Razor
não deve conter um modelo de rota:

@page "{id:int}"

Cada Página do Razor deve incluir a diretiva @page .

Anterior Próximo
Classificação, filtragem, paginação e agrupamento –
EF Core com as Páginas do Razor (3 de 8)
14/02/2018 • 25 min to read • Edit Online

Por Tom Dykstra, Rick Anderson e Jon P Smith


O aplicativo Web Contoso University demonstra como criar aplicativos Web das Páginas do Razor usando o EF
Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o primeiro tutorial.
Neste tutorial, as funcionalidades de classificação, filtragem, agrupamento e paginação são adicionadas.
A ilustração a seguir mostra uma página concluída. Os títulos de coluna são links clicáveis para classificar a
coluna. Clicar em um título de coluna alterna repetidamente entre a ordem de classificação ascendente e
descendente.

Caso tenha problemas que não consiga resolver, baixe o aplicativo concluído para este estágio.

Adicionar uma classificação à página Índice


Adicione cadeias de caracteres ao PageModel de Students/Index.cshtml.cs para que ele contenha os parâmetros de
classificação:
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public IndexModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

public string NameSort { get; set; }


public string DateSort { get; set; }
public string CurrentFilter { get; set; }
public string CurrentSort { get; set; }

Atualize OnGetAsync de Students/Index.cshtml.cs com o seguinte código:

public async Task OnGetAsync(string sortOrder)


{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";

IQueryable<Student> studentIQ = from s in _context.Students


select s;

switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}

Student = await studentIQ.AsNoTracking().ToListAsync();


}

O código anterior recebe um parâmetro sortOrder da cadeia de caracteres de consulta na URL. A URL (incluindo
a cadeia de caracteres de consulta) é gerada pelo Auxiliar de Marcação de Âncora
O parâmetro sortOrder é "Name" ou "Data". O parâmetro sortOrder é opcionalmente seguido de "_desc" para
especificar a ordem descendente. A ordem de classificação crescente é padrão.
Quando a página Índice é solicitada do link Alunos, não há nenhuma cadeia de caracteres de consulta. Os alunos
são exibidos em ordem ascendente por sobrenome. A ordem ascendente por sobrenome é o padrão (caso fall-
through) na instrução switch . Quando o usuário clica em um link de título de coluna, o valor sortOrder
apropriado é fornecido no valor de cadeia de caracteres de consulta.
NameSort e DateSort são usados pela Página do Razor para configurar os hiperlinks de título de coluna com os
valores de cadeia de caracteres de consulta apropriados:
public async Task OnGetAsync(string sortOrder)
{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";

IQueryable<Student> studentIQ = from s in _context.Students


select s;

switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}

Student = await studentIQ.AsNoTracking().ToListAsync();


}

O seguinte código contém o operador ?: do C#:

NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";


DateSort = sortOrder == "Date" ? "date_desc" : "Date";

A primeira linha especifica que, quando sortOrder é nulo ou vazio, NameSort é definido como "name_desc". Se
sortOrder não é nulo nem vazio, NameSort é definido como uma cadeia de caracteres vazia.

O ?: operator também é conhecido como o operador ternário.


Essas duas instruções permitem que a exibição defina os hiperlinks de título de coluna da seguinte maneira:

ORDEM DE CLASSIFICAÇÃO ATUAL HIPERLINK DO SOBRENOME HIPERLINK DE DATA

Sobrenome ascendente descending ascending

Sobrenome descendente ascending ascending

Data ascendente ascending descending

Data descendente ascending ascending

O método usa o LINQ to Entities para especificar a coluna pela qual classificar. O código inicializa um
IQueryable<Student> antes da instrução switch e modifica-o na instrução switch:
public async Task OnGetAsync(string sortOrder)
{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";

IQueryable<Student> studentIQ = from s in _context.Students


select s;

switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}

Student = await studentIQ.AsNoTracking().ToListAsync();


}

Quando um IQueryable é criado ou modificado, nenhuma consulta é enviada ao banco de dados. A consulta não
é executada até que o objeto IQueryable seja convertido em uma coleção. IQueryable são convertidos em uma
coleção com uma chamada a um método como ToListAsync . Portanto, o código IQueryable resulta em uma
única consulta que não é executada até que a seguinte instrução:

Student = await studentIQ.AsNoTracking().ToListAsync();

OnGetAsync pode ficar detalhado com um grande número de colunas.


Adicionar hiperlinks de título de coluna à exibição Índice de Alunos
Substitua o código em Students/Index.cshtml, pelo seguinte código realçado:
@page
@model ContosoUniversity.Pages.Students.IndexModel

@{
ViewData["Title"] = "Index";
}

<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>

<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort">
@Html.DisplayNameFor(model => model.Student[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model => model.Student[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort">
@Html.DisplayNameFor(model => model.Student[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Student)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

O código anterior:
Adiciona hiperlinks aos títulos de coluna LastName e EnrollmentDate .
Usa as informações em NameSort e DateSort para configurar hiperlinks com os valores de ordem de
classificação atuais.
Para verificar se a classificação funciona:
Execute o aplicativo e selecione a guia Alunos.
Clique em Sobrenome.
Clique em Data de Registro.
Para obter um melhor entendimento do código:
Em Student/Index.cshtml.cs, defina um ponto de interrupção em switch (sortOrder) .
Adicione uma inspeção para NameSort e DateSort .
Em Student/Index.cshtml, defina um ponto de interrupção em
@Html.DisplayNameFor(model => model.Student[0].LastName) .

Execute o depurador em etapas.

Adicionar uma Caixa de Pesquisa à página Índice de Alunos


Para adicionar a filtragem à página Índice de Alunos:
Uma caixa de texto e um botão Enviar são adicionados à Página do Razor. A caixa de texto fornece uma cadeia
de caracteres de pesquisa no nome ou sobrenome.
O modelo de página é atualizado para usar o valor da caixa de texto.
Adicionar a funcionalidade de filtragem a método Index
Atualize OnGetAsync de Students/Index.cshtml.cs com o seguinte código:

public async Task OnGetAsync(string sortOrder, string searchString)


{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
CurrentFilter = searchString;

IQueryable<Student> studentIQ = from s in _context.Students


select s;
if (!String.IsNullOrEmpty(searchString))
{
studentIQ = studentIQ.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}

switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}

Student = await studentIQ.AsNoTracking().ToListAsync();


}

O código anterior:
Adiciona o parâmetro searchString ao método OnGetAsync . O valor de cadeia de caracteres de pesquisa é
recebido de uma caixa de texto que é adicionada na próxima seção.
Adicionou uma cláusula Where à instrução LINQ. A cláusula Where seleciona somente os alunos cujo nome
ou sobrenome contém a cadeia de caracteres de pesquisa. A instrução LINQ é executada somente se há um
valor a ser pesquisado.
Observação: o código anterior chama o método Where em um objeto IQueryable , e o filtro é processado no
servidor. Em alguns cenários, o aplicativo pode chamar o método Where como um método de extensão em uma
coleção em memória. Por exemplo, suponha que _context.Students seja alterado do DbSet do EF Core para um
método de repositório que retorna uma coleção IEnumerable . O resultado normalmente é o mesmo, mas em
alguns casos pode ser diferente.
Por exemplo, a implementação do .NET Framework do Contains executa uma comparação diferencia maiúsculas
de minúsculas por padrão. No SQL Server, a diferenciação de maiúsculas e minúsculas de Contains é
determinada pela configuração de agrupamento da instância do SQL Server. O SQL Server usa como padrão a
não diferenciação de maiúsculas e minúsculas. ToUpper pode ser chamado para fazer com que o teste diferencie
maiúsculas de minúsculas de forma explícita:
Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())

O código anterior garantirá que os resultados diferenciem maiúsculas de minúsculas se o código for alterado para
usar IEnumerable . Quando Contains é chamado em uma coleção IEnumerable , a implementação do .NET Core é
usada. Quando Contains é chamado em um objeto IQueryable , a implementação do banco de dados é usada. O
retorno de um IEnumerable de um repositório pode ter uma penalidade significativa de desempenho:
1. Todas as linhas são retornadas do servidor de BD.
2. O filtro é aplicado a todas as linhas retornadas no aplicativo.
Há uma penalidade de desempenho por chamar ToUpper . O código ToUpper adiciona uma função à cláusula
WHERE da instrução TSQL SELECT. A função adicionada impede que o otimizador use um índice. Considerando
que o SQL é instalado como diferenciando maiúsculas de minúsculas, é melhor evitar a chamada ToUpper
quando ela não for necessária.
Adicionar uma Caixa de Pesquisa à exibição Índice de Alunos
Em Views/Student/Index.cshtml, adicione o código realçado a seguir para criar um botão Pesquisar e o cromado
variado.

@page
@model ContosoUniversity.Pages.Students.IndexModel

@{
ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
<a asp-page="Create">Create New</a>
</p>

<form asp-page="./Index" method="get">


<div class="form-actions no-color">
<p>
Find by name:
<input type="text" name="SearchString" value="@Model.CurrentFilter" />
<input type="submit" value="Search" class="btn btn-default" /> |
<a asp-page="./Index">Back to full List</a>
</p>
</div>
</form>

<table class="table">
O código anterior usa o auxiliar de marcação <form> para adicionar o botão e a caixa de texto de pesquisa. Por
padrão, o auxiliar de marcação <form> envia dados de formulário com um POST. Com o POST, os parâmetros
são passados no corpo da mensagem HTTP e não na URL. Quando o HTTP GET é usado, os dados de formulário
são passados na URL como cadeias de consulta. Passar os dados com cadeias de consulta permite aos usuários
marcar a URL. As diretrizes do W3C recomendam o uso de GET quando a ação não resulta em uma atualização.
Teste o aplicativo:
Selecione a guia Alunos e insira uma cadeia de caracteres de pesquisa.
Selecione Pesquisar.
Observe que a URL contém a cadeia de caracteres de pesquisa.

http://localhost:5000/Students?SearchString=an

Se a página estiver marcada, o indicador conterá a URL para a página e a cadeia de caracteres de consulta
SearchString . O method="get" na marcação form é o que fez com que a cadeia de caracteres de consulta fosse
gerada.
Atualmente, quando um link de classificação de título de coluna é selecionado, o valor de filtro da caixa Pesquisa
é perdido. O valor de filtro perdido é corrigido na próxima seção.

Adicionar a funcionalidade de paginação à página Índice de Alunos


Nesta seção, uma classe PaginatedList é criada para dar suporte à paginação. A classe PaginatedList usa as
instruções Skip e Take para filtrar dados no servidor em vez de recuperar todas as linhas da tabela. A ilustração
a seguir mostra os botões de paginação.

Na pasta do projeto, crie PaginatedList.cs com o seguinte código:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity
{
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }

public PaginatedList(List<T> items, int count, int pageIndex, int pageSize)


{
PageIndex = pageIndex;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);

this.AddRange(items);
}

public bool HasPreviousPage


{
get
{
return (PageIndex > 1);
}
}

public bool HasNextPage


{
get
{
return (PageIndex < TotalPages);
}
}

public static async Task<PaginatedList<T>> CreateAsync(


IQueryable<T> source, int pageIndex, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip(
(pageIndex - 1) * pageSize)
.Take(pageSize).ToListAsync();
return new PaginatedList<T>(items, count, pageIndex, pageSize);
}
}
}

O método CreateAsync no código anterior usa o tamanho da página e o número da página e aplica as instruções
Skip e Take ao IQueryable . Quando ToListAsync é chamado no IQueryable , ele retorna uma Lista que contém
somente a página solicitada. As propriedades HasPreviousPage e HasNextPage são usadas para habilitar ou
desabilitar os botões de paginação Anterior e Próximo.
O método CreateAsyncé usado para criar o PaginatedList<T> . Um construtor não pode criar o objeto
PaginatedList<T> ; construtores não podem executar um código assíncrono.

Adicionar a funcionalidade de paginação ao método Index


Em Students/Index.cshtml.cs, atualize o tipo de Student em IList<Student> para PaginatedList<Student> :

public PaginatedList<Student> Student { get; set; }


Atualize OnGetAsync de Students/Index.cshtml.cs com o seguinte código:

public async Task OnGetAsync(string sortOrder,


string currentFilter, string searchString, int? pageIndex)
{
CurrentSort = sortOrder;
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
if (searchString != null)
{
pageIndex = 1;
}
else
{
searchString = currentFilter;
}

CurrentFilter = searchString;

IQueryable<Student> studentIQ = from s in _context.Students


select s;
if (!String.IsNullOrEmpty(searchString))
{
studentIQ = studentIQ.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}

int pageSize = 3;
Student = await PaginatedList<Student>.CreateAsync(
studentIQ.AsNoTracking(), pageIndex ?? 1, pageSize);
}

O código anterior adiciona o índice de página, o sortOrder atual e o currentFilter à assinatura do método.

public async Task OnGetAsync(string sortOrder,


string currentFilter, string searchString, int? pageIndex)

Todos os parâmetros são nulos quando:


A página é chamada no link Alunos.
O usuário ainda não clicou em um link de paginação ou classificação.
Quando um link de paginação recebe um clique, a variável de índice de páginas contém o número da página a ser
exibido.
CurrentSort fornece à Página do Razor a ordem de classificação atual. A ordem de classificação atual precisa ser
incluída nos links de paginação para que a ordem de classificação seja mantida durante a paginação.
CurrentFilter fornece à Página do Razor a cadeia de caracteres de filtro atual. O valor CurrentFilter :
Deve ser incluído nos links de paginação para que as configurações de filtro sejam mantidas durante a
paginação.
Deve ser restaurado para a caixa de texto quando a página é exibida novamente.
Se a cadeia de caracteres de pesquisa é alterada durante a paginação, a página é redefinida como 1. A página
precisa ser redefinida como 1, porque o novo filtro pode resultar na exibição de dados diferentes. Quando um
valor de pesquisa é inserido e Enviar é selecionado:
A cadeia de caracteres de pesquisa foi alterada.
O parâmetro searchString não é nulo.

if (searchString != null)
{
pageIndex = 1;
}
else
{
searchString = currentFilter;
}

O método PaginatedList.CreateAsync converte a consulta de alunos em uma única página de alunos de um tipo
de coleção compatível com paginação. Essa única página de alunos é passada para a Página do Razor.

Student = await PaginatedList<Student>.CreateAsync(


studentIQ.AsNoTracking(), pageIndex ?? 1, pageSize);

Os dois pontos de interrogação em PaginatedList.CreateAsync representam o [operador de união de nulo]


(https://docs.microsoft.com/ dotnet/csharp/language-reference/operators/null-conditional-operator). O operador
de união de nulo define um valor padrão para um tipo que permite valor nulo. A expressão (pageIndex ?? 1)
significará retornar o valor de pageIndex se ele tiver um valor. Se pageIndex não tiver um valor, 1 será retornado.

Adicionar links de paginação à Página do Razor do aluno


Atualize a marcação em Students/Index.cshtml. As alterações são realçadas:

@page
@model ContosoUniversity.Pages.Students.IndexModel

@{
ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
<a asp-page="Create">Create New</a>
</p>

<form asp-page="./Index" method="get">


<div class="form-actions no-color">
<p>
Find by name: <input type="text" name="SearchString" value="@Model.CurrentFilter" />
<input type="submit" value="Search" class="btn btn-default" /> |
<a asp-page="./Index">Back to full List</a>
</p>
</div>
</form>
<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Student[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model => model.Student[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Student[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Student) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

@{
var prevDisabled = !Model.Student.HasPreviousPage ? "disabled" : "";
var nextDisabled = !Model.Student.HasNextPage ? "disabled" : "";
}

<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Student.PageIndex - 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-default @prevDisabled">
Previous
</a>
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Student.PageIndex + 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-default @nextDisabled">
Next
</a>

Os links de cabeçalho de coluna usam a cadeia de caracteres de consulta para passar a cadeia de caracteres de
pesquisa atual para o método OnGetAsync , de modo que o usuário possa classificar nos resultados do filtro:
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Student[0].LastName)
</a>

Os botões de paginação são exibidos por auxiliares de marcação:

<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Student.PageIndex - 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-default @prevDisabled">
Previous
</a>
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Student.PageIndex + 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-default @nextDisabled">
Next
</a>

Execute o aplicativo e navegue para a página de alunos.


Para verificar se a paginação funciona, clique nos links de paginação em ordens de classificação diferentes.
Para verificar se a paginação funciona corretamente com a classificação e filtragem, insira uma cadeia de
caracteres de pesquisa e tente fazer a paginação.

Para obter um melhor entendimento do código:


Em Student/Index.cshtml.cs, defina um ponto de interrupção em switch (sortOrder) .
Adicione uma inspeção para NameSort , DateSort , CurrentSort e Model.Student.PageIndex .
Em Student/Index.cshtml, defina um ponto de interrupção em
@Html.DisplayNameFor(model => model.Student[0].LastName) .
Execute o depurador em etapas.

Atualizar a página Sobre para mostras estatísticas de alunos


Nesta etapa, Pages/About.cshtml é atualizada para exibir quantos alunos se registraram para cada data de
registro. A atualização usa o agrupamento e inclui as seguintes etapas:
Criar uma classe de modelo de exibição para os dados usados pela página Sobre.
Modificar a Página Sobre do Razor e o modelo de página.
Criar o modelo de exibição
Crie uma pasta SchoolViewModels na pasta Models.
Na pasta SchoolViewModels, adicione um EnrollmentDateGroup.cs com o seguinte código:

using System;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models.SchoolViewModels
{
public class EnrollmentDateGroup
{
[DataType(DataType.Date)]
public DateTime? EnrollmentDate { get; set; }

public int StudentCount { get; set; }


}
}

Atualizar o modelo da página Sobre


Atualize o arquivo Pages/About.cshtml.cs com o seguinte código:
using ContosoUniversity.Data;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages
{
public class AboutModel : PageModel
{
private readonly SchoolContext _context;

public AboutModel(SchoolContext context)


{
_context = context;
}

public IList<EnrollmentDateGroup> Student { get; set; }

public async Task OnGetAsync()


{
IQueryable<EnrollmentDateGroup> data =
from student in _context.Students
group student by student.EnrollmentDate into dateGroup
select new EnrollmentDateGroup()
{
EnrollmentDate = dateGroup.Key,
StudentCount = dateGroup.Count()
};

Student = await data.AsNoTracking().ToListAsync();


}
}
}

A instrução LINQ agrupa as entidades de alunos por data de registro, calcula o número de entidades em cada
grupo e armazena os resultados em uma coleção de objetos de modelo de exibição EnrollmentDateGroup .
Observação: no momento, não há suporte para o comando group LINQ no EF Core. No código anterior, todos
os registros de alunos são retornados do SQL Server. O comando group é aplicado no aplicativo Páginas do
Razor, não no SQL Server. O EF Core 2.1 dará suporte a esse operador group LINQ, e o agrupamento ocorre no
SQL Server. Consulte Relacional: suporte à conversão de GroupBy() em SQL. O EF Core 2.1 será lançado com o
.NET Core 2.1. Para obter mais informações, consulte Roteiro do .NET Core.
Modificar a Página Sobre do Razor
Substitua o código no arquivo Views/Home/About.cshtml pelo seguinte código:
@page
@model ContosoUniversity.Pages.AboutModel

@{
ViewData["Title"] = "Student Body Statistics";
}

<h2>Student Body Statistics</h2>

<table>
<tr>
<th>
Enrollment Date
</th>
<th>
Students
</th>
</tr>

@foreach (var item in Model.Student)


{
<tr>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
@item.StudentCount
</td>
</tr>
}
</table>

Execute o aplicativo e navegue para a página Sobre. A contagem de alunos para cada data de registro é exibida
em uma tabela.
Caso tenha problemas que não consiga resolver, baixe o aplicativo concluído para este estágio.

Recursos adicionais
Depuração de origem do ASP.NET Core 2.x
No próximo tutorial, o aplicativo usa migrações para atualizar o modelo de dados.
Anterior Próximo
Migrações – tutorial do EF Core com as Páginas do
Razor (4 de 8)
14/02/2018 • 17 min to read • Edit Online

Por Tom Dykstra, Jon P Smith e Rick Anderson


O aplicativo Web Contoso University demonstra como criar aplicativos Web das Páginas do Razor usando o EF
Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o primeiro tutorial.
Neste tutorial, o recurso de migrações do EF Core para o gerenciamento de alterações do modelo de dados é
usado.
Caso tenha problemas que não consiga resolver, baixe o aplicativo concluído para este estágio.
Quando um novo aplicativo é desenvolvido, o modelo de dados é alterado com frequência. Sempre que o modelo
é alterado, ele fica fora de sincronia com o banco de dados. Este tutorial começa configurando o Entity Framework
para criar o banco de dados, caso ele não exista. Sempre que o modelo de dados é alterado:
O BD é removido.
O EF cria um novo que corresponde ao modelo.
O aplicativo propaga o BD com os dados de teste.
Essa abordagem para manter o BD em sincronia com o modelo de dados funciona bem até que você implante o
aplicativo em produção. Quando o aplicativo é executado em produção, normalmente, ele armazena dados que
precisam ser mantidos. O aplicativo não pode começar com um BD de teste sempre que uma alteração é feita
(como a adição de uma nova coluna). O recurso Migrações do EF Core resolve esse problema, permitindo que o
EF Core atualize o esquema de BD em vez de criar um novo BD.
Em vez de remover e recriar o BD quando o modelo de dados é alterado, as migrações atualizam o esquema e
retêm os dados existentes.

Pacotes NuGet do Entity Framework Core para migrações


Para trabalhar com migrações, use o PMC (Console do Gerenciador de Pacotes) ou a CLI (interface de linha de
comando). Esses tutoriais mostram como usar comandos da CLI. Encontre informações sobre o PMC no final
deste tutorial.
As ferramentas do EF Core para a CLI (interface de linha de comando) são fornecidas em
Microsoft.EntityFrameworkCore.Tools.DotNet. Para instalar esse pacote, adicione-o à coleção
DotNetCliToolReference no arquivo .csproj, conforme mostrado. Observação: esse pacote precisa ser instalado
pela edição do arquivo .csproj. O comando install-package ou a GUI do gerenciador de pacotes não pode ser
usado para instalar esse pacote. Edite o arquivo .csproj clicando com o botão direito do mouse no nome do
projeto no Gerenciador de Soluções e selecionando Editar ContosoUniversity.csproj.
A seguinte marcação mostra o arquivo .csproj atualizado com as ferramentas da CLI do EF Core realçadas:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Utils" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
</ItemGroup>
</Project>

No exemplo anterior, os números de versão eram atuais no momento em que o tutorial foi escrito. Use a mesma
versão das ferramentas da CLI do EF Core encontrada nos outros pacotes.

Alterar a cadeia de conexão


No arquivo appsettings.json, altere o nome do BD na cadeia de conexão para ContosoUniversity2.

{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity2;ConnectRetryCount=0;Trusted_Connection=True;MultipleActive
ResultSets=true"
},

A alteração do nome do BD na cadeia de conexão faz com que a primeira migração crie um novo BD. Um novo
BD é criado porque um banco de dados com esse nome não existe. A alteração da cadeia de conexão não é
necessária para começar a usar as migrações.
Uma alternativa à alteração do nome do BD é excluir o BD. Use o SSOX (Pesquisador de Objetos do SQL Server)
ou o comando database drop da CLI:

dotnet ef database drop

A seção a seguir explica como executar comandos da CLI.

Criar uma migração inicial


Compile o projeto.
Abra uma janela Comando e navegue para a pasta do projeto. A pasta do projeto contém o arquivo Startup.cs.
Insira o seguinte na janela Comando:

dotnet ef migrations add InitialCreate

A janela Comando exibe informações semelhantes às seguintes:


info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
User profile is available. Using 'C:\Users\username\AppData\Local\ASP.NET\DataProtection-Keys' as key
repository and Windows DPAPI to encrypt keys at rest.
info: Microsoft.EntityFrameworkCore.Infrastructure[100403]
Entity Framework Core 2.0.0-rtm-26452 initialized 'SchoolContext' using provider
'Microsoft.EntityFrameworkCore.SqlServer' with options: None
Done. To undo this action, use 'ef migrations remove'

Se a migração falhar com a mensagem "não é possível acessar o arquivo... ContosoUniversity.dll porque ele está
sendo usado por outro processo." será exibido:
Pare o IIS Express.
Saia e reinicie o Visual Studio ou
Encontre o ícone do IIS Express na Bandeja do Sistema do Windows.
Clique com o botão direito do mouse no ícone do IIS Express e, em seguida, clique em
ContosoUniversity > Parar Site.
Se a mensagem de erro "Falha no build." for exibida, execute o comando novamente. Se você receber esse erro,
deixe uma observação na parte inferior deste tutorial.
Examinar os métodos Up e Down
O comando migrations add do EF Core gerou um código do qual criar o BD. Esse código de migrações está
localizado no arquivo Migrations<timestamp>_InitialCreate.cs. O método Up da classe InitialCreate cria as
tabelas de BD que correspondem aos conjuntos de entidades do modelo de dados. O método Down exclui-os,
conforme mostrado no seguinte exemplo:

public partial class InitialCreate : Migration


{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Course",
columns: table => new
{
CourseID = table.Column<int>(type: "int", nullable: false),
Credits = table.Column<int>(type: "int", nullable: false),
Title = table.Column<string>(type: "nvarchar(max)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Course", x => x.CourseID);
});

protected override void Down(MigrationBuilder migrationBuilder)


{
migrationBuilder.DropTable(
name: "Enrollment");

migrationBuilder.DropTable(
name: "Course");

migrationBuilder.DropTable(
name: "Student");
}
}
}

As migrações chamam o método Up para implementar as alterações do modelo de dados para uma migração.
Quando você insere um comando para reverter a atualização, as migrações chamam o método Down .
O código anterior refere-se à migração inicial. Esse código foi criado quando o comando
migrations add InitialCreate foi executado. O parâmetro de nome da migração ("InitialCreate" no exemplo) é
usado para o nome do arquivo. O nome da migração pode ser qualquer nome de arquivo válido. É melhor
escolher uma palavra ou frase que resume o que está sendo feito na migração. Por exemplo, uma migração que
adicionou uma tabela de departamento pode ser chamada "AddDepartmentTable".
Se a migração inicial é criada e o BD é encerrado:
O código de criação do BD é gerado.
O código de criação do BD não precisa ser executado porque o BD já corresponde ao modelo de dados. Se o
código de criação do BD é executado, ele não faz nenhuma alteração porque o BD já corresponde ao modelo
de dados.
Quando o aplicativo é implantado em um novo ambiente, o código de criação do BD precisa ser executado para
criar o BD.
Anteriormente, a cadeia de conexão foi alterada para usar um novo nome para o BD. O BD especificado não existe
e, portanto, as migrações criam o BD.
Examinar o instantâneo do modelo de dados
As migrações criam um instantâneo do esquema de BD atual em Migrations/SchoolContextModelSnapshot.cs:
[DbContext(typeof(SchoolContext))]
partial class SchoolContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
modelBuilder
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452")
.HasAnnotation("SqlServer:ValueGenerationStrategy",
SqlServerValueGenerationStrategy.IdentityColumn);

modelBuilder.Entity("ContosoUniversity.Models.Course", b =>
{
b.Property<int>("CourseID");

b.Property<int>("Credits");

b.Property<string>("Title");

b.HasKey("CourseID");

b.ToTable("Course");
});

// Additional code for Enrollment and Student tables not shown.

modelBuilder.Entity("ContosoUniversity.Models.Enrollment", b =>
{
b.HasOne("ContosoUniversity.Models.Course", "Course")
.WithMany("Enrollments")
.HasForeignKey("CourseID")
.OnDelete(DeleteBehavior.Cascade);

b.HasOne("ContosoUniversity.Models.Student", "Student")
.WithMany("Enrollments")
.HasForeignKey("StudentID")
.OnDelete(DeleteBehavior.Cascade);
});
}
}

Como o esquema de BD atual é representado no código, o EF Core não precisa interagir com o BD para criar
migrações. Quando você adiciona uma migração, o EF Core determina o que foi alterado, comparando o modelo
de dados com o arquivo de instantâneo. O EF Core interage com o BD somente quando é necessário atualizar o
BD.
O arquivo de instantâneo precisa estar em sincronia com as migrações que o criaram. Uma migração não pode
ser removida com a exclusão do arquivo nomeado <timestamp>_<migrationname>.cs. Se esse arquivo for
excluído, as migrações restantes ficarão fora de sincronia com o arquivo de instantâneo de BD. Para excluir a
última migração adicionada, use o comando dotnet ef migrations remove.

Remover EnsureCreated
Para o desenvolvimento inicial, o comando EnsureCreated foi usado. Neste tutorial, as migrações são usadas.
EnsureCreated tem as seguintes limitações:

Ignora as migrações e cria o BD e o esquema.


Não cria uma tabela de migrações.
Não pode ser usado com migrações.
Foi projetado para teste ou criação rápida de protótipos em que o BD é removido e recriado com frequência.
Remova a seguinte linha de DbInitializer :
context.Database.EnsureCreated();

Aplicar a migração ao BD em desenvolvimento


Na janela Comando, insira o seguinte para criar o BD e as tabelas.

dotnet ef database update

Observação: se o comando update retornar o erro "Falha no build":


Execute o comando novamente.
Se ele falhar novamente, saia do Visual Studio e, em seguida, execute o comando update .
Deixe uma mensagem na parte inferior da página.
A saída do comando é semelhante à saída de comando migrations add . No comando anterior, os logs para os
comandos SQL que configuraram o BD são exibidos. A maioria dos logs é omitida na seguinte saída de exemplo:

info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
User profile is available. Using 'C:\Users\username\AppData\Local\ASP.NET\DataProtection-Keys' as key
repository and Windows DPAPI to encrypt keys at rest.
info: Microsoft.EntityFrameworkCore.Infrastructure[100403]
Entity Framework Core 2.0.0-rtm-26452 initialized 'SchoolContext' using provider
'Microsoft.EntityFrameworkCore.SqlServer' with options: None
info: Microsoft.EntityFrameworkCore.Database.Command[200101]
Executed DbCommand (467ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
CREATE DATABASE [ContosoUniversity2];
info: Microsoft.EntityFrameworkCore.Database.Command[200101]
Executed DbCommand (20ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE [__EFMigrationsHistory] (
[MigrationId] nvarchar(150) NOT NULL,
[ProductVersion] nvarchar(32) NOT NULL,
CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
);

<logs omitted for brevity>

info: Microsoft.EntityFrameworkCore.Database.Command[200101]
Executed DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20170816151242_InitialCreate', N'2.0.0-rtm-26452');
Done.

Para reduzir o nível de detalhes em mensagens de log, altere os níveis de log no arquivo
appsettings.Development.json. Para obter mais informações, consulte Introdução ao log.
Use o Pesquisador de Objetos do SQL Server para inspecionar o BD. Observe a adição de uma tabela
__EFMigrationsHistory . A tabela __EFMigrationsHistory controla quais migrações foram aplicadas ao BD. Exiba os
dados na __EFMigrationsHistory tabela; ela mostra uma linha para a primeira migração. O último log no exemplo
de saída da CLI anterior mostra a instrução INSERT que cria essa linha.
Execute o aplicativo e verifique se tudo funciona.

Aplicando migrações em produção


Recomendamos que os aplicativos de produção não chamem Database.Migrate na inicialização do aplicativo.
Migrate não deve ser chamado em um aplicativo no farm de servidores. Por exemplo, se o aplicativo foi
implantado na nuvem com escalabilidade horizontal (várias instâncias do aplicativo estão sendo executadas).
A migração de banco de dados deve ser feita como parte da implantação e de maneira controlada. Abordagens de
migração de banco de dados de produção incluem:
Uso de migrações para criar scripts SQL e uso dos scripts SQL na implantação.
Execução de dotnet ef database update em um ambiente controlado.

O EF Core usa a tabela __MigrationsHistory para ver se uma migração precisa ser executada. Se o BD estiver
atualizado, nenhuma migração será executada.

CLI (interface de linha de comando) vs. PMC (Console do Gerenciador


de Pacotes)
As ferramentas do EF Core para o gerenciamento de migrações estão disponíveis em:
Comandos da CLI do .NET Core.
Os cmdlets do PowerShell na janela do PMC (Console do Gerenciador de Pacotes) no Visual Studio.
Este tutorial mostra como usar a CLI; alguns desenvolvedores preferem usar o PMC.
Os comandos do EF Core para o PMC estão no pacote Microsoft.EntityFrameworkCore.Tools. Este pacote está
incluído no metapacote Microsoft.AspNetCore.All e, portanto, não é necessário instalá-lo.
Importante: esse não é o mesmo pacote que é instalado para a CLI com a edição do arquivo .csproj. O nome
deste termina com Tools , ao contrário do nome do pacote da CLI que termina com Tools.DotNet .
Para obter mais informações sobre os comandos da CLI, consulte CLI do .NET Core.
Para obter mais informações sobre os comandos do PMC, consulte Console do Gerenciador de Pacotes (Visual
Studio).

Solução de problemas
Baixe o aplicativo concluído para este estágio.
O aplicativo gera a seguinte exceção:

`SqlException: Cannot open database "ContosoUniversity" requested by the login.


The login failed.
Login failed for user 'user name'.

Solução: execute dotnet ef database update

Se o comando update retornar o erro "Falha no build":


Execute o comando novamente.
Deixe uma mensagem na parte inferior da página.

Anterior Próximo
Criando um modelo de dados complexo – tutorial
do EF Core com as Páginas do Razor (5 de 8)
14/02/2018 • 47 min to read • Edit Online

Por Tom Dykstra e Rick Anderson


O aplicativo Web Contoso University demonstra como criar aplicativos Web das Páginas do Razor usando o EF
Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o primeiro tutorial.
Os tutoriais anteriores trabalharam com um modelo de dados básico composto por três entidades. Neste tutorial:
Mais entidades e relações são adicionadas.
O modelo de dados é personalizado com a especificação das regras de formatação, validação e mapeamento
de banco de dados.
As classes de entidade para o modelo de dados concluído são mostradas na seguinte ilustração:

Caso tenha problemas que não consiga resolver, baixe o aplicativo concluído para este estágio.
Personalizar o modelo de dados com atributos
Nesta seção, o modelo de dados é personalizado com atributos.
O atributo DataType
As páginas de alunos atualmente exibem a hora da data de registro. Normalmente, os campos de data mostram
apenas a data e não a hora.
Atualize Models/Student.cs com o seguinte código realçado:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }

public ICollection<Enrollment> Enrollments { get; set; }


}
}

O atributo DataType especifica um tipo de dados mais específico do que o tipo intrínseco de banco de dados.
Neste caso, apenas a data deve ser exibida, não a data e a hora. A Enumeração DataType fornece muitos tipos de
dados, como Date, Time, PhoneNumber, Currency, EmailAddress, etc. O atributo DataType também pode permitir
que o aplicativo forneça automaticamente recursos específicos a um tipo. Por exemplo:
O link mailto: é criado automaticamente para DataType.EmailAddress .
O seletor de data é fornecido para DataType.Date na maioria dos navegadores.
O atributo DataType emite atributos data- HTML 5 (pronunciados “data dash”) que são consumidos pelos
navegadores HTML 5. Os atributos DataType não fornecem validação.
DataType.Date não especifica o formato da data exibida. Por padrão, o campo de dados é exibido de acordo com
os formatos padrão com base nas CultureInfo do servidor.
O atributo DisplayFormat é usado para especificar explicitamente o formato de data:

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

A configuração ApplyFormatInEditMode especifica que a formatação também deve ser aplicada à interface do
usuário de edição. Alguns campos não devem usar ApplyFormatInEditMode . Por exemplo, o símbolo de moeda
geralmente não deve ser exibido em uma caixa de texto de edição.
O atributo DisplayFormat pode ser usado por si só. Geralmente, é uma boa ideia usar o atributo DataType com o
atributo DisplayFormat . O atributo DataType transmite a semântica dos dados em vez de como renderizá-los em
uma tela. O atributo DataType oferece os seguintes benefícios que não estão disponíveis em DisplayFormat :
O navegador pode habilitar recursos do HTML5. Por exemplo, mostra um controle de calendário, o símbolo de
moeda apropriado à localidade, links de email, validação de entrada do lado do cliente, etc.
Por padrão, o navegador renderiza os dados usando o formato correto de acordo com a localidade.
Para obter mais informações, consulte a documentação do Auxiliar de Marcação <input>.
Execute o aplicativo. Navegue para a página Índice de Alunos. As horas não são mais exibidas. Cada exibição que
usa o modelo Student exibe a data sem a hora.

O atributo StringLength
Regras de validação de dados e mensagens de erro de validação podem ser especificadas com atributos. O
atributo StringLength especifica o tamanho mínimo e máximo de caracteres permitidos em um campo de dados.
O atributo StringLength também fornece a validação do lado do cliente e do servidor. O valor mínimo não tem
impacto sobre o esquema de banco de dados.
Atualize o modelo Student com o seguinte código:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }

public ICollection<Enrollment> Enrollments { get; set; }


}
}

O código anterior limita os nomes a, no máximo, 50 caracteres. O atributo StringLength não impede que um
usuário insira um espaço em branco em um nome. O atributo RegularExpression é usado para aplicar restrições à
entrada. Por exemplo, o seguinte código exige que o primeiro caractere esteja em maiúscula e os caracteres
restantes estejam em ordem alfabética:

[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]

Execute o aplicativo:
Navegue para a página Alunos.
Selecione Criar Novo e insira um nome com mais de 50 caracteres.
Selecione Criar e a validação do lado do cliente mostrará uma mensagem de erro.

No SSOX (Pesquisador de Objetos do SQL Server), abra o designer de tabela Aluno clicando duas vezes na
tabela Aluno.
A imagem anterior mostra o esquema para a tabela Student . Os campos de nome têm o tipo nvarchar(MAX)
porque as migrações não foram executadas no BD. Quando as migrações forem executadas mais adiante neste
tutorial, os campos de nome se tornarão nvarchar(50) .
O atributo Column
Os atributos podem controlar como as classes e propriedades são mapeadas para o banco de dados. Nesta seção,
o atributo Column é usado para mapear o nome da propriedade FirstMidName como "FirstName" no BD.
Quando o BD é criado, os nomes de propriedade no modelo são usados para nomes de coluna (exceto quando o
atributo Column é usado).
O modelo Student usa FirstMidName para o campo de nome porque o campo também pode conter um
sobrenome.
Atualize o arquivo Student.cs com o seguinte código:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }

public ICollection<Enrollment> Enrollments { get; set; }


}
}

Com a alteração anterior, Student.FirstMidName no aplicativo é mapeado para a coluna FirstName da tabela
Student .
A adição do atributo Column altera o modelo que dá suporte ao SchoolContext . O modelo que dá suporte ao
SchoolContext não corresponde mais ao banco de dados. Se o aplicativo for executado antes da aplicação das
migrações, a seguinte exceção será gerada:

SqlException: Invalid column name 'FirstName'.

Para atualizar o BD:


Compile o projeto.
Abra uma janela Comando na pasta do projeto. Insira os seguintes comandos para criar uma nova
migração e atualizar o BD:

dotnet ef migrations add ColumnFirstName


dotnet ef database update

O comando dotnet ef migrations add ColumnFirstName gera a seguinte mensagem de aviso:

An operation was scaffolded that may result in the loss of data.


Please review the migration for accuracy.

O aviso é gerado porque os campos de nome agora estão limitados a 50 caracteres. Se um nome no BD tiver
mais de 50 caracteres, o 51º caractere até o último caractere serão perdidos.
Teste o aplicativo.
Abra a tabela Alunos no SSOX:

Antes de a migração ser aplicada, as colunas de nome eram do tipo nvarchar(MAX). As colunas de nome agora
são nvarchar(50) . O nome da coluna foi alterado de FirstMidName para FirstName .

OBSERVAÇÃO
Na seção a seguir, a criação do aplicativo em alguns estágios gera erros do compilador. As instruções especificam quando
compilar o aplicativo.
Atualização da entidade Student

Atualize Models/Student.cs com o seguinte código:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}

public ICollection<Enrollment> Enrollments { get; set; }


}
}

O atributo Required
O atributo Required torna as propriedades de nome campos obrigatórios. O atributo Required não é necessário
para tipos que não permitem valor nulo, como tipos de valor ( DateTime , int , double , etc.). Tipos que não
podem ser nulos são tratados automaticamente como campos obrigatórios.
O atributo Required pode ser substituído por um parâmetro de tamanho mínimo no atributo StringLength :

[Display(Name = "Last Name")]


[StringLength(50, MinimumLength=1)]
public string LastName { get; set; }
O atributo Display
O atributo Display especifica que a legenda para as caixas de texto deve ser "Nome", "Sobrenome", "Nome
Completo" e "Data de Registro". As legendas padrão não tinham nenhum espaço entre as palavras, por exemplo,
"Lastname".
A propriedade calculada FullName
FullName é uma propriedade calculada que retorna um valor criado pela concatenação de duas outras
propriedades. FullName não pode ser definido; ele apenas tem um acessador get. Nenhuma coluna FullName é
criada no banco de dados.

Criar a entidade Instructor

Crie Models/Instructor.cs com o seguinte código:


using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Instructor
{
public int ID { get; set; }

[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }

[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }

[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }

[Display(Name = "Full Name")]


public string FullName
{
get { return LastName + ", " + FirstMidName; }
}

public ICollection<CourseAssignment> CourseAssignments { get; set; }


public OfficeAssignment OfficeAssignment { get; set; }
}
}

Observe que várias propriedades são iguais nas entidades Student e Instructor . No tutorial Implementando a
herança mais adiante nesta série, esse código é refatorado para eliminar a redundância.
Vários atributos podem estar em uma linha. Os atributos HireDate podem ser escritos da seguinte maneira:

[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",


ApplyFormatInEditMode = true)]

As propriedades de navegação CourseAssignments e OfficeAssignment


As propriedades CourseAssignments e OfficeAssignment são propriedades de navegação.
Um instrutor pode ministrar qualquer quantidade de cursos e, portanto, CourseAssignments é definido como uma
coleção.

public ICollection<CourseAssignment> CourseAssignments { get; set; }

Se uma propriedade de navegação armazenar várias entidades:


Ele deve ser um tipo de lista no qual as entradas possam ser adicionadas, excluídas e atualizadas.
Os tipos de propriedade de navegação incluem:
ICollection<T>
List<T>
HashSet<T>

Se ICollection<T> for especificado, o EF Core criará uma coleção HashSet<T> por padrão.
A entidade CourseAssignment é explicada na seção sobre relações muitos para muitos.
Regras de negócio do Contoso University indicam que um instrutor pode ter, no máximo, um escritório. A
propriedade OfficeAssignment contém uma única entidade OfficeAssignment . OfficeAssignment será nulo se
nenhum escritório for atribuído.

public OfficeAssignment OfficeAssignment { get; set; }

Criar a entidade OfficeAssignment

Crie Models/OfficeAssignment.cs com o seguinte código:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class OfficeAssignment
{
[Key]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }

public Instructor Instructor { get; set; }


}
}

O atributo Key
O atributo [Key] é usado para identificar uma propriedade como a PK (chave primária) quando o nome da
propriedade é algo diferente de classnameID ou ID.
Há uma relação um para zero ou um entre as entidades Instructor e OfficeAssignment . Uma atribuição de
escritório existe apenas em relação ao instrutor ao qual ela é atribuída. A PK OfficeAssignment também é a FK
(chave estrangeira) da entidade Instructor . O EF Core não pode reconhecer InstructorID automaticamente
como o PK de OfficeAssignment porque:
InstructorID não segue a convenção de nomenclatura de ID nem de classnameID.

Portanto, o atributo Key é usado para identificar InstructorID como a PK:


[Key]
public int InstructorID { get; set; }

Por padrão, o EF Core trata a chave como não gerada pelo banco de dados porque a coluna destina-se a uma
relação de identificação.
A propriedade de navegação Instructor
A propriedade de navegação OfficeAssignment da entidade Instructor permite valor nulo porque:
Tipos de referência (como classes que permitem valor nulo).
Um instrutor pode não ter uma atribuição de escritório.
A entidade OfficeAssignment tem uma propriedade de navegação Instructor que não permite valor nulo
porque:
InstructorID não permite valor nulo.
Uma atribuição de escritório não pode existir sem um instrutor.
Quando uma entidade Instructor tem uma entidade OfficeAssignment relacionada, cada entidade tem uma
referência à outra em sua propriedade de navegação.
O atributo [Required] pode ser aplicado à propriedade de navegação Instructor :

[Required]
public Instructor Instructor { get; set; }

O código anterior especifica que deve haver um instrutor relacionado. O código anterior é desnecessário porque a
chave estrangeira InstructorID (que também é a PK) não permite valor nulo.

Modificar a entidade Course

Atualize Models/Course.cs com o seguinte código:


using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }

[StringLength(50, MinimumLength = 3)]


public string Title { get; set; }

[Range(0, 5)]
public int Credits { get; set; }

public int DepartmentID { get; set; }

public Department Department { get; set; }


public ICollection<Enrollment> Enrollments { get; set; }
public ICollection<CourseAssignment> CourseAssignments { get; set; }
}
}

A entidade Course tem uma propriedade de FK (chave estrangeira) DepartmentID . DepartmentID aponta para a
entidade Department relacionada. A entidade Course tem uma propriedade de navegação Department .
O EF Core não exige uma propriedade de FK para um modelo de dados quando o modelo tem uma propriedade
de navegação para uma entidade relacionada.
O EF Core cria automaticamente FKs no banco de dados sempre que forem necessárias. O EF Core cria
propriedades de sombra para FKs criadas automaticamente. Ter a FK no modelo de dados pode tornar as
atualizações mais simples e mais eficientes. Por exemplo, considere um modelo em que a propriedade de FK
DepartmentID não é incluída. Quando uma entidade de curso é buscada para editar:

A entidade Department será nula se não for carregada de forma explícita.


Para atualizar a entidade de curso, a entidade Department primeiro deve ser buscada.
Quando a propriedade de FK DepartmentID está incluída no modelo de dados, não é necessário buscar a entidade
Department antes de uma atualização.

O atributo DatabaseGenerated
O atributo [DatabaseGenerated(DatabaseGeneratedOption.None)] especifica que a PK é fornecida pelo aplicativo em
vez de ser gerada pelo banco de dados.

[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }

Por padrão, o EF Core supõe que os valores de PK sejam gerados pelo BD. Os valores de PK gerados pelo BD
geralmente são a melhor abordagem. Para entidades Course , o usuário especifica o PK. Por exemplo, um número
de curso, como uma série 1000 para o departamento de matemática e uma série 2000 para o departamento em
inglês.
O atributo DatabaseGenerated também pode ser usado para gerar valores padrão. Por exemplo, o BD pode gerar
automaticamente um campo de data para registrar a data em que uma linha foi criada ou atualizada. Para obter
mais informações, consulte Propriedades geradas.
Propriedades de navegação e de chave estrangeira
As propriedades de navegação e de FK (chave estrangeira) na entidade Course refletem as seguintes relações:
Um curso é atribuído a um departamento; portanto, há uma FK DepartmentID e uma propriedade de navegação
Department .

public int DepartmentID { get; set; }


public Department Department { get; set; }

Um curso pode ter qualquer quantidade de estudantes inscritos; portanto, a propriedade de navegação
Enrollments é uma coleção:

public ICollection<Enrollment> Enrollments { get; set; }

Um curso pode ser ministrado por vários instrutores; portanto, a propriedade de navegação CourseAssignments é
uma coleção:

public ICollection<CourseAssignment> CourseAssignments { get; set; }

CourseAssignment é explicado posteriormente.

Criar a entidade Department

Crie Models/Department.cs com o seguinte código:


using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }

[StringLength(50, MinimumLength = 3)]


public string Name { get; set; }

[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }

[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }

public int? InstructorID { get; set; }

public Instructor Administrator { get; set; }


public ICollection<Course> Courses { get; set; }
}
}

O atributo Column
Anteriormente, o atributo Column foi usado para alterar o mapeamento de nome de coluna. No código da
entidade Department , o atributo Column é usado para alterar o mapeamento de tipo de dados SQL. A coluna
Budget é definida usando o tipo de dinheiro do SQL Server no BD:

[Column(TypeName="money")]
public decimal Budget { get; set; }

Em geral, o mapeamento de coluna não é necessário. Em geral, o EF Core escolhe o tipo de dados do SQL Server
apropriado com base no tipo CLR da propriedade. O tipo decimal CLR é mapeado para um tipo decimal SQL
Server. Budget refere-se à moeda e o tipo de dados de dinheiro é mais apropriado para moeda.
Propriedades de navegação e de chave estrangeira
As propriedades de navegação e de FK refletem as seguintes relações:
Um departamento pode ou não ter um administrador.
Um administrador é sempre um instrutor. Portanto, a propriedade InstructorID está incluída como a FK da
entidade Instructor .
A propriedade de navegação é chamada Administrator , mas contém uma entidade Instructor :

public int? InstructorID { get; set; }


public Instructor Administrator { get; set; }

O ponto de interrogação (?) no código anterior especifica que a propriedade permite valor nulo.
Um departamento pode ter vários cursos e, portanto, há uma propriedade de navegação Courses:
public ICollection<Course> Courses { get; set; }

Observação: por convenção, o EF Core habilita a exclusão em cascata em FKs que não permitem valor nulo e em
relações muitos para muitos. A exclusão em cascata pode resultar em regras de exclusão em cascata circular. As
regras de exclusão em cascata circular causam uma exceção quando uma migração é adicionada.
Por exemplo, se a propriedade Department.InstructorID não foi definida como uma propriedade que permite
valor nulo:
O EF Core configura uma regra de exclusão em cascata para excluir o instrutor quando o departamento é
excluído.
A exclusão do instrutor quando o departamento é excluído não é o comportamento pretendido.
Se as regras de negócio exigirem que a propriedade InstructorID não permita valor nulo, use a seguinte
instrução da API fluente:

modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)

O código anterior desabilita a exclusão em cascata na relação departamento-instrutor.

Atualizar a entidade Enrollment


Um registro refere-se a um curso feito por um aluno.

Atualize Models/Enrollment.cs com o seguinte código:


using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}

public class Enrollment


{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
[DisplayFormat(NullDisplayText = "No grade")]
public Grade? Grade { get; set; }

public Course Course { get; set; }


public Student Student { get; set; }
}
}

Propriedades de navegação e de chave estrangeira


As propriedades de navegação e de FK refletem as seguintes relações:
Um registro destina-se a um curso e, portanto, há uma propriedade de FK CourseID e uma propriedade de
navegação Course :

public int CourseID { get; set; }


public Course Course { get; set; }

Um registro destina-se a um aluno e, portanto, há uma propriedade de FK StudentID e uma propriedade de


navegação Student :

public int StudentID { get; set; }


public Student Student { get; set; }

Relações muitos para muitos


Há uma relação muitos para muitos entre as entidades Student e Course . A entidade Enrollment funciona como
uma tabela de junção muitos para muitos com conteúdo no banco de dados. "Com conteúdo" significa que a
tabela Enrollment contém dados adicionais além das FKs das tabelas unidas (nesse caso, a FK e Grade ).
A ilustração a seguir mostra a aparência dessas relações em um diagrama de entidades. (Esse diagrama foi
gerado com o EF Power Tools para EF 6.x. A criação do diagrama não faz parte do tutorial.)
Cada linha de relação tem um 1 em uma extremidade e um asterisco (*) na outra, indicando uma relação um para
muitos.
Se a tabela Enrollment não incluir informações de nota, ela apenas precisará conter as duas FKs ( CourseID e
StudentID ). Uma tabela de junção muitos para muitos sem conteúdo é às vezes chamada de PJT (uma tabela de
junção pura).
As entidades Instructor e Course têm uma relação muitos para muitos usando uma tabela de junção pura.
Observação: o EF 6.x é compatível com tabelas de junção implícita para relações muitos para muitos, ao contrário
do EF Core. Para obter mais informações, consulte Relações muitos para muitos no EF Core 2.0.

A entidade CourseAssignment

Crie Models/CourseAssignment.cs com o seguinte código:


using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class CourseAssignment
{
public int InstructorID { get; set; }
public int CourseID { get; set; }
public Instructor Instructor { get; set; }
public Course Course { get; set; }
}
}

Instrutor para Cursos

A relação muitos para muitos de Instrutor para Cursos:


Exige que uma tabela de junção seja representada por um conjunto de entidades.
É uma tabela de junção pura (tabela sem conteúdo).
É comum nomear uma entidade de junção EntityName1EntityName2 . Por exemplo, a tabela de junção Instrutor
para Cursos com esse padrão é CourseInstructor . No entanto, recomendamos que você use um nome que
descreve a relação.
Modelos de dados começam simples e aumentam. PJTs ( junções sem conteúdo) evoluem com frequência para
incluir o conteúdo. Começando com um nome descritivo de entidade, o nome não precisa ser alterado quando a
tabela de junção é alterada. O ideal é que a entidade de junção tenha seu próprio nome natural (possivelmente,
uma única palavra) no domínio de negócios. Por exemplo, Manuais e Clientes podem ser vinculados com uma
entidade de junção chamada Ratings. Para a relação muitos para muitos de Instrutor para Cursos,
CourseAssignment é preferível a CourseInstructor .
Chave composta
As FKs não permitem valor nulo. As duas FKs em CourseAssignment ( InstructorID e CourseID ) juntas
identificam exclusivamente cada linha da tabela CourseAssignment . CourseAssignment não exige um PK dedicado.
As propriedades InstructorID e CourseID funcionam como uma PK composta. A única maneira de especificar
PKs compostas no EF Core é com a API fluente. A próxima seção mostra como configurar a PK composta.
A chave composta garante:
Várias linhas são permitidas para um curso.
Várias linhas são permitidas para um instrutor.
Não é permitido ter várias linhas para o mesmo instrutor e curso.
A entidade de junção Enrollment define sua própria PK e, portanto, duplicatas desse tipo são possíveis. Para
impedir duplicatas como essas:
Adicione um índice exclusivo nos campos de FK ou
Configure Enrollment com uma chave primária composta semelhante a CourseAssignment . Para obter mais
informações, consulte Índices.

Atualizar o contexto de BD
Adicione o seguinte código realçado a Data/SchoolContext.cs:

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}

public DbSet<Course> Courses { get; set; }


public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
public DbSet<CourseAssignment> CourseAssignments { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
modelBuilder.Entity<Department>().ToTable("Department");
modelBuilder.Entity<Instructor>().ToTable("Instructor");
modelBuilder.Entity<OfficeAssignment>().ToTable("OfficeAssignment");
modelBuilder.Entity<CourseAssignment>().ToTable("CourseAssignment");

modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
}
}
}

O código anterior adiciona novas entidades e configura a PK composta da entidade CourseAssignment .


Alternativa de API fluente para atributos
O método OnModelCreating no código anterior usa a API fluente para configurar o comportamento do EF Core. A
API é chamada "fluente" porque geralmente é usada pelo encadeamento de uma série de chamadas de método
em uma única instrução. O seguinte código é um exemplo da API fluente:

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}

Neste tutorial, a API fluente é usada apenas para o mapeamento do BD que não pode ser feito com atributos. No
entanto, a API fluente pode especificar a maioria das regras de formatação, validação e mapeamento que pode ser
feita com atributos.
Alguns atributos como MinimumLength não podem ser aplicados com a API fluente. MinimumLength não altera o
esquema; apenas aplica uma regra de validação de tamanho mínimo.
Alguns desenvolvedores preferem usar a API fluente exclusivamente para que possam manter suas classes de
entidade "limpas". Atributos e a API fluente podem ser combinados. Há algumas configurações que apenas
podem ser feitas com a API fluente (especificando uma PK composta). Há algumas configurações que apenas
podem ser feitas com atributos ( MinimumLength ). A prática recomendada para uso de atributos ou da API fluente:
Escolha uma dessas duas abordagens.
Use a abordagem escolhida da forma mais consistente possível.
Alguns dos atributos usados neste tutorial são usados para:
Somente validação (por exemplo, MinimumLength ).
Apenas configuração do EF Core (por exemplo, HasKey ).
Validação e configuração do EF Core (por exemplo, [StringLength(50)] ).
Para obter mais informações sobre atributos vs. API fluente, consulte Métodos de configuração.

Diagrama de entidade mostrando relações


A ilustração a seguir mostra o diagrama criado pelo EF Power Tools para o modelo Escola concluído.
O diagrama anterior mostra:
Várias linhas de relação um-para-muitos (1 para *).
A linha de relação um para zero ou um (1 para 0..1) entre as entidades Instructor e OfficeAssignment .
A linha de relação zero-ou-um-para-muitos (0..1 para *) entre as entidades Instructor e Department .

Propagar o BD com os dados de teste


Atualize o código em Data/DbInitializer.cs:

using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
//context.Database.EnsureCreated();
// Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
}

var students = new Student[]


{
new Student { FirstMidName = "Carson", LastName = "Alexander",
EnrollmentDate = DateTime.Parse("2010-09-01") },
new Student { FirstMidName = "Meredith", LastName = "Alonso",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Arturo", LastName = "Anand",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Gytis", LastName = "Barzdukas",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Yan", LastName = "Li",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Peggy", LastName = "Justice",
EnrollmentDate = DateTime.Parse("2011-09-01") },
new Student { FirstMidName = "Laura", LastName = "Norman",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Nino", LastName = "Olivetto",
EnrollmentDate = DateTime.Parse("2005-09-01") }
};

foreach (Student s in students)


{
context.Students.Add(s);
}
context.SaveChanges();

var instructors = new Instructor[]


{
new Instructor { FirstMidName = "Kim", LastName = "Abercrombie",
HireDate = DateTime.Parse("1995-03-11") },
new Instructor { FirstMidName = "Fadi", LastName = "Fakhouri",
HireDate = DateTime.Parse("2002-07-06") },
new Instructor { FirstMidName = "Roger", LastName = "Harui",
HireDate = DateTime.Parse("1998-07-01") },
new Instructor { FirstMidName = "Candace", LastName = "Kapoor",
HireDate = DateTime.Parse("2001-01-15") },
new Instructor { FirstMidName = "Roger", LastName = "Zheng",
HireDate = DateTime.Parse("2004-02-12") }
};

foreach (Instructor i in instructors)


{
context.Instructors.Add(i);
}
context.SaveChanges();

var departments = new Department[]


{
new Department { Name = "English", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Abercrombie").ID },
new Department { Name = "Mathematics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID },
new Department { Name = "Engineering", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Harui").ID },
new Department { Name = "Economics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID }
};

foreach (Department d in departments)


foreach (Department d in departments)
{
context.Departments.Add(d);
}
context.SaveChanges();

var courses = new Course[]


{
new Course {CourseID = 1050, Title = "Chemistry", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID
},
new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
},
new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
},
new Course {CourseID = 1045, Title = "Calculus", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
},
new Course {CourseID = 3141, Title = "Trigonometry", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
},
new Course {CourseID = 2021, Title = "Composition", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
},
new Course {CourseID = 2042, Title = "Literature", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
},
};

foreach (Course c in courses)


{
context.Courses.Add(c);
}
context.SaveChanges();

var officeAssignments = new OfficeAssignment[]


{
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID,
Location = "Smith 17" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Harui").ID,
Location = "Gowan 27" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID,
Location = "Thompson 304" },
};

foreach (OfficeAssignment o in officeAssignments)


{
context.OfficeAssignments.Add(o);
}
context.SaveChanges();

var courseInstructors = new CourseAssignment[]


{
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Kapoor").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
},
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Fakhouri").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Literature" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
},
};

foreach (CourseAssignment ci in courseInstructors)


{
context.CourseAssignments.Add(ci);
}
context.SaveChanges();

var enrollments = new Enrollment[]


{
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
Grade = Grade.A
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
Grade = Grade.C
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
Grade = Grade.B
},
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Barzdukas").ID,
CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Li").ID,
CourseID = courses.Single(c => c.Title == "Composition").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Justice").ID,
CourseID = courses.Single(c => c.Title == "Literature").CourseID,
Grade = Grade.B
}
};

foreach (Enrollment e in enrollments)


{
var enrollmentInDataBase = context.Enrollments.Where(
s =>
s.Student.ID == e.StudentID &&
s.Course.CourseID == e.CourseID).SingleOrDefault();
if (enrollmentInDataBase == null)
{
context.Enrollments.Add(e);
}
}
context.SaveChanges();
}
}
}

O código anterior fornece dados de semente para as novas entidades. A maioria desse código cria novos objetos
de entidade e carrega dados de exemplo. Os dados de exemplo são usados para teste. O código anterior cria as
seguintes relações muitos para muitos:
Enrollments
CourseAssignment

Observação: o EF Core 2.1 dará suporte à propagação de dados.

Adicionar uma migração


Compile o projeto. Abra uma janela Comando na pasta do projeto e insira o seguinte comando:

dotnet ef migrations add ComplexDataModel

O comando anterior exibe um aviso sobre a possível perda de dados.

An operation was scaffolded that may result in the loss of data.


Please review the migration for accuracy.
Done. To undo this action, use 'ef migrations remove'

Se o comando database update é executado, o seguinte erro é produzido:

The ALTER TABLE statement conflicted with the FOREIGN KEY constraint
"FK_dbo.Course_dbo.Department_DepartmentID". The conflict occurred in
database "ContosoUniversity", table "dbo.Department", column 'DepartmentID'.
Quando as migrações são executadas com os dados existentes, pode haver restrições de FK que não são
atendidas com os dados existentes. Para este tutorial, um novo BD é criado e, portanto, não há nenhuma violação
de restrição de FK. Consulte Corrigindo restrições de chave estrangeira com os dados herdados para obter
instruções sobre como corrigir as violações de FK no BD atual.

Alterar a cadeia de conexão e atualizar o BD


O código no DbInitializer atualizado adiciona dados de semente às novas entidades. Para forçar o EF Core a
criar um novo BD vazio:
Altere o nome de cadeia de conexão do BD no appsettings.json para ContosoUniversity3. O novo nome
deve ser um nome que ainda não foi usado no computador.

{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity3;Trusted_Connection=True;MultipleActiveResultSets=tr
ue"
},

Como alternativa, exclua o BD usando:


SSOX (Pesquisador de Objetos do SQL Server).
O comando database drop da CLI:

dotnet ef database drop

Execute database update na janela Comando:

dotnet ef database update

O comando anterior executa todas as migrações.


Execute o aplicativo. A execução do aplicativo executa o método DbInitializer.Initialize .O
DbInitializer.Initialize popula o novo BD.

Abra o BD no SSOX:
Expanda o nó Tabelas. As tabelas criadas são exibidas.
Se o SSOX for aberto anteriormente, clique no botão Atualizar.
Examine a tabela CourseAssignment:
Clique com o botão direito do mouse na tabela CourseAssignment e selecione Exibir Dados.
Verifique se a tabela CourseAssignment contém dados.

Corrigindo restrições de chave estrangeira com os dados herdados


Esta seção é opcional.
Quando as migrações são executadas com os dados existentes, pode haver restrições de FK que não são
atendidas com os dados existentes. Com os dados de produção, é necessário executar etapas para migrar os
dados existentes. Esta seção fornece um exemplo de correção de violações de restrição de FK. Não faça essas
alterações de código sem um backup. Não faça essas alterações de código se você concluir a seção anterior e
atualizou o banco de dados.
O arquivo {timestamp }_ComplexDataModel.cs contém o seguinte código:

migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
type: "int",
nullable: false,
defaultValue: 0);

O código anterior adiciona uma FK DepartmentID que não permite valor nulo à tabela Course . O BD do tutorial
anterior contém linhas em Course e, portanto, essa tabela não pode ser atualizada por migrações.
Para fazer a migração ComplexDataModel funcionar com os dados existentes:
Altere o código para dar à nova coluna ( DepartmentID ) um valor padrão.
Crie um departamento fictício chamado "Temp" para atuar como o departamento padrão.
Corrigir as restrições de chave estrangeira
Atualize o método Up das classes ComplexDataModel :
Abra o arquivo {timestamp }_ComplexDataModel.cs.
Comente a linha de código que adiciona a coluna DepartmentID à tabela Course .

migrationBuilder.AlterColumn<string>(
name: "Title",
table: "Course",
maxLength: 50,
nullable: true,
oldClrType: typeof(string),
oldNullable: true);

//migrationBuilder.AddColumn<int>(
// name: "DepartmentID",
// table: "Course",
// nullable: false,
// defaultValue: 0);

Adicione o código realçado a seguir. O novo código é inserido após o bloco .CreateTable( name: "Department" :
[!code-csharpMain]
Com as alterações anteriores, as linhas Course existentes estarão relacionadas ao departamento "Temp" após a
execução do método ComplexDataModel Up .
Um aplicativo de produção:
Inclui código ou scripts para adicionar linhas Department e linhas Course relacionadas às novas linhas
Department .
Não usa o departamento "Temp" nem o valor padrão para Course.DepartmentID .

O próximo tutorial abrange os dados relacionados.

Anterior Próximo
Lendo dados relacionados – EF Core com Páginas
do Razor (6 de 8)
14/02/2018 • 24 min to read • Edit Online

Por Tom Dykstra, Jon P Smith e Rick Anderson


O aplicativo Web Contoso University demonstra como criar aplicativos Web das Páginas do Razor usando o EF
Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o primeiro tutorial.
Neste tutorial, os dados relacionados são lidos e exibidos. Dados relacionados são dados que o EF Core carrega
nas propriedades de navegação.
Caso tenha problemas que não consiga resolver, baixe o aplicativo concluído para este estágio.
As seguintes ilustrações mostram as páginas concluídas para este tutorial:
Carregamento adiantado, explícito e lento de dados relacionados
Há várias maneiras pelas quais o EF Core pode carregar dados relacionados nas propriedades de navegação de
uma entidade:
Carregamento adiantado. O carregamento adiantado é quando uma consulta para um tipo de entidade
também carrega entidades relacionadas. Quando a entidade é lida, seus dados relacionados são recuperados.
Normalmente, isso resulta em uma única consulta de junção que recupera todos os dados necessários. O EF
Core emitirá várias consultas para alguns tipos de carregamento adiantado. A emissão de várias consultas
pode ser mais eficiente do que era o caso para algumas consultas no EF6 quando havia uma única consulta. O
carregamento adiantado é especificado com os métodos Include e ThenInclude .

O carregamento adiantado envia várias consultas quando a navegação de uma coleção é incluída:
Uma consulta para a consulta principal
Uma consulta para cada "borda" de coleção na árvore de carregamento.
Separe consultas com Load : os dados podem ser recuperados em consultas separadas e o EF Core
"corrige" as propriedades de navegação. "Correção" significa que o EF Core popula automaticamente as
propriedades de navegação. A separação de consultas com Load é mais parecida com o carregamento
explícito do que com o carregamento adiantado.

Observação: o EF Core corrige automaticamente as propriedades de navegação para outras entidades que foram
carregadas anteriormente na instância do contexto. Mesmo se os dados de uma propriedade de navegação não
foram incluídos de forma explícita, a propriedade ainda pode ser populada se algumas ou todas as entidades
relacionadas foram carregadas anteriormente.
Carregamento explícito. Quando a entidade é lida pela primeira vez, os dados relacionados não são
recuperados. Um código precisa ser escrito para recuperar os dados relacionados quando eles forem
necessários. O carregamento explícito com consultas separadas resulta no envio de várias consultas ao BD.
Com o carregamento explícito, o código especifica as propriedades de navegação a serem carregadas. Use o
método Load para fazer o carregamento explícito. Por exemplo:

Carregamento lento. No momento, o EF Core não dá suporte ao carregamento lento. Quando a entidade é
lida pela primeira vez, os dados relacionados não são recuperados. Na primeira vez que uma propriedade
de navegação é acessada, os dados necessários para essa propriedade de navegação são recuperados
automaticamente. Uma consulta é enviada para o BD sempre que uma propriedade de navegação é
acessada pela primeira vez.
O operador Select carrega somente os dados relacionados necessários.

Criar uma página Courses que exibe o nome do departamento


A entidade Course inclui uma propriedade de navegação que contém a entidade Department . A entidade
Department contém o departamento ao qual o curso é atribuído.

Para exibir o nome do departamento atribuído em uma lista de cursos:


Obtenha a propriedade Name da entidade Department .
A entidade Department é obtida da propriedade de navegação Course.Department .

Gerar o modelo Curso por scaffolding


Saia do Visual Studio.
Abra uma janela de comando no diretório do projeto (o diretório que contém os arquivos Program.cs,
Startup.cs e .csproj).
Execute o seguinte comando:

dotnet aspnet-codegenerator razorpage -m Course -dc SchoolContext -udl -outDir Pages\Courses --


referenceScriptLibraries

O comando anterior gera o modelo Course por scaffolding. Abra o projeto no Visual Studio.
Compile o projeto. O build gera erros, como o seguinte:
1>Pages/Courses/Index.cshtml.cs(26,37,26,43): error CS1061: 'SchoolContext' does not contain a definition for
'Course' and no extension method 'Course' accepting a first argument of type 'SchoolContext' could be found
(are you missing a using directive or an assembly reference?)

Altere _context.Course globalmente para _context.Courses (ou seja, adicione um "s" a Course ). 7 ocorrências
foram encontradas e atualizadas.
Abra Pages/Courses/Index.cshtml.cs e examine o método OnGetAsync . O mecanismo de scaffolding especificou o
carregamento adiantado para a propriedade de navegação Department . O método Include especifica o
carregamento adiantado.
Execute o aplicativo e selecione o link Cursos. A coluna de departamento exibe a DepartmentID , que não é útil.
Atualize o método OnGetAsync pelo seguinte código:

public async Task OnGetAsync()


{
Course = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.ToListAsync();
}

O código anterior adiciona AsNoTracking . AsNoTracking melhora o desempenho porque as entidades retornadas
não são controladas. As entidades não são controladas porque elas não são atualizadas no contexto atual.
Atualize Views/Courses/Index.cshtml com a seguinte marcação realçada:
@page
@model ContosoUniversity.Pages.Courses.IndexModel
@{
ViewData["Title"] = "Courses";
}

<h2>Courses</h2>

<p>
<a asp-page="TestCreate">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Course[0].CourseID)
</th>
<th>
@Html.DisplayNameFor(model => model.Course[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Course[0].Credits)
</th>
<th>
@Html.DisplayNameFor(model => model.Course[0].Department)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Course)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.CourseID)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Credits)
</td>
<td>
@Html.DisplayFor(modelItem => item.Department.Name)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.CourseID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.CourseID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.CourseID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

As seguintes alterações foram feitas na biblioteca gerada por código em scaffolding:


Alterou o cabeçalho de Índice para Cursos.
Adicionou uma coluna Número que mostra o valor da propriedade CourseID . Por padrão, as chaves
primárias não são geradas por scaffolding porque normalmente não têm sentido para os usuários finais.
No entanto, nesse caso, a chave primária é significativa.
Alterou a coluna Departamento para que ela exiba o nome de departamento. O código exibe a
propriedade Name da entidade Department que é carregada na propriedade de navegação Department :
@Html.DisplayFor(modelItem => item.Department.Name)

Execute o aplicativo e selecione a guia Cursos para ver a lista com nomes de departamentos.

Carregando dados relacionados com Select


O método OnGetAsync carrega dados relacionados com o método Include :

public async Task OnGetAsync()


{
Course = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.ToListAsync();
}

O operador Select carrega somente os dados relacionados necessários. Para itens únicos, como o
Department.Name , ele usa um SQL INNER JOIN. Para coleções, ele usa outro acesso de banco de dados, assim
como o operador Include em coleções.
O seguinte código carrega dados relacionados com o método Select :

public IList<CourseViewModel> CourseVM { get; set; }

public async Task OnGetAsync()


{
CourseVM = await _context.Courses
.Select(p => new CourseViewModel
{
CourseID = p.CourseID,
Title = p.Title,
Credits = p.Credits,
DepartmentName = p.Department.Name
}).ToListAsync();
}

O CourseViewModel :
public class CourseViewModel
{
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public string DepartmentName { get; set; }
}

Consulte IndexSelect.cshtml e IndexSelect.cshtml.cs para obter um exemplo completo.

Criar uma página Instrutores que mostra Cursos e Registros


Nesta seção, a página Instrutores é criada.
Essa página lê e exibe dados relacionados das seguintes maneiras:
A lista de instrutores exibe dados relacionados da entidade OfficeAssignment (Office na imagem anterior). As
entidades Instructor e OfficeAssignment estão em uma relação um para zero ou um. O carregamento
adiantado é usado para as entidades OfficeAssignment . O carregamento adiantado costuma ser mais eficiente
quando os dados relacionados precisam ser exibidos. Nesse caso, as atribuições de escritório para os
instrutores são exibidas.
Quando o usuário seleciona um instrutor (Pedro na imagem anterior), as entidades Course relacionadas são
exibidas. As entidades Instructor e Course estão em uma relação muitos para muitos. O carregamento
adiantado para as entidades Course e suas entidades Department relacionadas é usado. Nesse caso, consultas
separadas podem ser mais eficientes porque somente os cursos para o instrutor selecionado são necessários.
Este exemplo mostra como usar o carregamento adiantado para propriedades de navegação em entidades que
estão nas propriedades de navegação.
Quando o usuário seleciona um curso (Química na imagem anterior), os dados relacionados da entidade
Enrollments são exibidos. Na imagem anterior, o nome do aluno e a nota são exibidos. As entidades Course e
Enrollment estão em uma relação um -para-muitos.

Criar um modelo de exibição para a exibição Índice de Instrutor


A página Instrutores mostra dados de três tabelas diferentes. É criado um modelo de exibição que inclui as três
entidades que representam as três tabelas.
Na pasta SchoolViewModels, crie InstructorIndexData.cs com o seguinte código:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Models.SchoolViewModels
{
public class InstructorIndexData
{
public IEnumerable<Instructor> Instructors { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<Enrollment> Enrollments { get; set; }
}
}

Gerar o modelo Instrutor por scaffolding


Saia do Visual Studio.
Abra uma janela de comando no diretório do projeto (o diretório que contém os arquivos Program.cs,
Startup.cs e .csproj).
Execute o seguinte comando:

dotnet aspnet-codegenerator razorpage -m Instructor -dc SchoolContext -udl -outDir Pages\Instructors --


referenceScriptLibraries

O comando anterior gera o modelo Instructor por scaffolding. Abra o projeto no Visual Studio.
Compile o projeto. O build gera erros.
Altere _context.Instructor globalmente para _context.Instructors (ou seja, adicione um "s" a Instructor ). 7
ocorrências foram encontradas e atualizadas.
Execute o aplicativo e navegue para a página Instrutores.
Substitua Pages/Instructors/Index.cshtml.cs pelo seguinte código:
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels; // Add VM
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public IndexModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

public InstructorIndexData Instructor { get; set; }


public int InstructorID { get; set; }

public async Task OnGetAsync(int? id)


{
Instructor = new InstructorIndexData();
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();

if (id != null)
{
InstructorID = id.Value;
}
}
}
}

O método OnGetAsync aceita dados de rota opcionais para a ID do instrutor selecionado.


Examine a consulta na página Pages/Instructors/Index.cshtml:

Instructor.Instructors = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();

A consulta tem duas inclusões:


OfficeAssignment : exibido na exibição de instrutores.
CourseAssignments : que exibe os cursos ministrados.

Atualizar a página Índice de instrutores


Atualize Pages/Instructors/Index.cshtml com a seguinte marcação:
@page "{id:int?}"
@model ContosoUniversity.Pages.Instructors.IndexModel

@{
ViewData["Title"] = "Instructors";
}

<h2>Instructors</h2>

<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Instructor.Instructors)
{
string selectedRow = "";
if (item.ID == Model.InstructorID)
{
selectedRow = "success";
}
<tr class="@selectedRow">
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.HireDate)
</td>
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
<td>
@{
foreach (var course in item.CourseAssignments)
{
@course.Course.CourseID @: @course.Course.Title <br />
}
}
</td>
<td>
<a asp-page="./Index" asp-route-id="@item.ID">Select</a> |
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
A marcação anterior faz as seguintes alterações:
Atualiza a diretiva page de @page para @page "{id:int?}" . "{id:int?}" é um modelo de rota. O modelo
de rota altera cadeias de consulta de inteiro na URL para dados de rota. Por exemplo, clicar no link
Selecionar para o instrutor quando a diretiva de página produz uma URL semelhante à seguinte:
http://localhost:1234/Instructors?id=2

Quando a diretiva de página é @page "{id:int?}" , a URL anterior é:


http://localhost:1234/Instructors/2

O título de página é Instrutores.


Adicionou uma coluna Office que exibe item.OfficeAssignment.Location somente se
item.OfficeAssignment não é nulo. Como essa é uma relação um para zero ou um, pode não haver uma
entidade OfficeAssignment relacionada.

@if (item.OfficeAssignment != null)


{
@item.OfficeAssignment.Location
}

Adicionou uma coluna Courses que exibe os cursos ministrados por cada instrutor. Consulte Transição de
linha explícita com @: para obter mais informações sobre essa sintaxe Razor.
Adicionou um código que adiciona class="success" dinamicamente ao elemento tr do instrutor
selecionado. Isso define uma cor da tela de fundo para a linha selecionada usando uma classe Bootstrap.

string selectedRow = "";


if (item.CourseID == Model.CourseID)
{
selectedRow = "success";
}
<tr class="@selectedRow">

Adicionou um novo hiperlink rotulado Selecionar. Este link envia a ID do instrutor selecionado para o
método Index e define uma cor da tela de fundo.

<a asp-action="Index" asp-route-id="@item.ID">Select</a> |

Execute o aplicativo e selecione a guia Instrutores. A página exibe o Location (escritório) da entidade
OfficeAssignment relacionada. Se OfficeAssignment é nulo, uma célula de tabela vazia é exibida.
Clique no link Selecionar. O estilo de linha é alterado.
Adicionar cursos ministrados pelo instrutor selecionado
Atualize o método OnGetAsync em Pages/Instructors/Index.cshtml.cs com o seguinte código:

public async Task OnGetAsync(int? id, int? courseID)


{
Instructor = new InstructorIndexData();
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();

if (id != null)
{
InstructorID = id.Value;
Instructor instructor = Instructor.Instructors.Where(
i => i.ID == id.Value).Single();
Instructor.Courses = instructor.CourseAssignments.Select(s => s.Course);
}

if (courseID != null)
{
CourseID = courseID.Value;
Instructor.Enrollments = Instructor.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
}

Examine a consulta atualizada:


Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();

A consulta anterior adiciona as entidades Department .


O código a seguir é executado quando o instrutor é selecionado ( id != null ). O instrutor selecionado é
recuperado da lista de instrutores no modelo de exibição. Em seguida, a propriedade Courses do modelo de
exibição é carregada com as entidades Course da propriedade de navegação CourseAssignments desse instrutor.

if (id != null)
{
InstructorID = id.Value;
Instructor instructor = Instructor.Instructors.Where(
i => i.ID == id.Value).Single();
Instructor.Courses = instructor.CourseAssignments.Select(s => s.Course);
}

O método Where retorna uma coleção. No método Where anterior, uma única entidade Instructor é retornada.
O método Single converte a coleção em uma única entidade Instructor . A entidade Instructor fornece acesso
à propriedade CourseAssignments . CourseAssignments fornece acesso às entidades Course relacionadas.

O método Single é usado em uma coleção quando a coleção tem apenas um item. O método Single gera uma
exceção se a coleção está vazia ou se há mais de um item. Uma alternativa é SingleOrDefault , que retorna um
valor padrão (nulo, nesse caso) se a coleção está vazia. O uso de SingleOrDefault é uma coleção vazia:
Resulta em uma exceção (da tentativa de encontrar uma propriedade Courses em uma referência nula).
A mensagem de exceção indica menos claramente a causa do problema.
O seguinte código popula a propriedade Enrollments do modelo de exibição quando um curso é selecionado:

if (courseID != null)
{
CourseID = courseID.Value;
Instructor.Enrollments = Instructor.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}

Adicione a seguinte marcação ao final da Página do Razor Pages/Courses/Index.cshtml:

<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>


</td>
</tr>
}
</tbody>
</table>

@if (Model.Instructor.Courses != null)


{
<h3>Courses Taught by Selected Instructor</h3>
<table class="table">
<tr>
<th></th>
<th>Number</th>
<th>Title</th>
<th>Department</th>
</tr>

@foreach (var item in Model.Instructor.Courses)


{
string selectedRow = "";
if (item.CourseID == Model.CourseID)
{
selectedRow = "success";
}
<tr class="@selectedRow">
<td>
@Html.ActionLink("Select", "OnGetAsync",
new { courseID = item.CourseID })
</td>
<td>
@item.CourseID
</td>
<td>
@item.Title
</td> <td>
@item.Department.Name
</td>
</tr>
}

</table>
}

A marcação anterior exibe uma lista de cursos relacionados a um instrutor quando um instrutor é selecionado.
Teste o aplicativo. Clique em um link Selecionar na página Instrutores.
Mostrar dados de alunos
Nesta seção, o aplicativo é atualizado para mostrar os dados de alunos de um curso selecionado.
Atualize a consulta no método OnGetAsync em Pages/Instructors/Index.cshtml.cs com o seguinte código:

Instructor.Instructors = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();

Atualize Pages/Instructors/Index.cshtml. Adicione a seguinte marcação ao final do arquivo:


@if (Model.Instructor.Enrollments != null)
{
<h3>
Students Enrolled in Selected Course
</h3>
<table class="table">
<tr>
<th>Name</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Instructor.Enrollments)
{
<tr>
<td>
@item.Student.FullName
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
}

A marcação anterior exibe uma lista dos alunos registrados no curso selecionado.
Atualize a página e selecione um instrutor. Selecione um curso para ver a lista de alunos registrados e suas notas.
Usando Single
O método Single pode passar a condição Where em vez de chamar o método Where separadamente:
public async Task OnGetAsync(int? id, int? courseID)
{
Instructor = new InstructorIndexData();

Instructor.Instructors = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();

if (id != null)
{
InstructorID = id.Value;
Instructor instructor = Instructor.Instructors.Single(
i => i.ID == id.Value);
Instructor.Courses = instructor.CourseAssignments.Select(
s => s.Course);
}

if (courseID != null)
{
CourseID = courseID.Value;
Instructor.Enrollments = Instructor.Courses.Single(
x => x.CourseID == courseID).Enrollments;
}
}

A abordagem Single anterior não oferece nenhum benefício em relação ao uso de Where . Alguns
desenvolvedores preferem o estilo de abordagem Single .

Carregamento explícito
O código atual especifica o carregamento adiantado para Enrollments e Students :

Instructor.Instructors = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();

Suponha que os usuários raramente desejem ver registros em um curso. Nesse caso, uma otimização será
carregar apenas os dados de registro se eles forem solicitados. Nesta seção, o OnGetAsync é atualizado para usar
o carregamento explícito de Enrollments e Students .
Atualize o OnGetAsync com o seguinte código:
public async Task OnGetAsync(int? id, int? courseID)
{
Instructor = new InstructorIndexData();
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
//.Include(i => i.CourseAssignments)
// .ThenInclude(i => i.Course)
// .ThenInclude(i => i.Enrollments)
// .ThenInclude(i => i.Student)
// .AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();

if (id != null)
{
InstructorID = id.Value;
Instructor instructor = Instructor.Instructors.Where(
i => i.ID == id.Value).Single();
Instructor.Courses = instructor.CourseAssignments.Select(s => s.Course);
}

if (courseID != null)
{
CourseID = courseID.Value;
var selectedCourse = Instructor.Courses.Where(x => x.CourseID == courseID).Single();
await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
foreach (Enrollment enrollment in selectedCourse.Enrollments)
{
await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
}
Instructor.Enrollments = selectedCourse.Enrollments;
}
}

O código anterior remove as chamadas do método ThenInclude para dados de registro e de alunos. Se um curso
é selecionado, o código realçado recupera:
As entidades Enrollment para o curso selecionado.
As entidades Student para cada Enrollment .
Observe que o código anterior comenta .AsNoTracking() . As propriedades de navegação apenas podem ser
carregadas de forma explícita para entidades controladas.
Teste o aplicativo. De uma perspectiva dos usuários, o aplicativo se comporta de forma idêntica à versão anterior.
O próximo tutorial mostra como atualizar os dados relacionados.

A N T E R IO R P R Ó X IM O
Atualizando dados relacionados – Páginas do Razor
do EF Core (7 de 8)
14/02/2018 • 23 min to read • Edit Online

Por Tom Dykstra e Rick Anderson


O aplicativo Web Contoso University demonstra como criar aplicativos Web das Páginas do Razor usando o EF
Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o primeiro tutorial.
Este tutorial demonstra como atualizar dados relacionados. Caso tenha problemas que não consiga resolver, baixe
o aplicativo concluído para este estágio.
As ilustrações a seguir mostram algumas das páginas concluídas.
Examine e teste as páginas de cursos Criar e Editar. Crie um novo curso. O departamento é selecionado por sua
chave primária (um inteiro), não pelo nome. Edite o novo curso. Quando concluir o teste, exclua o novo curso.

Criar uma classe base para compartilhar um código comum


As páginas Cursos/Criar e Cursos/Editar precisam de uma lista de nomes de departamentos. Crie a classe base
Pages/Courses/DepartmentNamePageModel.cshtml.cs para as páginas Criar e Editar:
using ContosoUniversity.Data;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using System.Linq;

namespace ContosoUniversity.Pages.Courses
{
public class DepartmentNamePageModel : PageModel
{
public SelectList DepartmentNameSL { get; set; }

public void PopulateDepartmentsDropDownList(SchoolContext _context,


object selectedDepartment = null)
{
var departmentsQuery = from d in _context.Departments
orderby d.Name // Sort by name.
select d;

DepartmentNameSL = new SelectList(departmentsQuery.AsNoTracking(),


"DepartmentID", "Name", selectedDepartment);
}
}
}

O código anterior cria uma SelectList para conter a lista de nomes de departamentos. Se selectedDepartment for
especificado, esse departamento estará selecionado na SelectList .
As classes de modelo da página Criar e Editar serão derivadas de DepartmentNamePageModel .

Personalizar as páginas Courses


Quando uma nova entidade de curso é criada, ela precisa ter uma relação com um departamento existente. Para
adicionar um departamento durante a criação de um curso, a classe base para Create e Edit contém uma lista
suspensa para seleção do departamento. A lista suspensa define a propriedade Course.DepartmentID de FK (chave
estrangeira). O EF Core usa a Course.DepartmentID FK para carregar a propriedade de navegação Department .
Atualize o modelo da página Criar com o seguinte código:
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
public class CreateModel : DepartmentNamePageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public CreateModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

public IActionResult OnGet()


{
PopulateDepartmentsDropDownList(_context);
return Page();
}

[BindProperty]
public Course Course { get; set; }

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

var emptyCourse = new Course();

if (await TryUpdateModelAsync<Course>(
emptyCourse,
"course", // Prefix for form value.
s => s.CourseID, s => s.DepartmentID, s => s.Title, s => s.Credits))
{
_context.Courses.Add(emptyCourse);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}

// Select DepartmentID if TryUpdateModelAsync fails.


PopulateDepartmentsDropDownList(_context, emptyCourse.DepartmentID);
return Page();
}
}
}

O código anterior:
Deriva de DepartmentNamePageModel .
Usa TryUpdateModelAsync para impedir o excesso de postagem.
Substitui ViewData["DepartmentID"] por DepartmentNameSL (da classe base).

ViewData["DepartmentID"] é substituído pelo DepartmentNameSL fortemente tipado. Modelos fortemente tipados


são preferíveis aos fracamente tipados. Para obter mais informações, consulte Dados fracamente tipados
(ViewData e ViewBag).
Atualizar a página Criar Cursos
Atualize Pages/Courses/Create.cshtml com a seguinte marcação:
@page
@model ContosoUniversity.Pages.Courses.CreateModel
@{
ViewData["Title"] = "Create Course";
}
<h2>Create</h2>
<h4>Course</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Course.CourseID" class="control-label"></label>
<input asp-for="Course.CourseID" class="form-control" />
<span asp-validation-for="Course.CourseID" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Course.Title" class="control-label"></label>
<input asp-for="Course.Title" class="form-control" />
<span asp-validation-for="Course.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Course.Credits" class="control-label"></label>
<input asp-for="Course.Credits" class="form-control" />
<span asp-validation-for="Course.Credits" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Course.Department" class="control-label"></label>
<select asp-for="Course.DepartmentID" class="form-control"
asp-items="@Model.DepartmentNameSL">
<option value="">-- Select Department --</option>
</select>
<span asp-validation-for="Course.DepartmentID" class="text-danger" />
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

A marcação anterior faz as seguintes alterações:


Altera a legenda de DepartmentID para Departamento.
Substitui "ViewBag.DepartmentID" por DepartmentNameSL (da classe base).
Adiciona a opção "Selecionar Departamento". Essa alteração renderiza "Selecionar Departamento", em vez do
departamento primeiro.
Adiciona uma mensagem de validação quando o departamento não está selecionado.
A Página do Razor usa a opção Selecionar Auxiliar de Marcação:
<div class="form-group">
<label asp-for="Course.Department" class="control-label"></label>
<select asp-for="Course.DepartmentID" class="form-control"
asp-items="@Model.DepartmentNameSL">
<option value="">-- Select Department --</option>
</select>
<span asp-validation-for="Course.DepartmentID" class="text-danger" />
</div>

Teste a página Criar. A página Criar exibe o nome do departamento em vez de a ID do departamento.
Atualize a página Editar Cursos.
Atualize o modelo de página de edição com o seguinte código:
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
public class EditModel : DepartmentNamePageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public EditModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

[BindProperty]
public Course Course { get; set; }

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


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

Course = await _context.Courses


.Include(c => c.Department).FirstOrDefaultAsync(m => m.CourseID == id);

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

// Select current DepartmentID.


PopulateDepartmentsDropDownList(_context,Course.DepartmentID);
return Page();
}

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


{
if (!ModelState.IsValid)
{
return Page();
}

var courseToUpdate = await _context.Courses.FindAsync(id);

if (await TryUpdateModelAsync<Course>(
courseToUpdate,
"course", // Prefix for form value.
c => c.Credits, c => c.DepartmentID, c => c.Title))
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}

// Select DepartmentID if TryUpdateModelAsync fails.


PopulateDepartmentsDropDownList(_context, courseToUpdate.DepartmentID);
return Page();
}
}
}

As alterações são semelhantes às feitas no modelo da página Criar. No código anterior,


PopulateDepartmentsDropDownList passa a ID do departamento, que seleciona o departamento especificado na lista
suspensa.
Atualize Pages/Courses/Edit.cshtml com a seguinte marcação:

@page
@model ContosoUniversity.Pages.Courses.EditModel

@{
ViewData["Title"] = "Edit";
}

<h2>Edit</h2>

<h4>Course</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Course.CourseID" />
<div class="form-group">
<label asp-for="Course.CourseID" class="control-label"></label>
<div>@Html.DisplayFor(model => model.Course.CourseID)</div>
</div>
<div class="form-group">
<label asp-for="Course.Title" class="control-label"></label>
<input asp-for="Course.Title" class="form-control" />
<span asp-validation-for="Course.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Course.Credits" class="control-label"></label>
<input asp-for="Course.Credits" class="form-control" />
<span asp-validation-for="Course.Credits" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Course.Department" class="control-label"></label>
<select asp-for="Course.DepartmentID" class="form-control"
asp-items="@Model.DepartmentNameSL"></select>
<span asp-validation-for="Course.DepartmentID" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>
</div>
</div>

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

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

A marcação anterior faz as seguintes alterações:


Exibe a ID do curso. Geralmente, a PK (chave primária) de uma entidade não é exibida. Em geral, PKs não têm
sentido para os usuários. Nesse caso, o PK é o número do curso.
Altera a legenda de DepartmentID para Departamento.
Substitui "ViewBag.DepartmentID" por DepartmentNameSL (da classe base).
Adiciona a opção "Selecionar Departamento". Essa alteração renderiza "Selecionar Departamento", em vez do
departamento primeiro.
Adiciona uma mensagem de validação quando o departamento não está selecionado.
A página contém um campo oculto ( <input type="hidden"> ) para o número do curso. A adição de um auxiliar de
marcação <label> com asp-for="Course.CourseID" não elimina a necessidade do campo oculto.
<input type="hidden"> é necessário para que o número seja incluído nos dados postados quando o usuário clicar
em Salvar.
Teste o código atualizado. Crie, edite e exclua um curso.

Adicionar AsNoTracking aos modelos de página Detalhes e Excluir


AsNoTracking pode melhorar o desempenho quando o acompanhamento não é necessário. Adicione
AsNoTracking ao modelo de página Excluir e Detalhes. O seguinte código mostra o modelo de página Excluir
atualizado:
public class DeleteModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public DeleteModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

[BindProperty]
public Course Course { get; set; }

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


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

Course = await _context.Courses


.AsNoTracking()
.Include(c => c.Department)
.FirstOrDefaultAsync(m => m.CourseID == id);

if (Course == null)
{
return NotFound();
}
return Page();
}

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


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

Course = await _context.Courses


.AsNoTracking()
.FirstOrDefaultAsync(m => m.CourseID == id);

if (Course != null)
{
_context.Courses.Remove(Course);
await _context.SaveChangesAsync();
}

return RedirectToPage("./Index");
}
}

Atualize o método OnGetAsync no arquivo Pages/Courses/Details.cshtml.cs:


public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}

Course = await _context.Courses


.AsNoTracking()
.Include(c => c.Department)
.FirstOrDefaultAsync(m => m.CourseID == id);

if (Course == null)
{
return NotFound();
}
return Page();
}

Modificar as páginas Excluir e Detalhes


Atualize a página Excluir do Razor com a seguinte marcação:
@page
@model ContosoUniversity.Pages.Courses.DeleteModel

@{
ViewData["Title"] = "Delete";
}

<h2>Delete</h2>

<h3>Are you sure you want to delete this?</h3>


<div>
<h4>Course</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Course.CourseID)
</dt>
<dd>
@Html.DisplayFor(model => model.Course.CourseID)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Course.Title)
</dt>
<dd>
@Html.DisplayFor(model => model.Course.Title)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Course.Credits)
</dt>
<dd>
@Html.DisplayFor(model => model.Course.Credits)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Course.Department)
</dt>
<dd>
@Html.DisplayFor(model => model.Course.Department.DepartmentID)
</dd>
</dl>

<form method="post">
<input type="hidden" asp-for="Course.CourseID" />
<input type="submit" value="Delete" class="btn btn-default" /> |
<a asp-page="./Index">Back to List</a>
</form>
</div>

Faça as mesmas alterações na página Detalhes.


Testar as páginas Curso
Teste as páginas Criar, Editar, Detalhes e Excluir.

Atualizar as páginas do instrutor


As seções a seguir atualizam as páginas do instrutor.
Adicionar local do escritório
Ao editar um registro de instrutor, é recomendável atualizar a atribuição de escritório do instrutor. A entidade
Instructor tem uma relação um para zero ou um com a entidade OfficeAssignment . O código do instrutor
precisa manipular:
Se o usuário limpar a atribuição de escritório, exclua a entidade OfficeAssignment .
Se o usuário inserir uma atribuição de escritório e ela estava vazia, crie uma nova entidade OfficeAssignment .
Se o usuário alterar a atribuição de escritório, atualize a entidade OfficeAssignment .
Atualize o modelo de página Editar Instrutores com o seguinte código:

public class EditModel : PageModel


{
private readonly ContosoUniversity.Data.SchoolContext _context;

public EditModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

[BindProperty]
public Instructor Instructor { get; set; }

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


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

Instructor = await _context.Instructors


.Include(i => i.OfficeAssignment)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);

if (Instructor == null)
{
return NotFound();
}
return Page();
}

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


{
if (!ModelState.IsValid)
{
return Page();
}

var instructorToUpdate = await _context.Instructors


.Include(i => i.OfficeAssignment)
.FirstOrDefaultAsync(s => s.ID == id);

if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"Instructor",
i => i.FirstMidName, i => i.LastName,
i => i.HireDate, i => i.OfficeAssignment))
{
if (String.IsNullOrWhiteSpace(
instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
await _context.SaveChangesAsync();
}
return RedirectToPage("./Index");

}
}

O código anterior:
Obtém a entidade Instructor atual do banco de dados usando o carregamento adiantado para a propriedade
de navegação OfficeAssignment .
Atualiza a entidade Instructor recuperada com valores do associador de modelos. TryUpdateModel impede o
excesso de postagem.
Se o local do escritório estiver em branco, Instructor.OfficeAssignment será definido como nulo. Quando
Instructor.OfficeAssignment é nulo, a linha relacionada na tabela OfficeAssignment é excluída.

Atualizar a página Editar Instrutor


Atualize Pages/Instructors/Edit.cshtml com o local do escritório:

@page
@model ContosoUniversity.Pages.Instructors.EditModel
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Instructor</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Instructor.ID" />
<div class="form-group">
<label asp-for="Instructor.LastName" class="control-label"></label>
<input asp-for="Instructor.LastName" class="form-control" />
<span asp-validation-for="Instructor.LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.FirstMidName" class="control-label"></label>
<input asp-for="Instructor.FirstMidName" class="form-control" />
<span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.HireDate" class="control-label"></label>
<input asp-for="Instructor.HireDate" class="form-control" />
<span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
<input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
<span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>
</div>
</div>

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

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Verifique se você pode alterar o local do escritório de um instrutor.

Adicionar atribuições de Curso à página Editar Instrutor


Os instrutores podem ministrar a quantidade de cursos que desejarem. Nesta seção, você adiciona a capacidade
de alterar as atribuições de curso. A seguinte imagem mostra a página Editar Instrutor atualizada:

Course e Instructor têm uma relação muitos para muitos. Para adicionar e remover relações, adicione e remova
entidades do conjunto de entidades de junção CourseAssignments .
As caixas de seleção permitem alterações em cursos aos quais um instrutor é atribuído. Uma caixa de seleção é
exibida para cada curso no banco de dados. Os cursos aos quais o instrutor é atribuído estão marcados. O usuário
pode marcar ou desmarcar as caixas de seleção para alterar as atribuições de curso. Se a quantidade de cursos for
muito maior:
Provavelmente, você usará outra interface do usuário para exibir os cursos.
O método de manipulação de uma entidade de junção para criar ou excluir relações não será alterado.
Adicionar classes para dar suporte às páginas Criar e Editar Instrutor
Crie SchoolViewModels/AssignedCourseData.cs com o seguinte código:

namespace ContosoUniversity.Models.SchoolViewModels
{
public class AssignedCourseData
{
public int CourseID { get; set; }
public string Title { get; set; }
public bool Assigned { get; set; }
}
}
A classe AssignedCourseData contém dados para criar as caixas de seleção para os cursos atribuídos por um
instrutor.
Crie a classe base Pages/Instructors/InstructorCoursesPageModel.cshtml.cs:

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Linq;

namespace ContosoUniversity.Pages.Instructors
{
public class InstructorCoursesPageModel : PageModel
{

public List<AssignedCourseData> AssignedCourseDataList;

public void PopulateAssignedCourseData(SchoolContext context,


Instructor instructor)
{
var allCourses = context.Courses;
var instructorCourses = new HashSet<int>(
instructor.CourseAssignments.Select(c => c.CourseID));
AssignedCourseDataList = new List<AssignedCourseData>();
foreach (var course in allCourses)
{
AssignedCourseDataList.Add(new AssignedCourseData
{
CourseID = course.CourseID,
Title = course.Title,
Assigned = instructorCourses.Contains(course.CourseID)
});
}
}

public void UpdateInstructorCourses(SchoolContext context,


string[] selectedCourses, Instructor instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}

var selectedCoursesHS = new HashSet<string>(selectedCourses);


var instructorCourses = new HashSet<int>
(instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
foreach (var course in context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.CourseAssignments.Add(
new CourseAssignment
{
InstructorID = instructorToUpdate.ID,
CourseID = course.CourseID
});
}
}
else
{
if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove
CourseAssignment courseToRemove
= instructorToUpdate
.CourseAssignments
.SingleOrDefault(i => i.CourseID == course.CourseID);
context.Remove(courseToRemove);
}
}
}
}
}
}

A InstructorCoursesPageModel é a classe base que será usada para os modelos de página Editar e Criar.
PopulateAssignedCourseData lê todas as entidades Course para popular AssignedCourseDataList . Para cada curso,
o código define a CourseID , o título e se o instrutor está ou não atribuído ao curso. Um HashSet é usado para
criar pesquisas eficientes.
Modelo de página Editar Instrutor
Atualize o modelo de página Editar Instrutor com o seguinte código:
public class EditModel : InstructorCoursesPageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public EditModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

[BindProperty]
public Instructor Instructor { get; set; }

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


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

Instructor = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments).ThenInclude(i => i.Course)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);

if (Instructor == null)
{
return NotFound();
}
PopulateAssignedCourseData(_context, Instructor);
return Page();
}

public async Task<IActionResult> OnPostAsync(int? id, string[] selectedCourses)


{
if (!ModelState.IsValid)
{
return Page();
}

var instructorToUpdate = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.FirstOrDefaultAsync(s => s.ID == id);

if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"Instructor",
i => i.FirstMidName, i => i.LastName,
i => i.HireDate, i => i.OfficeAssignment))
{
if (String.IsNullOrWhiteSpace(
instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
UpdateInstructorCourses(_context, selectedCourses, instructorToUpdate);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
UpdateInstructorCourses(_context, selectedCourses, instructorToUpdate);
PopulateAssignedCourseData(_context, instructorToUpdate);
return Page();
}
}
O código anterior manipula as alterações de atribuição de escritório.
Atualize a Exibição do Razor do instrutor:

@page
@model ContosoUniversity.Pages.Instructors.EditModel
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Instructor</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Instructor.ID" />
<div class="form-group">
<label asp-for="Instructor.LastName" class="control-label"></label>
<input asp-for="Instructor.LastName" class="form-control" />
<span asp-validation-for="Instructor.LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.FirstMidName" class="control-label"></label>
<input asp-for="Instructor.FirstMidName" class="form-control" />
<span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.HireDate" class="control-label"></label>
<input asp-for="Instructor.HireDate" class="form-control" />
<span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
<input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
<span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;

foreach (var course in Model.AssignedCourseDataList)


{
if (cnt++ % 3 == 0)
{
@:</tr><tr>
}
@:<td>
<input type="checkbox"
name="selectedCourses"
value="@course.CourseID"
@(Html.Raw(course.Assigned ? "checked=\"checked\"" : "")) />
@course.CourseID @: @course.Title
@:</td>
}
@:</tr>
}
</table>
</div>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>
</div>
</div>
</div>

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

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

OBSERVAÇÃO
Quando você cola o código no Visual Studio, as quebras de linha são alteradas de uma forma que divide o código. Pressione
Ctrl+Z uma vez para desfazer a formatação automática. A tecla de atalho Ctrl+Z corrige as quebras de linha para que elas se
pareçam com o que você vê aqui. O recuo não precisa ser perfeito, mas cada uma das linhas @</tr><tr> , @:<td> ,
@:</td> e @:</tr> precisa estar em uma única linha, conforme mostrado. Com o bloco de novo código selecionado,
pressione Tab três vezes para alinhar o novo código com o código existente. Vote ou examine o status deste bug com este
link.

Esse código anterior cria uma tabela HTML que contém três colunas. Cada coluna tem uma caixa de seleção e
uma legenda que contém o número e o título do curso. Todas as caixas de seleção têm o mesmo nome
("selectedCourses"). O uso do mesmo nome instrui o associador de modelos a tratá-las como um grupo. O
atributo de valor de cada caixa de seleção é definido como CourseID . Quando a página é postada, o associador de
modelos passa uma matriz que consiste nos valores CourseID para apenas as caixas de seleção marcadas.
Quando as caixas de seleção são inicialmente renderizadas, os cursos atribuídos ao instrutor têm atributos
marcados.
Execute o aplicativo e teste a página Editar Instrutor atualizada. Altere algumas atribuições de curso. As alterações
são refletidas na página Índice.
Observação: a abordagem usada aqui para editar os dados de curso do instrutor funciona bem quando há uma
quantidade limitada de cursos. Para coleções muito maiores, uma interface do usuário e um método de
atualização diferentes são mais utilizáveis e eficientes.
Atualizar a página Criar Instrutor
Atualize o modelo de página Criar instrutor com o seguinte código:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
public class CreateModel : InstructorCoursesPageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public CreateModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

public IActionResult OnGet()


{
var instructor = new Instructor();
instructor.CourseAssignments = new List<CourseAssignment>();

// Provides an empty collection for the foreach loop


// foreach (var course in Model.AssignedCourseDataList)
// in the Create Razor page.
PopulateAssignedCourseData(_context, instructor);
return Page();
}

[BindProperty]
public Instructor Instructor { get; set; }

public async Task<IActionResult> OnPostAsync(string[] selectedCourses)


{
if (!ModelState.IsValid)
{
return Page();
}

var newInstructor = new Instructor();


if (selectedCourses != null)
{
newInstructor.CourseAssignments = new List<CourseAssignment>();
foreach (var course in selectedCourses)
{
var courseToAdd = new CourseAssignment
{
CourseID = int.Parse(course)
};
newInstructor.CourseAssignments.Add(courseToAdd);
}
}

if (await TryUpdateModelAsync<Instructor>(
newInstructor,
"Instructor",
i => i.FirstMidName, i => i.LastName,
i => i.HireDate, i => i.OfficeAssignment))
{
_context.Instructors.Add(newInstructor);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
PopulateAssignedCourseData(_context, newInstructor);
return Page();
}
}
}

O código anterior é semelhante ao código de Pages/Instructors/Edit.cshtml.cs.


Atualize a página Criar instrutor do Razor com a seguinte marcação:

@page
@model ContosoUniversity.Pages.Instructors.CreateModel

@{
ViewData["Title"] = "Create";
}

<h2>Create</h2>

<h4>Instructor</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Instructor.LastName" class="control-label"></label>
<input asp-for="Instructor.LastName" class="form-control" />
<span asp-validation-for="Instructor.LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.FirstMidName" class="control-label"></label>
<input asp-for="Instructor.FirstMidName" class="form-control" />
<span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.HireDate" class="control-label"></label>
<input asp-for="Instructor.HireDate" class="form-control" />
<span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
</div>

<div class="form-group">
<label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
<input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
<span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;

foreach (var course in Model.AssignedCourseDataList)


{
if (cnt++ % 3 == 0)
{
@:</tr><tr>
}
@:<td>
<input type="checkbox"
name="selectedCourses"
value="@course.CourseID"
@(Html.Raw(course.Assigned ? "checked=\"checked\"" : "")) />
@course.CourseID @: @course.Title
@:</td>
}
@:</tr>
}
</table>
</div>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>

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

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Teste a página Criar Instrutor.

Atualizar a página Excluir


Atualize o modelo da página Excluir com o seguinte código:
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
public class DeleteModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public DeleteModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

[BindProperty]
public Instructor Instructor { get; set; }

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


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

Instructor = await _context.Instructors.SingleAsync(m => m.ID == id);

if (Instructor == null)
{
return NotFound();
}
return Page();
}

public async Task<IActionResult> OnPostAsync(int id)


{
Instructor instructor = await _context.Instructors
.Include(i => i.CourseAssignments)
.SingleAsync(i => i.ID == id);

var departments = await _context.Departments


.Where(d => d.InstructorID == id)
.ToListAsync();
departments.ForEach(d => d.InstructorID = null);

_context.Instructors.Remove(instructor);

await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}

O código anterior faz as seguintes alterações:


Usa o carregamento adiantado para a propriedade de navegação CourseAssignments . CourseAssignments
deve ser incluído ou eles não são excluídos quando o instrutor é excluído. Para evitar a necessidade de lê-
las, configure a exclusão em cascata no banco de dados.
Se o instrutor a ser excluído é atribuído como administrador de qualquer departamento, remove a
atribuição de instrutor desse departamento.
Anterior Próximo
14/02/2018 • 27 min to read • Edit Online

en-us/

Lidando com conflitos de simultaneidade – EF Core com as


Páginas do Razor (8 de 8)
Por Rick Anderson, Tom Dykstra e Jon P Smith
O aplicativo Web Contoso University demonstra como criar aplicativos Web das Páginas do Razor usando o EF
Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o primeiro tutorial.
Este tutorial mostra como lidar com conflitos quando os mesmos usuários atualizam uma entidade
simultaneamente. Caso tenha problemas que não consiga resolver, baixe o aplicativo concluído para este estágio.

Conflitos de simultaneidade
Um conflito de simultaneidade ocorre quando:
Um usuário navega para a página de edição de uma entidade.
Outro usuário atualiza a mesma entidade antes que a primeira alteração do usuário seja gravada no BD.
Se a detecção de simultaneidade não estiver habilitada, quando ocorrerem atualizações simultâneas:
A última atualização vencerá. Ou seja, os últimos valores de atualização serão salvos no BD.
A primeira das atualizações atuais será perdida.
Simultaneidade otimista
A simultaneidade otimista permite que conflitos de simultaneidade ocorram e, em seguida, responde
adequadamente quando ocorrem. Por exemplo, Alice visita a página Editar Departamento e altera o orçamento
para o departamento de inglês de US$ 350.000,00 para US$ 0,00.
Antes que Alice clique em Salvar, Julio visita a mesma página e altera o campo Data de Início de 1/9/2007 para
1/9/2013.
Alice clica em Salvar primeiro e vê a alteração quando o navegador exibe a página Índice.

Julio clica em Salvar em uma página Editar que ainda mostra um orçamento de US$ 350.000,00. O que
acontece em seguida é determinado pela forma como você lida com conflitos de simultaneidade.
A simultaneidade otimista inclui as seguintes opções:
Controle qual propriedade um usuário modificou e atualize apenas as colunas correspondentes no BD.
No cenário, não haverá perda de dados. Propriedades diferentes foram atualizadas pelos dois usuários. Na
próxima vez que alguém navegar no departamento de inglês, verá as alterações de Alice e Julio. Esse método de
atualização pode reduzir o número de conflitos que podem resultar em perda de dados. Essa abordagem: * Não
poderá evitar a perda de dados se forem feitas alterações concorrentes na mesma propriedade. * Geralmente,
não é prática em um aplicativo Web. Ela exige um estado de manutenção significativo para controlar todos os
valores buscados e novos valores. Manter grandes quantidades de estado pode afetar o desempenho do
aplicativo. * Pode aumentar a complexidade do aplicativo comparado à detecção de simultaneidade em uma
entidade.
Você não pode deixar a alteração de Julio substituir a alteração de Alice.
Na próxima vez que alguém navegar pelo departamento de inglês, verá 1/9/2013 e o valor de US$ 350.000,00
buscado. Essa abordagem é chamada de um cenário O cliente vence ou O último vence. (Todos os valores do
cliente têm precedência sobre o conteúdo do armazenamento de dados.) Se você não fizer nenhuma codificação
para a manipulação de simultaneidade, o cenário O cliente vence ocorrerá automaticamente.
Você pode impedir que as alterações de Julio sejam atualizadas no BD. Normalmente, o aplicativo: * Exibe
uma mensagem de erro. * Mostra o estado atual dos dados. * Permite ao usuário aplicar as alterações
novamente.
Isso é chamado de um cenário O armazenamento vence. (Os valores do armazenamento de dados têm
precedência sobre os valores enviados pelo cliente.) Você implementará o cenário O armazenamento vence neste
tutorial. Esse método garante que nenhuma alteração é substituída sem que um usuário seja alertado.

Tratamento de simultaneidade
Quando uma propriedade é configurada como um token de simultaneidade:
O EF Core verifica se a propriedade não foi modificada depois que foi buscada. A verificação ocorre quando
SaveChanges ou SaveChangesAsync é chamado.
Se a propriedade tiver sido alterada depois que ela foi buscada, uma DbUpdateConcurrencyException será
gerada.
O BD e o modelo de dados precisam ser configurados para dar suporte à geração de
DbUpdateConcurrencyException .

Detectando conflitos de simultaneidade em uma propriedade


Os conflitos de simultaneidade podem ser detectados no nível do propriedade com o atributo
ConcurrencyCheck. O atributo pode ser aplicado a várias propriedades no modelo. Para obter mais informações,
consulte Data Annotations-ConcurrencyCheck.
O atributo [ConcurrencyCheck] não é usado neste tutorial.
Detectando conflitos de simultaneidade em uma linha
Para detectar conflitos de simultaneidade, uma coluna de controle de rowversion é adicionada ao modelo.
rowversion :

É específico ao SQL Server. Outros bancos de dados podem não fornecer um recurso semelhante.
É usado para determinar se uma entidade não foi alterada desde que foi buscada no BD.
O BD gera um número rowversion sequencial que é incrementado sempre que a linha é atualizada. Em um
comando Update ou Delete , a cláusula Where inclui o valor buscado de rowversion . Se a linha que está sendo
atualizada foi alterada:
rowversionnão corresponde ao valor buscado.
Os comandos Update ou Delete não encontram uma linha porque a cláusula Where inclui a rowversion
buscada.
Uma DbUpdateConcurrencyException é gerada.
No EF Core, quando nenhuma linha é atualizada por um comando Update ou Delete , uma exceção de
simultaneidade é gerada.
Adicionar uma propriedade de controle à entidade Department
Em Models/Department.cs, adicione uma propriedade de controle chamada RowVersion:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }

[StringLength(50, MinimumLength = 3)]


public string Name { get; set; }

[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }

[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }

public int? InstructorID { get; set; }

[Timestamp]
public byte[] RowVersion { get; set; }

public Instructor Administrator { get; set; }


public ICollection<Course> Courses { get; set; }
}
}

O atributo Timestamp especifica que essa coluna é incluída na cláusula Where dos comandos Update e Delete .
O atributo é chamado Timestamp porque as versões anteriores do SQL Server usavam um tipo de dados
timestamp do SQL antes de o tipo rowversion SQL substituí-lo.

A API fluente também pode especificar a propriedade de controle:

modelBuilder.Entity<Department>()
.Property<byte[]>("RowVersion")
.IsRowVersion();

O seguinte código mostra uma parte do T-SQL gerado pelo EF Core quando o nome do Departamento é
atualizado:

SET NOCOUNT ON;


UPDATE [Department] SET [Name] = @p0
WHERE [DepartmentID] = @p1 AND [RowVersion] = @p2;
SELECT [RowVersion]
FROM [Department]
WHERE @@ROWCOUNT = 1 AND [DepartmentID] = @p1;

O código anterior realçado mostra a cláusula WHERE que contém RowVersion . Se o BD RowVersion não for igual
ao parâmetro RowVersion ( @p2 ), nenhuma linha será atualizada.
O seguinte código realçado mostra o T-SQL que verifica exatamente se uma linha foi atualizada:
SET NOCOUNT ON;
UPDATE [Department] SET [Name] = @p0
WHERE [DepartmentID] = @p1 AND [RowVersion] = @p2;
SELECT [RowVersion]
FROM [Department]
WHERE @@ROWCOUNT = 1 AND [DepartmentID] = @p1;

@@ROWCOUNT retorna o número de linhas afetadas pela última instrução. Quando nenhuma linha é
atualizada, o EF Core gera uma DbUpdateConcurrencyException .
Veja o T-SQL gerado pelo EF Core na janela de Saída do Visual Studio.
Atualizar o BD
A adição da propriedade RowVersion altera o modelo de BD, o que exige uma migração.
Compile o projeto. Insira o seguinte em uma janela Comando:

dotnet ef migrations add RowVersion


dotnet ef database update

Os comandos anteriores:
Adicionam o arquivo de migração Migrations/{time stamp }_RowVersion.cs.
Atualizam o arquivo Migrations/SchoolContextModelSnapshot.cs. A atualização adiciona o seguinte código
realçado ao método BuildModel :

modelBuilder.Entity("ContosoUniversity.Models.Department", b =>
{
b.Property<int>("DepartmentID")
.ValueGeneratedOnAdd();

b.Property<decimal>("Budget")
.HasColumnType("money");

b.Property<int?>("InstructorID");

b.Property<string>("Name")
.HasMaxLength(50);

b.Property<byte[]>("RowVersion")
.IsConcurrencyToken()
.ValueGeneratedOnAddOrUpdate();

b.Property<DateTime>("StartDate");

b.HasKey("DepartmentID");

b.HasIndex("InstructorID");

b.ToTable("Department");
});

Executam migrações para atualizar o BD.

Gerar o modelo Departamentos por scaffolding


Saia do Visual Studio.
Abra uma janela de comando no diretório do projeto (o diretório que contém os arquivos Program.cs,
Startup.cs e .csproj).
Execute o seguinte comando:

dotnet aspnet-codegenerator razorpage -m Department -dc SchoolContext -udl -outDir Pages\Departments --


referenceScriptLibraries

O comando anterior gera o modelo Department por scaffolding. Abra o projeto no Visual Studio.
Compile o projeto. O build gera erros, como o seguinte:
1>Pages/Departments/Index.cshtml.cs(26,37,26,43): error CS1061: 'SchoolContext' does not contain a definition
for 'Department' and no extension method 'Department' accepting a first argument of type 'SchoolContext' could
be found (are you missing a using directive or an assembly reference?)

Altere _context.Department globalmente para _context.Departments (ou seja, adicione um "s" a Department ). 7
ocorrências foram encontradas e atualizadas.
Atualizar a página Índice de Departamentos
O mecanismo de scaffolding criou uma coluna RowVersion para a página Índice, mas esse campo não deve ser
exibido. Neste tutorial, o último byte da RowVersion é exibido para ajudar a entender a simultaneidade. O último
byte não tem garantia de ser exclusivo. Um aplicativo real não exibe RowVersion ou o último byte de RowVersion .
Atualize a página Índice:
Substitua Índice por Departamentos.
Substitua a marcação que contém RowVersion pelo último byte de RowVersion .
Substitua FirstMidName por FullName.
A seguinte marcação mostra a página atualizada:
@page
@model ContosoUniversity.Pages.Departments.IndexModel

@{
ViewData["Title"] = "Departments";
}

<h2>Departments</h2>

<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Department[0].Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Department[0].Budget)
</th>
<th>
@Html.DisplayNameFor(model => model.Department[0].StartDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Department[0].Administrator)
</th>
<th>
RowVersion
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Department) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Budget)
</td>
<td>
@Html.DisplayFor(modelItem => item.StartDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Administrator.FullName)
</td>
<td>
@item.RowVersion[7]
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.DepartmentID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.DepartmentID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.DepartmentID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Atualizar o modelo da página Editar


Atualize pages\departments\edit.cshtml.cs com o seguinte código:

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Departments
{
public class EditModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public EditModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

[BindProperty]
public Department Department { get; set; }
// Replace ViewData["InstructorID"]
public SelectList InstructorNameSL { get; set; }

public async Task<IActionResult> OnGetAsync(int id)


{
Department = await _context.Departments
.Include(d => d.Administrator) // eager loading
.AsNoTracking() // tracking not required
.FirstOrDefaultAsync(m => m.DepartmentID == id);

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

// Use strongly typed data rather than ViewData.


InstructorNameSL = new SelectList(_context.Instructors,
"ID", "FirstMidName");

return Page();
}

public async Task<IActionResult> OnPostAsync(int id)


{
if (!ModelState.IsValid)
{
return Page();
}

var departmentToUpdate = await _context.Departments


.Include(i => i.Administrator)
.FirstOrDefaultAsync(m => m.DepartmentID == id);

// null means Department was deleted by another user.


if (departmentToUpdate == null)
{
return await HandleDeletedDepartment();
}

// Update the RowVersion to the value when this entity was


// fetched. If the entity has been updated after it was
// fetched, RowVersion won't match the DB RowVersion and
// a DbUpdateConcurrencyException is thrown.
// A second postback will make them match, unless a new
// concurrency issue happens.
_context.Entry(departmentToUpdate)
.Property("RowVersion").OriginalValue = Department.RowVersion;

if (await TryUpdateModelAsync<Department>(
if (await TryUpdateModelAsync<Department>(
departmentToUpdate,
"Department",
s => s.Name, s => s.StartDate, s => s.Budget, s => s.InstructorID))
{
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (Department)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable to save. " +
"The department was deleted by another user.");
return Page();
}

var dbValues = (Department)databaseEntry.ToObject();


await setDbErrorMessage(dbValues, clientValues, _context);

// Save the current RowVersion so next postback


// matches unless an new concurrency issue happens.
Department.RowVersion = (byte[])dbValues.RowVersion;
// Must clear the model error for the next postback.
ModelState.Remove("Department.RowVersion");
}
}

InstructorNameSL = new SelectList(_context.Instructors,


"ID", "FullName", departmentToUpdate.InstructorID);

return Page();
}

private async Task<IActionResult> HandleDeletedDepartment()


{
Department deletedDepartment = new Department();
// ModelState contains the posted data because of the deletion error and will overide the
Department instance values when displaying Page().
ModelState.AddModelError(string.Empty,
"Unable to save. The department was deleted by another user.");
InstructorNameSL = new SelectList(_context.Instructors, "ID", "FullName",
Department.InstructorID);
return Page();
}

private async Task setDbErrorMessage(Department dbValues,


Department clientValues, SchoolContext context)
{

if (dbValues.Name != clientValues.Name)
{
ModelState.AddModelError("Department.Name",
$"Current value: {dbValues.Name}");
}
if (dbValues.Budget != clientValues.Budget)
{
ModelState.AddModelError("Department.Budget",
$"Current value: {dbValues.Budget:c}");
}
if (dbValues.StartDate != clientValues.StartDate)
{
ModelState.AddModelError("Department.StartDate",
$"Current value: {dbValues.StartDate:d}");
}
if (dbValues.InstructorID != clientValues.InstructorID)
{
Instructor dbInstructor = await _context.Instructors
.FindAsync(dbValues.InstructorID);
ModelState.AddModelError("Department.InstructorID",
$"Current value: {dbInstructor?.FullName}");
}

ModelState.AddModelError(string.Empty,
"The record you attempted to edit "
+ "was modified by another user after you. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again.");
}
}
}

Para detectar um problema de simultaneidade, o OriginalValue é atualizado com o valor rowVersion da entidade
que foi buscada. O EF Core gera um comando SQL UPDATE com uma cláusula WHERE que contém o valor
RowVersion original. Se nenhuma linha for afetada pelo comando UPDATE (nenhuma linha tem o valor
RowVersion original), uma exceção DbUpdateConcurrencyException será gerada.

public async Task<IActionResult> OnPostAsync(int id)


{
if (!ModelState.IsValid)
{
return Page();
}

var departmentToUpdate = await _context.Departments


.Include(i => i.Administrator)
.FirstOrDefaultAsync(m => m.DepartmentID == id);

// null means Department was deleted by another user.


if (departmentToUpdate == null)
{
return await HandleDeletedDepartment();
}

// Update the RowVersion to the value when this entity was


// fetched. If the entity has been updated after it was
// fetched, RowVersion won't match the DB RowVersion and
// a DbUpdateConcurrencyException is thrown.
// A second postback will make them match, unless a new
// concurrency issue happens.
_context.Entry(departmentToUpdate)
.Property("RowVersion").OriginalValue = Department.RowVersion;

No código anterior, Department.RowVersion é o valor quando a entidade foi buscada. OriginalValue é o valor no
BD quando FirstOrDefaultAsync foi chamado nesse método.
O seguinte código obtém os valores de cliente (os valores postados nesse método) e os valores do BD:
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (Department)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable to save. " +
"The department was deleted by another user.");
return Page();
}

var dbValues = (Department)databaseEntry.ToObject();


await setDbErrorMessage(dbValues, clientValues, _context);

// Save the current RowVersion so next postback


// matches unless an new concurrency issue happens.
Department.RowVersion = (byte[])dbValues.RowVersion;
// Must clear the model error for the next postback.
ModelState.Remove("Department.RowVersion");
}

O seguinte código adiciona uma mensagem de erro personalizada a cada coluna que tem valores de BD
diferentes daqueles que foram postados em OnPostAsync :

private async Task setDbErrorMessage(Department dbValues,


Department clientValues, SchoolContext context)
{

if (dbValues.Name != clientValues.Name)
{
ModelState.AddModelError("Department.Name",
$"Current value: {dbValues.Name}");
}
if (dbValues.Budget != clientValues.Budget)
{
ModelState.AddModelError("Department.Budget",
$"Current value: {dbValues.Budget:c}");
}
if (dbValues.StartDate != clientValues.StartDate)
{
ModelState.AddModelError("Department.StartDate",
$"Current value: {dbValues.StartDate:d}");
}
if (dbValues.InstructorID != clientValues.InstructorID)
{
Instructor dbInstructor = await _context.Instructors
.FindAsync(dbValues.InstructorID);
ModelState.AddModelError("Department.InstructorID",
$"Current value: {dbInstructor?.FullName}");
}

ModelState.AddModelError(string.Empty,
"The record you attempted to edit "
+ "was modified by another user after you. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again.");
}
O código realçado a seguir define o valor RowVersion com o novo valor recuperado do BD. Na próxima vez que
o usuário clicar em Salvar, somente os erros de simultaneidade que ocorrerem desde a última exibição da
página Editar serão capturados.

try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (Department)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable to save. " +
"The department was deleted by another user.");
return Page();
}

var dbValues = (Department)databaseEntry.ToObject();


await setDbErrorMessage(dbValues, clientValues, _context);

// Save the current RowVersion so next postback


// matches unless an new concurrency issue happens.
Department.RowVersion = (byte[])dbValues.RowVersion;
// Must clear the model error for the next postback.
ModelState.Remove("Department.RowVersion");
}

A instrução ModelState.Remove é obrigatória porque ModelState tem o valor RowVersion antigo. Na Página do
Razor, o valor ModelState de um campo tem precedência sobre os valores de propriedade do modelo, quando
ambos estão presentes.

Atualizar a página Editar


Atualize Pages/Departments/Edit.cshtml com a seguinte marcação:
@page "{id:int}"
@model ContosoUniversity.Pages.Departments.EditModel
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Department</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Department.DepartmentID" />
<input type="hidden" asp-for="Department.RowVersion" />
<div class="form-group">
<label>RowVersion</label>
@Model.Department.RowVersion[7]
</div>
<div class="form-group">
<label asp-for="Department.Name" class="control-label"></label>
<input asp-for="Department.Name" class="form-control" />
<span asp-validation-for="Department.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Department.Budget" class="control-label"></label>
<input asp-for="Department.Budget" class="form-control" />
<span asp-validation-for="Department.Budget" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Department.StartDate" class="control-label"></label>
<input asp-for="Department.StartDate" class="form-control" />
<span asp-validation-for="Department.StartDate" class="text-danger">
</span>
</div>
<div class="form-group">
<label class="control-label">Instructor</label>
<select asp-for="Department.InstructorID" class="form-control"
asp-items="@Model.InstructorNameSL"></select>
<span asp-validation-for="Department.InstructorID" class="text-danger">
</span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="./Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

A marcação anterior:
Atualiza a diretiva page de @page para @page "{id:int}" .
Adiciona uma versão de linha oculta. RowVersion deve ser adicionado para que o postback associe o valor.
Exibe o último byte de RowVersion para fins de depuração.
Substitui ViewData pelo InstructorNameSL fortemente tipado.

Testar conflitos de simultaneidade com a página Editar


Abra duas instâncias de navegadores de Editar no departamento de inglês:
Execute o aplicativo e selecione Departamentos.
Clique com o botão direito do mouse no hiperlink Editar do departamento de inglês e selecione Abrir em
uma nova guia.
Na primeira guia, clique no hiperlink Editar do departamento de inglês.
As duas guias do navegador exibem as mesmas informações.
Altere o nome na primeira guia do navegador e clique em Salvar.

O navegador mostra a página de Índice com o valor alterado e o indicador de rowVersion atualizado. Observe o
indicador de rowVersion atualizado: ele é exibido no segundo postback na outra guia.
Altere outro campo na segunda guia do navegador.
Clique em Salvar. Você verá mensagens de erro em todos os campos que não correspondem aos valores do BD:
Essa janela do navegador não pretendia alterar o campo Name. Copie e cole o valor atual (Languages) para o
campo Name. Saída da guia. A validação do lado do cliente remove a mensagem de erro.
Clique em Salvar novamente. O valor inserido na segunda guia do navegador foi salvo. Você verá os valores
salvos na página Índice.

Atualizar a página Excluir


Atualize o modelo da página Excluir com o seguinte código:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Departments
{
public class DeleteModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public DeleteModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}

[BindProperty]
public Department Department { get; set; }
public string ConcurrencyErrorMessage { get; set; }

public async Task<IActionResult> OnGetAsync(int id, bool? concurrencyError)


{
Department = await _context.Departments
.Include(d => d.Administrator)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.DepartmentID == id);

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

if (concurrencyError.GetValueOrDefault())
{
ConcurrencyErrorMessage = "The record you attempted to delete "
+ "was modified by another user after you selected delete. "
+ "The delete operation was canceled and the current values in the "
+ "database have been displayed. If you still want to delete this "
+ "record, click the Delete button again.";
}
return Page();
}

public async Task<IActionResult> OnPostAsync(int id)


{
try
{
if (await _context.Departments.AnyAsync(
m => m.DepartmentID == id))
{
// Department.rowVersion value is from when the entity
// was fetched. If it doesn't match the DB, a
// DbUpdateConcurrencyException exception is thrown.
_context.Departments.Remove(Department);
await _context.SaveChangesAsync();
}
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException)
{
return RedirectToPage("./Delete",
new { concurrencyError = true, id = id });
}
}
}
}

A página Excluir detectou conflitos de simultaneidade quando a entidade foi alterada depois de ser buscada.
Department.RowVersion é a versão de linha quando a entidade foi buscada. Quando o EF Core cria o comando
SQL DELETE, ele inclui uma cláusula WHERE com RowVersion . Se o comando SQL DELETE não resultar em
nenhuma linha afetada:
A RowVersion no comando SQL DELETE não corresponderá a RowVersion no BD.
Uma exceção DbUpdateConcurrencyException é gerada.
OnGetAsync é chamado com o concurrencyError .
Atualizar a página Excluir
Atualize Pages/Departments/Delete.cshtml com o seguinte código:

@page "{id:int}"
@model ContosoUniversity.Pages.Departments.DeleteModel

@{
ViewData["Title"] = "Delete";
}

<h2>Delete</h2>

<p class="text-danger">@Model.ConcurrencyErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>


<div>
<h4>Department</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Department.Name)
</dt>
<dd>
@Html.DisplayFor(model => model.Department.Name)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Department.Budget)
</dt>
<dd>
@Html.DisplayFor(model => model.Department.Budget)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Department.StartDate)
</dt>
<dd>
@Html.DisplayFor(model => model.Department.StartDate)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Department.RowVersion)
</dt>
<dd>
@Html.DisplayFor(model => model.Department.RowVersion[7])
</dd>
<dt>
@Html.DisplayNameFor(model => model.Department.Administrator)
</dt>
<dd>
@Html.DisplayFor(model => model.Department.Administrator.FullName)
</dd>
</dl>

<form method="post">
<input type="hidden" asp-for="Department.DepartmentID" />
<input type="hidden" asp-for="Department.RowVersion" />
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-default" /> |
<a asp-page="./Index">Back to List</a>
</div>
</form>
</div>

A marcação anterior faz as seguintes alterações:


Atualiza a diretiva page de @page para @page "{id:int}" .
Adiciona uma mensagem de erro.
Substitua FirstMidName por FullName no campo Administrador.
Altere RowVersion para exibir o último byte.
Adiciona uma versão de linha oculta. RowVersion deve ser adicionado para que o postback associe o valor.
Testar conflitos de simultaneidade com a página Excluir
Crie um departamento de teste.
Abra duas instâncias dos navegadores de Excluir no departamento de teste:
Execute o aplicativo e selecione Departamentos.
Clique com o botão direito do mouse no hiperlink Excluir do departamento de teste e selecione Abrir em
uma nova guia.
Clique no hiperlink Editar do departamento de teste.
As duas guias do navegador exibem as mesmas informações.
Altere o orçamento na primeira guia do navegador e clique em Salvar.
O navegador mostra a página de Índice com o valor alterado e o indicador de rowVersion atualizado. Observe o
indicador de rowVersion atualizado: ele é exibido no segundo postback na outra guia.
Exclua o departamento de teste na segunda guia. Um erro de simultaneidade é exibido com os valores atuais do
BD. Clicar em Excluir exclui a entidade, a menos que RowVersion tenha sido atualizada e o departamento tenha
sido excluído.
Consulte Herança para saber como herdar um modelo de dados.
Recursos adicionais
Tokens de simultaneidade no EF Core
Tratamento de simultaneidade no EF Core

Anterior
ASP.NET Core MVC com EF Core – série de tutoriais
10/04/2018 • 1 min to read • Edit Online

Este tutorial ensina a usar o ASP.NET Core MVC e o Entity Framework Core com controladores e exibições. As
Páginas Razor é uma nova alternativa no ASP.NET Core 2.0, um modelo de programação baseado em página que
torna a criação da interface do usuário da Web mais fácil e produtiva. É recomendável tentar o tutorial das Páginas
Razor na versão do MVC. O tutorial Páginas do Razor:
É mais fácil de acompanhar.
Fornece mais práticas recomendadas do EF Core.
Usa consultas mais eficientes.
É mais atual com a API mais recente.
Aborda mais recursos.
É a abordagem preferencial para o desenvolvimento de novos aplicativos.
1. Introdução
2. Operações Create, Read, Update e Delete
3. Classificação, filtragem, paginação e agrupamento
4. Migrações
5. Criar um modelo de dados complexo
6. Lendo dados relacionados
7. Atualizando dados relacionados
8. Tratar conflitos de simultaneidade
9. Herança
10. Tópicos avançados
Introdução ao ASP.NET MVC Core e Entity
Framework Core usando o Visual Studio (1 a 10)
02/03/2018 • 39 min to read • Edit Online

Por Tom Dykstra e Rick Anderson


Uma versão das Páginas do Razor deste tutorial está disponível aqui. A versão das Páginas Razor é mais fácil
de seguir e abrange mais recursos de EF. Recomendamos que você siga a versão das Páginas do Razor deste
tutorial.
O aplicativo Web de exemplo Contoso University demonstra como criar aplicativos Web ASP.NET Core 2.0
MVC usando o EF (Entity Framework) Core 2.0 e o Visual Studio 2017.
O aplicativo de exemplo é um site de uma Contoso University fictícia. Ele inclui funcionalidades como
admissão de alunos, criação de cursos e atribuições de instrutor. Este é o primeiro de uma série de tutoriais
que explica como criar o aplicativo de exemplo Contoso University do zero.
Baixe ou exiba o aplicativo concluído.
O EF Core 2.0 é a última versão do EF, mas ainda não tem todos os recursos do EF 6.x. Para obter
informações sobre como escolher entre o EF 6.x e o EF Core, consulte EF Core vs. EF6.x. Se você escolher o
EF 6.x, confira a versão anterior desta série de tutoriais.

OBSERVAÇÃO
Para obter a versão ASP.NET Core 1.1 deste tutorial, confira a versão VS 2017 Atualização 2 deste tutorial em
formato PDF.
Para obter a versão do Visual Studio 2015 deste tutorial, consulte a Versão do VS 2015 da documentação do
ASP.NET Core no formato PDF.

Pré-requisitos
Install the following:
.NET Core 2.0.0 SDK or later.
Visual Studio 2017 version 15.3 or later with the ASP.NET and web development workload.

Solução de problemas
Caso tenha um problema que não consiga resolver, em geral, você poderá encontrar a solução comparando o
código com o projeto concluído. Para obter uma lista de erros comuns e como resolvê-los, consulte a seção
Solução de problemas do último tutorial da série. Caso não encontre o que precisa na seção, poste uma
pergunta no StackOverflow.com sobre o ASP.NET Core ou o EF Core.

DICA
Esta é uma série de dez tutoriais, cada um se baseando no que é feito nos tutoriais anteriores. Considere a possibilidade
de salvar uma cópia do projeto após a conclusão bem-sucedida de cada tutorial. Caso tenha problemas, comece
novamente no tutorial anterior em vez de voltar ao início de toda a série.
O aplicativo Web Contoso University
O aplicativo que você criará nestes tutoriais é um site simples de uma universidade.
Os usuários podem exibir e atualizar informações de alunos, cursos e instrutores. Estas são algumas das telas
que você criará.

O estilo de interface do usuário desse site foi mantido perto do que é gerado pelos modelos internos, de
modo que o tutorial possa se concentrar principalmente em como usar o Entity Framework.

Criar um aplicativo Web ASP.NET Core MVC


Abra o Visual Studio e crie um novo projeto Web ASP.NET Core C# chamado "ContosoUniversity".
No menu Arquivo, selecione Novo > Projeto.
No painel esquerdo, selecione Instalado > Visual C# > Web.
Selecione o modelo de projeto Aplicativo Web ASP.NET Core.
Insira ContosoUniversity como o nome e clique em OK.

Aguarde a caixa de diálogo Novo Aplicativo Web ASP.NET Core (.NET Core) ser exibida
Selecione ASP.NET Core 2.0 e o modelo Aplicativo Web (Model-View-Controller).
Observação: este tutorial exige o ASP.NET Core 2.0 e o EF Core 2.0 ou posterior — verifique se
ASP.NET Core 1.1 não está selecionado.
Verifique se a opção Autenticação está definida como Sem Autenticação.
Clique em OK
Configurar o estilo do site
Algumas alterações simples configurarão o menu do site, o layout e a home page.
Abra Views/Shared/_Layout.cshtml e faça as seguintes alterações:
Altere cada ocorrência de "ContosoUniversity" para "Contoso University". Há três ocorrências.
Adicione entradas de menu para Alunos, Cursos, Instrutores e Departamentos e exclua a entrada de
menu Contato.
As alterações são realçadas.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Contoso University</title>

<environment names="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment names="Staging,Production">
<link rel="stylesheet"
href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-
value="absolute" />
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>

</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-
collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">Contoso
University</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
<li><a asp-area="" asp-controller="Students" asp-action="Index">Students</a></li>
<li><a asp-area="" asp-controller="Courses" asp-action="Index">Courses</a></li>
<li><a asp-area="" asp-controller="Instructors" asp-action="Index">Instructors</a>
</li>
<li><a asp-area="" asp-controller="Departments" asp-action="Index">Departments</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>&copy; 2017 - Contoso University</p>
<p>&copy; 2017 - Contoso University</p>
</footer>
</div>

<environment names="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
</environment>
<environment names="Staging,Production">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
crossorigin="anonymous"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
</script>
<script src="~/js/site.min.js" asp-append-version="true"></script>
</environment>

@RenderSection("Scripts", required: false)


</body>
</html>

Em Views/Home/Index.cshtml, substitua o conteúdo do arquivo pelo seguinte código para substituir o texto
sobre o ASP.NET e MVC pelo texto sobre este aplicativo:

@{
ViewData["Title"] = "Home Page";
}

<div class="jumbotron">
<h1>Contoso University</h1>
</div>
<div class="row">
<div class="col-md-4">
<h2>Welcome to Contoso University</h2>
<p>
Contoso University is a sample application that
demonstrates how to use Entity Framework Core in an
ASP.NET Core MVC web application.
</p>
</div>
<div class="col-md-4">
<h2>Build it from scratch</h2>
<p>You can build the application by following the steps in a series of tutorials.</p>
<p><a class="btn btn-default" href="https://docs.asp.net/en/latest/data/ef-mvc/intro.html">See the
tutorial &raquo;</a></p>
</div>
<div class="col-md-4">
<h2>Download it</h2>
<p>You can download the completed project from GitHub.</p>
<p><a class="btn btn-default" href="https://github.com/aspnet/Docs/tree/master/aspnetcore/data/ef-
mvc/intro/samples/cu-final">See project source code &raquo;</a></p>
</div>
</div>

Pressione CTRL+F5 para executar o projeto ou escolha Depurar > Iniciar sem Depuração no menu. Você
verá a home page com guias para as páginas que você criará nestes tutoriais.
Pacotes NuGet do Entity Framework Core
Para adicionar o suporte do EF Core a um projeto, instale o provedor de banco de dados que você deseja ter
como destino. Este tutorial usa o SQL Server e o pacote de provedor é
Microsoft.EntityFrameworkCore.SqlServer. Este pacote está incluído no metapacote
Microsoft.AspNetCore.All e, portanto, não é necessário instalá-lo.
Este pacote e suas dependências ( Microsoft.EntityFrameworkCore e Microsoft.EntityFrameworkCore.Relational )
fornecem suporte de tempo de execução para o EF. Você adicionará um pacote de ferramentas
posteriormente, no tutorial Migrações.
Para obter informações sobre outros provedores de banco de dados que estão disponíveis para o Entity
Framework Core, consulte Provedores de banco de dados.

Criar o modelo de dados


Em seguida, você criará as classes de entidade para o aplicativo Contoso University. Você começará com as
três entidades a seguir.
Há uma relação um-para-muitos entre as entidades Student e Enrollment , e uma relação um-para-muitos
entre as entidades Course e Enrollment . Em outras palavras, um aluno pode ser registrado em qualquer
quantidade de cursos e um curso pode ter qualquer quantidade de alunos registrados.
Nas seções a seguir, você criará uma classe para cada uma dessas entidades.
A entidade Student

Na pasta Models, crie um arquivo de classe chamado Student.cs e substitua o código de modelo pelo código a
seguir.

using System;
using System.Collections.Generic;

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }

public ICollection<Enrollment> Enrollments { get; set; }


}
}

A propriedade ID se tornará a coluna de chave primária da tabela de banco de dados que corresponde a essa
classe. Por padrão, o Entity Framework interpreta uma propriedade nomeada ID ou classnameID como a
chave primária.
A propriedade Enrollments é uma propriedade de navegação. As propriedades de navegação armazenam
outras entidades que estão relacionadas a essa entidade. Nesse caso, a propriedade Enrollments de uma
Student entity armazenará todas as entidades Enrollment relacionadas a essa entidade Student . Em outras
palavras, se determinada linha Aluno no banco de dados tiver duas linhas Registro relacionadas (linhas que
contêm o valor de chave primária do aluno na coluna de chave estrangeira StudentID ), a propriedade de
navegação Enrollments dessa entidade Student conterá as duas entidades Enrollment .
Se uma propriedade de navegação pode armazenar várias entidades (como em relações muitos para muitos
ou um-para-muitos), o tipo precisa ser uma lista na qual entradas podem ser adicionadas, excluídas e
atualizadas, como ICollection<T> . Especifique ICollection<T> ou um tipo, como List<T> ou HashSet<T> . Se
você especificar ICollection<T> , o EF criará uma coleção HashSet<T> por padrão.
A entidade Enrollment

Na pasta Models, crie Enrollment.cs e substitua o código existente pelo seguinte código:

namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}

public class Enrollment


{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }

public Course Course { get; set; }


public Student Student { get; set; }
}
}

A propriedade EnrollmentID será a chave primária; essa entidade usa o padrão classnameID em vez de ID
por si só, como você viu na entidade Student . Normalmente, você escolhe um padrão e usa-o em todo o
modelo de dados. Aqui, a variação ilustra que você pode usar qualquer um dos padrões. Em um tutorial
posterior, você verá como usar uma ID sem nome de classe facilita a implementação da herança no modelo
de dados.
A propriedade Grade é um enum . O ponto de interrogação após a declaração de tipo Grade indica que a
propriedade Grade permite valor nulo. Uma nota nula é diferente de uma nota zero – nulo significa que uma
nota não é conhecida ou que ainda não foi atribuída.
A propriedade StudentID é uma chave estrangeira e a propriedade de navegação correspondente é Student .
Uma entidade Enrollment é associada a uma entidade Student , de modo que a propriedade possa
armazenar apenas uma única entidade Student (ao contrário da propriedade de navegação
Student.Enrollments que você viu anteriormente, que pode armazenar várias entidades Enrollment ).

A propriedade CourseID é uma chave estrangeira e a propriedade de navegação correspondente é Course .


Uma entidade Enrollment está associada a uma entidade Course .

O Entity Framework interpreta uma propriedade como uma propriedade de chave estrangeira se ela é
nomeada <navigation property name><primary key property name> (por exemplo, StudentID para a
propriedade de navegação Student , pois a chave primária da entidade Student é ID ). As propriedades de
chave estrangeira também podem ser nomeadas apenas <primary key property name> (por exemplo,
CourseID , pois a chave primária da entidade Course é CourseID ).
A entidade Course

Na pasta Models, crie Course.cs e substitua o código existente pelo seguinte código:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }

public ICollection<Enrollment> Enrollments { get; set; }


}
}

A propriedade Enrollments é uma propriedade de navegação. Uma entidade Course pode estar relacionada
a qualquer quantidade de entidades Enrollment .
Falaremos mais sobre o atributo DatabaseGenerated em um tutorial posterior desta série. Basicamente, esse
atributo permite que você insira a chave primária do curso, em vez de fazer com que ela seja gerada pelo
banco de dados.

Criar o contexto de banco de dados


A classe principal que coordena a funcionalidade do Entity Framework para determinado modelo de dados é
a classe de contexto de banco de dados. Você cria essa classe derivando-a da classe
Microsoft.EntityFrameworkCore.DbContext . No código, especifique quais entidades são incluídas no modelo de
dados. Também personalize o comportamento específico do Entity Framework. Neste projeto, a classe é
chamada SchoolContext .
Na pasta do projeto, crie uma pasta chamada Dados.
Na pasta Dados, crie um novo arquivo de classe chamado SchoolContext.cs e substitua o código de modelo
pelo seguinte código:
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}

public DbSet<Course> Courses { get; set; }


public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
}
}

Esse código cria uma propriedade DbSet para cada conjunto de entidades. Na terminologia do Entity
Framework, um conjunto de entidades normalmente corresponde a uma tabela de banco de dados, enquanto
uma entidade corresponde a uma linha na tabela.
Você pode omitir as instruções DbSet<Enrollment> e DbSet<Course> e elas funcionarão da mesma maneira. O
Entity Framework inclui-os de forma implícita porque a entidade Student referencia a entidade Enrollment e
a entidade Enrollment referencia a entidade Course .
Quando o banco de dados é criado, o EF cria tabelas que têm nomes iguais aos nomes de propriedade DbSet .
Em geral, os nomes de propriedade de coleções são plurais (Alunos em vez de Aluno), mas os
desenvolvedores não concordam sobre se os nomes de tabela devem ser pluralizados ou não. Para esses
tutoriais, você substituirá o comportamento padrão especificando nomes singulares de tabela no DbContext.
Para fazer isso, adicione o código realçado a seguir após a última propriedade DbSet.

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}

public DbSet<Course> Courses { get; set; }


public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
}
}
}

Registrar o contexto com a injeção de dependência


O ASP.NET Core implementa a injeção de dependência por padrão. Serviços (como o contexto de banco de
dados do EF ) são registrados com injeção de dependência durante a inicialização do aplicativo. Os
componentes que exigem esses serviços (como controladores MVC ) recebem esses serviços por meio de
parâmetros do construtor. Você verá o código de construtor do controlador que obtém uma instância de
contexto mais adiante neste tutorial.
Para registrar SchoolContext como um serviço, abra Startup.cs e adicione as linhas realçadas ao método
ConfigureServices .

public void ConfigureServices(IServiceCollection services)


{
services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddMvc();
}

O nome da cadeia de conexão é passado para o contexto com a chamada de um método em um objeto
DbContextOptionsBuilder . Para o desenvolvimento local, o sistema de configuração do ASP.NET Core lê a
cadeia de conexão do arquivo appsettings.json.
Adicione instruções using aos namespaces ContosoUniversity.Data e Microsoft.EntityFrameworkCore e, em
seguida, compile o projeto.

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;

Abra o arquivo appsettings.json e adicione uma cadeia de conexão, conforme mostrado no exemplo a seguir.

{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity1;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}

SQL Server Express LocalDB


A cadeia de conexão especifica um banco de dados LocalDB do SQL Server. LocalDB é uma versão leve do
Mecanismo de Banco de Dados do SQL Server Express destinado ao desenvolvimento de aplicativos, e não
ao uso em produção. O LocalDB é iniciado sob demanda e executado no modo de usuário e, portanto, não há
nenhuma configuração complexa. Por padrão, o LocalDB cria arquivos de banco de dados .mdf no diretório
C:/Users/<user> .

Adicionar um código para inicializar o banco de dados com os dados


de teste
O Entity Framework criará um banco de dados vazio para você. Nesta seção, você escreve um método que é
chamado depois que o banco de dados é criado para populá-lo com os dados de teste.
Aqui, você usará o método EnsureCreated para criar o banco de dados automaticamente. Em um tutorial
posterior, você verá como manipular as alterações do modelo usando as Migrações do Code First para alterar
o esquema de banco de dados, em vez de remover e recriar o banco de dados.
Na pasta Data, crie um novo arquivo de classe chamado DbInitializer.cs e substitua o código de modelo pelo
código a seguir, que faz com que um banco de dados seja criado, quando necessário, e carrega dados de teste
no novo banco de dados.

using ContosoUniversity.Models;
using System;
using System.Linq;

namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
context.Database.EnsureCreated();

// Look for any students.


if (context.Students.Any())
{
return; // DB has been seeded
}

var students = new Student[]


{
new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2005-09-
01")},
new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-
01")},
new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-
01")},
new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-
01")},
new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-
01")},
new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-
01")},
new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-
01")}
};
foreach (Student s in students)
{
context.Students.Add(s);
}
context.SaveChanges();

var courses = new Course[]


{
new Course{CourseID=1050,Title="Chemistry",Credits=3},
new Course{CourseID=4022,Title="Microeconomics",Credits=3},
new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
new Course{CourseID=1045,Title="Calculus",Credits=4},
new Course{CourseID=3141,Title="Trigonometry",Credits=4},
new Course{CourseID=2021,Title="Composition",Credits=3},
new Course{CourseID=2042,Title="Literature",Credits=4}
};
foreach (Course c in courses)
{
context.Courses.Add(c);
}
context.SaveChanges();

var enrollments = new Enrollment[]


{
new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
new Enrollment{StudentID=3,CourseID=1050},
new Enrollment{StudentID=4,CourseID=1050},
new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
new Enrollment{StudentID=6,CourseID=1045},
new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
};
foreach (Enrollment e in enrollments)
{
context.Enrollments.Add(e);
}
context.SaveChanges();
}
}
}

O código verifica se há alunos no banco de dados e, se não há, ele pressupõe que o banco de dados é novo e
precisa ser propagado com os dados de teste. Ele carrega os dados de teste em matrizes em vez de em
coleções List<T> para otimizar o desempenho.
Em Program.cs, modifique o método Main para fazer o seguinte na inicialização do aplicativo:
Obtenha uma instância de contexto de banco de dados do contêiner de injeção de dependência.
Chame o método de semente passando a ele o contexto.
Descarte o contexto quando o método de semente for concluído.

public static void Main(string[] args)


{
var host = BuildWebHost(args);

using (var scope = host.Services.CreateScope())


{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<SchoolContext>();
DbInitializer.Initialize(context);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
}
}

host.Run();
}

Adicione instruções using :

using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Data;

Nos tutoriais mais antigos, você poderá ver um código semelhante no método Configure em Startup.cs.
Recomendamos que você use o método Configure apenas para configurar o pipeline de solicitação. O código
de inicialização do aplicativo pertence ao método Main .
Agora, na primeira vez que você executar o aplicativo, o banco de dados será criado e propagado com os
dados de teste. Sempre que você alterar o modelo de dados, exclua o banco de dados, atualize o método de
semente e comece novamente com um novo banco de dados da mesma maneira. Nos próximos tutoriais,
você verá como modificar o banco de dados quando o modelo de dados for alterado, sem excluí-lo e recriá-lo.

Criar um controlador e exibições


Em seguida, você usará o mecanismo de scaffolding no Visual Studio para adicionar um controlador MVC e
exibições que usam o EF para consultar e salvar dados.
A criação automática de métodos de ação CRUD e exibições é conhecida como scaffolding. O scaffolding
difere da geração de código, em que o código gerado por scaffolding é um ponto de partida que você pode
modificar de acordo com seus requisitos, enquanto que normalmente o código gerado não é modificado.
Quando precisar personalizar o código gerado, use classes parciais ou regenere o código quando as coisas
mudarem.
Clique com o botão direito do mouse na pasta Controladores no Gerenciador de Soluções e selecione
Adicionar > Novo Item Gerado por Scaffolding.
Se a caixa de diálogo Adicionar Dependências do MVC for exibida:
Atualize o Visual Studio para a última versão. Versões do Visual Studio anteriores a 15.5 mostram essa
caixa de diálogo.
Se não puder atualizar, selecione ADICIONAR e, em seguida, siga as etapas de adição do controlador
novamente.
Na caixa de diálogo Adicionar Scaffolding:
Selecione Controlador MVC com exibições, usando o Entity Framework.
Clique em Adicionar.
Na caixa de diálogo Adicionar Controlador:
Na classe Model, selecione Aluno.
Na Classe de contexto de dados selecione SchoolContext.
Aceite o StudentsController padrão como o nome.
Clique em Adicionar.

Quando você clica em Adicionar, o mecanismo de scaffolding do Visual Studio cria um arquivo
StudentsController.cs e um conjunto de exibições (arquivos .cshtml) que funcionam com o controlador.
(O mecanismo de scaffolding também poderá criar o contexto de banco de dados para você se não criá-lo
manualmente primeiro como fez anteriormente para este tutorial. Especifique uma nova classe de contexto na
caixa Adicionar Controlador clicando no sinal de adição à direita de Classe de contexto de dados. Em
seguida, o Visual Studio criará a classe DbContext , bem como o controlador e as exibições.)
Você observará que o controlador usa um SchoolContext como parâmetro de construtor.

namespace ContosoUniversity.Controllers
{
public class StudentsController : Controller
{
private readonly SchoolContext _context;

public StudentsController(SchoolContext context)


{
_context = context;
}

A injeção de dependência do ASP.NET será responsável por passar uma instância de SchoolContext para o
controlador. Você configurou isso no arquivo Startup.cs anteriormente.
O controlador contém um método de ação Index , que exibe todos os alunos no banco de dados. O método
obtém uma lista de alunos do conjunto de entidades Students pela leitura da propriedade Students da
instância de contexto de banco de dados:

public async Task<IActionResult> Index()


{
return View(await _context.Students.ToListAsync());
}

Você aprenderá sobre os elementos de programação assíncronos nesse código mais adiante no tutorial.
A exibição Views/Students/Index.cshtml mostra esta lista em uma tabela:
@model IEnumerable<ContosoUniversity.Models.Student>

@{
ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.LastName)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstMidName)
</th>
<th>
@Html.DisplayNameFor(model => model.EnrollmentDate)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</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>

Pressione CTRL+F5 para executar o projeto ou escolha Depurar > Iniciar sem Depuração no menu.
Clique na guia Alunos para ver os dados de teste inserido pelo método DbInitializer.Initialize .
Dependendo da largura da janela do navegador, você verá o link da guia Student na parte superior da página
ou precisará clicar no ícone de navegação no canto superior direito para ver o link.
Exibir o banco de dados
Quando você iniciou o aplicativo, o método DbInitializer.Initialize chamou EnsureCreated . O EF
observou que não havia nenhum banco de dados e, portanto, ele criou um; em seguida, o restante do código
do método Initialize populou o banco de dados com os dados. Use o SSOX (Pesquisador de Objetos do
SQL Server) para exibir o banco de dados no Visual Studio.
Feche o navegador.
Se a janela do SSOX ainda não estiver aberta, selecione-a no menu Exibir do Visual Studio.
No SSOX, clique em (localdb)\MSSQLLocalDB > Bancos de Dados e, em seguida, clique na entrada do
nome do banco de dados que está na cadeia de conexão no arquivo appsettings.json.
Expanda o nó Tabelas para ver as tabelas no banco de dados.
Clique com o botão direito do mouse na tabela Aluno e clique em Exibir Dados para ver as colunas criadas
e as linhas que foram inseridas na tabela.

Os arquivos de banco de dados .mdf e .ldf estão na pasta C:\Users<yourusername>.


Como você está chamando EnsureCreated no método inicializador executado na inicialização do aplicativo,
agora você pode fazer uma alteração na classe Student , excluir o banco de dados, executar novamente o
aplicativo e o banco de dados será recriado automaticamente para que ele corresponda à alteração. Por
exemplo, se você adicionar uma propriedade EmailAddress à classe Student , verá uma nova coluna
EmailAddress na tabela recriada.

Convenções
A quantidade de código feita para que o Entity Framework possa criar um banco de dados completo para
você é mínima, devido ao uso de convenções ou de suposições feitas pelo Entity Framework.
Os nomes de propriedades DbSet são usadas como nomes de tabela. Para entidades não
referenciadas por uma propriedade DbSet , os nomes de classe de entidade são usados como nomes
de tabela.
Os nomes de propriedade de entidade são usados para nomes de coluna.
As propriedades de entidade que são nomeadas ID ou classnameID são reconhecidas como
propriedades de chave primária.
Uma propriedade é interpretada como uma propriedade de chave estrangeira se ela é nomeada (por
exemplo, StudentID para a propriedade de navegação Student , pois a chave primária da entidade
Student é ID ). As propriedades de chave estrangeira também podem ser nomeadas apenas (por
exemplo, EnrollmentID , pois a chave primária da entidade Enrollment é EnrollmentID ).
O comportamento convencional pode ser substituído. Por exemplo, você pode especificar os nomes de tabela
de forma explícita, conforme visto anteriormente neste tutorial. Além disso, você pode definir nomes de
coluna e qualquer propriedade como a chave primária ou chave estrangeira, como você verá em um tutorial
posterior desta série.

Código assíncrono
A programação assíncrona é o modo padrão do ASP.NET Core e EF Core.
Um servidor Web tem um número limitado de threads disponíveis e, em situações de alta carga, todos os
threads disponíveis podem estar em uso. Quando isso acontece, o servidor não pode processar novas
solicitações até que os threads são liberados. Com um código síncrono, muitos threads podem ser vinculados
enquanto realmente não são fazendo nenhum trabalho porque estão aguardando a conclusão da E/S. Com
um código assíncrono, quando um processo está aguardando a conclusão da E/S, seu thread é liberado para o
servidor para ser usado para processar outras solicitações. Como resultado, o código assíncrono permite que
os recursos do servidor sejam usados com mais eficiência, e o servidor fica capacitado a manipular mais
tráfego sem atrasos.
O código assíncrono introduz uma pequena quantidade de sobrecarga em tempo de execução, mas para
situações de baixo tráfego, o impacto no desempenho é insignificante, ao passo que, em situações de alto
tráfego, a melhoria de desempenho potencial é significativa.
No código a seguir, a palavra-chave async , o valor retornado Task<T> , a palavra-chave await e o método
ToListAsync fazem o código ser executado de forma assíncrona.

public async Task<IActionResult> Index()


{
return View(await _context.Students.ToListAsync());
}

A palavra-chave async instrui o compilador a gerar retornos de chamada para partes do corpo do
método e a criar automaticamente o objeto Task<IActionResult> que é retornado.
O tipo de retorno Task<IActionResult> representa um trabalho em andamento com um resultado do
tipo IActionResult .
A palavra-chave await faz com que o compilador divida o método em duas partes. A primeira parte
termina com a operação que é iniciada de forma assíncrona. A segunda parte é colocada em um
método de retorno de chamada que é chamado quando a operação é concluída.
ToListAsync é a versão assíncrona do método de extensão ToList .
Algumas coisas a serem consideradas quando você estiver escrevendo um código assíncrono que usa o Entity
Framework:
Somente instruções que fazem com que consultas ou comandos sejam enviados ao banco de dados
são executadas de forma assíncrona. Isso inclui, por exemplo, ToListAsync , SingleOrDefaultAsync e
SaveChangesAsync . Isso não inclui, por exemplo, instruções que apenas alteram um IQueryable , como
var students = context.Students.Where(s => s.LastName == "Davolio") .

Um contexto do EF não é thread-safe: não tente realizar várias operações em paralelo. Quando você
chamar qualquer método assíncrono do EF, sempre use a palavra-chave await .
Se desejar aproveitar os benefícios de desempenho do código assíncrono, verifique se os pacotes de
biblioteca que você está usando (por exemplo, para paginação) também usam o código assíncrono se
eles chamam métodos do Entity Framework que fazem com que consultas sejam enviadas ao banco de
dados.
Para obter mais informações sobre a programação assíncrona no .NET, consulte Visão geral da programação
assíncrona.

Resumo
Agora, você criou um aplicativo simples que usa o Entity Framework Core e o LocalDB do SQL Server
Express para armazenar e exibir dados. No tutorial a seguir, você aprenderá a executar operações CRUD (criar,
ler, atualizar e excluir) básicas.

Avançar
Criar, ler, atualizar e excluir -Tutorial EF Core
comASP.NET Core MVC (2 de 10)
03/03/2018 • 37 min to read • Edit Online

Por Tom Dykstra e Rick Anderson


O aplicativo Web de exemplo Contoso University demonstra como criar aplicativos Web ASP.NET Core MVC
usando o Entity Framework Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o
primeiro tutorial da série.
No tutorial anterior, você criou um aplicativo MVC que armazena e exibe dados usando o Entity Framework e o
LocalDB do SQL Server. Neste tutorial, você examinará e personalizará o código CRUD (criar, ler, atualizar e
excluir) que o scaffolding do MVC cria automaticamente para você em controladores e exibições.

OBSERVAÇÃO
É uma prática comum implementar o padrão de repositório para criar uma camada de abstração entre o controlador e a
camada de acesso a dados. Para manter esses tutoriais simples e com foco no ensino de como usar o Entity Framework em
si, eles não usam repositórios. Para obter informações sobre repositórios com o EF, consulte o último tutorial desta série.

Neste tutorial, você trabalhará com as seguintes páginas da Web:


Personalizar a página Detalhes
O código gerado por scaffolding da página Índice de Alunos omitiu a propriedade Enrollments , porque essa
propriedade contém uma coleção. Na página Detalhes, você exibirá o conteúdo da coleção em uma tabela
HTML.
Em Controllers/StudentsController.cs, o método de ação para a exibição Detalhes usa o método
SingleOrDefaultAsync para recuperar uma única entidade Student . Adicione um código que chama Include . Os
métodos ThenInclude e AsNoTracking , conforme mostrado no código realçado a seguir.

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


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

var student = await _context.Students


.Include(s => s.Enrollments)
.ThenInclude(e => e.Course)
.AsNoTracking()
.SingleOrDefaultAsync(m => m.ID == id);

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

return View(student);
}

Os métodos Include e ThenInclude fazem com que o contexto carregue a propriedade de navegação
Student.Enrollments e, dentro de cada registro, a propriedade de navegação Enrollment.Course . Você aprenderá
mais sobre esses métodos no tutorial Lendo dados relacionados.
O método AsNoTracking melhora o desempenho em cenários em que as entidades retornadas não serão
atualizadas no tempo de vida do contexto atual. Você aprenderá mais sobre AsNoTracking ao final deste tutorial.
Dados de rota
O valor de chave que é passado para o método Details é obtido dos dados de rota. Dados de rota são dados
que o associador de modelos encontrou em um segmento da URL. Por exemplo, a rota padrão especifica os
segmentos de controlador, ação e ID:

app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});

Na URL a seguir, a rota padrão mapeia Instructor como o controlador, Index como a ação e 1 como a ID; esses
são valores de dados de rota.

http://localhost:1230/Instructor/Index/1?courseID=2021

A última parte da URL ("?courseID=2021") é um valor de cadeia de caracteres de consulta. O associador de


modelos passará o valor da ID para o parâmetro id do método Details se você passá-lo como um valor de
cadeia de caracteres de consulta:

http://localhost:1230/Instructor/Index?id=1&CourseID=2021

Na página Índice, as URLs de hiperlinks são criadas por instruções de auxiliar de marcação na exibição do Razor.
No código Razor a seguir, o parâmetro id corresponde à rota padrão e, portanto, id é adicionada aos dados de
rota.

<a asp-action="Edit" asp-route-id="@item.ID">Edit</a>

Isso gera o seguinte HTML quando item.ID é 6:

<a href="/Students/Edit/6">Edit</a>

No código a seguir Razor, studentID não corresponde a um parâmetro na rota padrão e, portanto, ela é
adicionada como uma cadeia de caracteres de consulta.

<a asp-action="Edit" asp-route-studentID="@item.ID">Edit</a>

Isso gera o seguinte HTML quando item.ID é 6:

<a href="/Students/Edit?studentID=6">Edit</a>

Para obter mais informações sobre auxiliares de marcação, consulte Auxiliares de marcação no ASP.NET Core.
Adicionar registros à exibição Detalhes
Abra Views/Students/Details.cshtml. Cada campo é exibido usando auxiliares DisplayNameFor e DisplayFor ,
conforme mostrado no seguinte exemplo:
<dt>
@Html.DisplayNameFor(model => model.LastName)
</dt>
<dd>
@Html.DisplayFor(model => model.LastName)
</dd>

Após o último campo e imediatamente antes da marcação </dl> de fechamento, adicione o seguinte código
para exibir uma lista de registros:

<dt>
@Html.DisplayNameFor(model => model.Enrollments)
</dt>
<dd>
<table class="table">
<tr>
<th>Course Title</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Enrollments)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Course.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
</dd>

Se o recuo do código estiver incorreto depois de colar o código, pressione CTRL -K-D para corrigi-lo.
Esse código percorre as entidades na propriedade de navegação Enrollments . Para cada registro, ele exibe o
nome do curso e a nota. O título do curso é recuperado da entidade Course, que é armazenada na propriedade de
navegação Course da entidade Enrollments.
Execute o aplicativo, selecione a guia Alunos e clique no link Detalhes de um aluno. Você verá a lista de cursos e
notas do aluno selecionado:
Atualizar a página Criar
Em StudentsController.cs, modifique o método HttpPost Create adicionando um bloco try-catch e removendo a
ID do atributo Bind .

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
{
try
{
if (ModelState.IsValid)
{
_context.Add(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists " +
"see your system administrator.");
}
return View(student);
}

Esse código adiciona a entidade Student criada pelo associador de modelos do ASP.NET MVC ao conjunto de
entidades Student e, em seguida, salva as alterações no banco de dados. (Associador de modelos refere-se à
funcionalidade do ASP.NET MVC que facilita o trabalho com os dados enviados por um formulário; um
associador de modelos converte os valores de formulário postados em tipos CLR e passa-os para o método de
ação em parâmetros. Nesse caso, o associador de modelos cria uma instância de uma entidade Student usando
valores de propriedade da coleção Form.)
Você removeu ID do atributo Bind porque a ID é o valor de chave primária que o SQL Server definirá
automaticamente quando a linha for inserida. A entrada do usuário não define o valor da ID.
Além do atributo Bind , o bloco try-catch é a única alteração que você fez no código gerado por scaffolding. Se
uma exceção que é derivada de DbUpdateException é capturada enquanto as alterações estão sendo salvas, uma
mensagem de erro genérica é exibida. Às vezes, as exceções DbUpdateException são causadas por algo externo ao
aplicativo, em vez de por um erro de programação e, portanto, o usuário é aconselhado a tentar novamente.
Embora não implementado nesta amostra, um aplicativo de qualidade de produção registrará a exceção em log.
Para obter mais informações, consulte a seção Log para informações em Monitoramento e telemetria (criando
aplicativos de nuvem do mundo real com o Azure).
O atributo ValidateAntiForgeryToken ajuda a impedir ataques CSRF (solicitação intersite forjada). O token é
injetado automaticamente na exibição pelo FormTagHelper e é incluído quando o formulário é enviado pelo
usuário. O token é validado pelo atributo ValidateAntiForgeryToken . Para obter mais informações sobre o CSRF,
consulte Falsificação antissolicitação.
Observação de segurança sobre o excesso de postagem
O atributo Bind que o código gerado por scaffolding inclui no método Create é uma maneira de proteger
contra o excesso de postagem em cenários de criação. Por exemplo, suponha que a entidade Student inclua uma
propriedade Secret que você não deseja que essa página da Web defina.

public class Student


{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public string Secret { get; set; }
}

Mesmo se você não tiver um campo Secret na página da Web, um invasor poderá usar uma ferramenta como o
Fiddler ou escrever um JavaScript para postar um valor de formulário Secret . Sem o atributo Bind limitando
os campos que o associador de modelos usa quando ele cria uma instância de Student, o associador de modelos
seleciona esse valor de formulário Secret e usa-o para criar a instância da entidade Student. Em seguida, seja
qual for o valor que o invasor especificou para o campo de formulário Secret , ele é atualizado no banco de
dados. A imagem a seguir mostra a ferramenta Fiddler adicionando o campo Secret (com o valor "OverPost")
aos valores de formulário postados.
Em seguida, o valor "OverPost" é adicionado com êxito à propriedade Secret da linha inserida, embora você
nunca desejou que a página da Web pudesse definir essa propriedade.
Impeça o excesso de postagem em cenários de edição lendo a entidade do banco de dados primeiro e, em
seguida, chamando TryUpdateModel , passando uma lista explícita de propriedades permitidas. Esse é o método
usado nestes tutoriais.
Uma maneira alternativa de impedir o excesso de postagem preferida por muitos desenvolvedores é usar
modelos de exibição em vez de classes de entidade com a associação de modelos. Inclua apenas as propriedades
que você deseja atualizar no modelo de exibição. Quando o associador de modelos MVC tiver concluído, copie as
propriedades do modelo de exibição para a instância da entidade, opcionalmente usando uma ferramenta como o
AutoMapper. Use _context.Entry na instância de entidade para definir seu estado como Unchanged e, em
seguida, defina Property("PropertyName").IsModified como verdadeiro em cada propriedade da entidade incluída
no modelo de exibição. Esse método funciona nos cenários de edição e criação.
Testar a página Criar
O código em Views/Students/Create.cshtml usa os auxiliares de marcação label , input e span (para
mensagens de validação) para cada campo.
Execute o aplicativo, selecione a guia Alunos e, em seguida, clique em Criar Novo.
Insira nomes e uma data. Tente inserir uma data inválida se o navegador permitir fazer isso. (Alguns navegadores
forçam o uso de um seletor de data.) Em seguida, clique em Criar para ver a mensagem de erro.
Essa é a validação do lado do servidor que você obtém por padrão; em um tutorial posterior, você verá como
adicionar atributos que gerarão o código para a validação do lado do cliente também. O código realçado a seguir
mostra a verificação de validação de modelo no método Create .

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
{
try
{
if (ModelState.IsValid)
{
_context.Add(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists " +
"see your system administrator.");
}
return View(student);
}

Altere a data para um valor válido e clique em Criar para ver o novo aluno ser exibido na página Índice.

Atualizar a página Editar


Em StudentController.cs, o método HttpGet Edit (aquele sem o atributo HttpPost ) usa o método
SingleOrDefaultAsync para recuperar a entidade Student selecionada, como você viu no método Details . Não é
necessário alterar esse método.
Código HttpPost Edit recomendado: ler e atualizar
Substitua o método de ação HttpPost Edit pelo código a seguir.

[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}
var studentToUpdate = await _context.Students.SingleOrDefaultAsync(s => s.ID == id);
if (await TryUpdateModelAsync<Student>(
studentToUpdate,
"",
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
try
{
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
}
return View(studentToUpdate);
}

Essas alterações implementam uma melhor prática de segurança para evitar o excesso de postagem. O scaffolder
gerou um atributo Bind e adicionou a entidade criada pelo associador de modelos ao conjunto de entidades com
um sinalizador Modified . Esse código não é recomendado para muitos cenários porque o atributo Bind limpa
os dados pré-existentes nos campos não listados no parâmetro Include .
O novo código lê a entidade existente e chama TryUpdateModel para atualizar os campos na entidade recuperada
com base na entrada do usuário nos dados de formulário postados. O controle automático de alterações do
Entity Framework define o sinalizador Modified nos campos alterados pela entrada de formulário. Quando o
método SaveChanges é chamado, o Entity Framework cria instruções SQL para atualizar a linha de banco de
dados. Os conflitos de simultaneidade são ignorados e somente as colunas de tabela que foram atualizadas pelo
usuário são atualizadas no banco de dados. (Um tutorial posterior mostra como lidar com conflitos de
simultaneidade.)
Como uma melhor prática para evitar o excesso de postagem, os campos que você deseja que sejam atualizáveis
pela página Editar estão na lista de permissões nos parâmetros TryUpdateModel . (A cadeia de caracteres vazia
antes da lista de campos na lista de parâmetros destina-se ao uso de um prefixo com os nomes de campos de
formulário.) Atualmente, não há nenhum campo extra que está sendo protegido, mas listar os campos que você
deseja que o associador de modelos associe garante que, se você adicionar campos ao modelo de dados no
futuro, eles serão automaticamente protegidos até que você adicione-os aqui de forma explícita.
Como resultado dessas alterações, a assinatura do método HttpPost Edit é a mesma do método HttpGet Edit ;
portanto, você já renomeou o método EditPost .
Código HttpPost Edit alternativo: criar e anexar
O código de edição HttpPost recomendado garante que apenas as colunas alteradas sejam atualizadas e preserva
os dados nas propriedades que você não deseja incluir para a associação de modelos. No entanto, a abordagem
de primeira leitura exige uma leitura de banco de dados extra e pode resultar em um código mais complexo para
lidar com conflitos de simultaneidade. Uma alternativa é anexar uma entidade criada pelo associador de modelos
ao contexto do EF e marcá-la como modificada. (Não atualize o projeto com esse código; ele é mostrado somente
para ilustrar uma abordagem opcional.)

public async Task<IActionResult> Edit(int id, [Bind("ID,EnrollmentDate,FirstMidName,LastName")] Student


student)
{
if (id != student.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
}
return View(student);
}

Use essa abordagem quando a interface do usuário da página da Web incluir todos os campos na entidade e
puder atualizar qualquer um deles.
O código gerado por scaffolding usa a abordagem "criar e anexar", mas apenas captura exceções
DbUpdateConcurrencyException e retorna códigos de erro 404. O exemplo mostrado captura qualquer exceção de
atualização de banco de dados e exibe uma mensagem de erro.
Estados da entidade
O contexto de banco de dados controla se as entidades em memória estão em sincronia com suas linhas
correspondentes no banco de dados, e essas informações determinam o que acontece quando você chama o
método SaveChanges . Por exemplo, quando você passa uma nova entidade para o método Add , o estado dessa
entidade é definido como Added . Em seguida, quando você chama o método SaveChanges , o contexto de banco
de dados emite um comando SQL INSERT.
Uma entidade pode estar em um dos seguintes estados:
Added. A entidade ainda não existe no banco de dados. O método SaveChanges emite uma instrução
INSERT.
Unchanged . Nada precisa ser feito com essa entidade pelo método SaveChanges . Ao ler uma entidade do
banco de dados, a entidade começa com esse status.
Modified . Alguns ou todos os valores de propriedade da entidade foram modificados. O método
SaveChanges emite uma instrução UPDATE.

Deleted . A entidade foi marcada para exclusão. O método SaveChanges emite uma instrução DELETE.
Detached . A entidade não está sendo controlada pelo contexto de banco de dados.
Em um aplicativo da área de trabalho, em geral, as alterações de estado são definidas automaticamente. Você lê
uma entidade e faz alterações em alguns de seus valores de propriedade. Isso faz com que seu estado da
entidade seja alterado automaticamente para Modified . Em seguida, quando você chama SaveChanges , o Entity
Framework gera uma instrução SQL UPDATE que atualiza apenas as propriedades reais que você alterou.
Em um aplicativo Web, o DbContext que inicialmente lê uma entidade e exibe seus dados a serem editados é
descartado depois que uma página é renderizada. Quando o método de ação HttpPost Edit é chamado, é feita
uma nova solicitação da Web e você tem uma nova instância do DbContext . Se você ler novamente a entidade
nesse novo contexto, simulará o processamento da área de trabalho.
Mas se você não desejar fazer a operação de leitura extra, precisará usar o objeto de entidade criado pelo
associador de modelos. A maneira mais simples de fazer isso é definir o estado da entidade como Modificado,
como é feito no código HttpPost Edit alternativo mostrado anteriormente. Em seguida, quando você chama
SaveChanges , o Entity Framework atualiza todas as colunas da linha de banco de dados, porque o contexto não
tem como saber quais propriedades foram alteradas.
Caso deseje evitar a abordagem de primeira leitura, mas também deseje que a instrução SQL UPDATE atualize
somente os campos que o usuário realmente alterar, o código será mais complexo. É necessário salvar os valores
originais de alguma forma (por exemplo, usando campos ocultos) para que eles estejam disponíveis quando o
método HttpPost Edit for chamado. Em seguida, você pode criar uma entidade Student usando os valores
originais, chamar o método Attach com a versão original da entidade, atualizar os valores da entidade para os
novos valores e, em seguida, chamar SaveChanges .
Testar a página Editar
Execute o aplicativo, selecione a guia Alunos e, em seguida, clique em um hiperlink Editar.

Altere alguns dos dados e clique em Salvar. A página Índice será aberta e você verá os dados alterados.

Atualizar a página Excluir


Em StudentController.cs, o código de modelo para o método HttpGet Delete usa o método
SingleOrDefaultAsync para recuperar a entidade Student selecionada, como você viu nos métodos Details e Edit.
No entanto, para implementar uma mensagem de erro personalizada quando a chamada a SaveChanges falhar,
você adicionará uma funcionalidade a esse método e à sua exibição correspondente.
Como você viu para operações de atualização e criação, as operações de exclusão exigem dois métodos de ação.
O método chamado em resposta a uma solicitação GET mostra uma exibição que dá ao usuário uma
oportunidade de aprovar ou cancelar a operação de exclusão. Se o usuário aprová-la, uma solicitação POST será
criada. Quando isso acontece, o método HttpPost Delete é chamado e, em seguida, esse método executa, de
fato, a operação de exclusão.
Você adicionará um bloco try-catch ao método HttpPost Delete para tratar os erros que podem ocorrer quando
o banco de dados é atualizado. Se ocorrer um erro, o método HttpPost Delete chamará o método HttpGet Delete,
passando a ele um parâmetro que indica que ocorreu um erro. Em seguida, o método HttpGet Delete exibe
novamente a página de confirmação, junto com a mensagem de erro, dando ao usuário a oportunidade de
cancelar ou tentar novamente.
Substitua o método de ação HttpGet Delete pelo código a seguir, que gerencia o relatório de erros.

public async Task<IActionResult> Delete(int? id, bool? saveChangesError = false)


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

var student = await _context.Students


.AsNoTracking()
.SingleOrDefaultAsync(m => m.ID == id);
if (student == null)
{
return NotFound();
}

if (saveChangesError.GetValueOrDefault())
{
ViewData["ErrorMessage"] =
"Delete failed. Try again, and if the problem persists " +
"see your system administrator.";
}

return View(student);
}

Este código aceita um parâmetro opcional que indica se o método foi chamado após uma falha ao salvar as
alterações. Esse parâmetro é falso quando o método HttpGet Delete é chamado sem uma falha anterior.
Quando ele é chamado pelo método HttpPost Delete em resposta a um erro de atualização de banco de dados,
o parâmetro é verdadeiro, e uma mensagem de erro é passada para a exibição.
A abordagem de primeira leitura para HttpPost Delete
Substitua o método de ação HttpPost Delete (chamado DeleteConfirmed ) pelo código a seguir, que executa a
operação de exclusão real e captura os erros de atualização de banco de dados.
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var student = await _context.Students
.AsNoTracking()
.SingleOrDefaultAsync(m => m.ID == id);
if (student == null)
{
return RedirectToAction(nameof(Index));
}

try
{
_context.Students.Remove(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction(nameof(Delete), new { id = id, saveChangesError = true });
}
}

Esse código recupera a entidade selecionada e, em seguida, chama o método Remove para definir o status da
entidade como Deleted . Quando SaveChanges é chamado, um comando SQL DELETE é gerado.
A abordagem "criar e anexar" para HttpPost Delete
Se a melhoria do desempenho de um aplicativo de alto volume for uma prioridade, você poderá evitar uma
consulta SQL desnecessária criando uma instância de uma entidade Student usando somente o valor de chave
primária e, em seguida, definindo o estado da entidade como Deleted . Isso é tudo o que o Entity Framework
precisa para excluir a entidade. (Não coloque esse código no projeto; ele está aqui apenas para ilustrar uma
alternativa.)

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
try
{
Student studentToDelete = new Student() { ID = id };
_context.Entry(studentToDelete).State = EntityState.Deleted;
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction(nameof(Delete), new { id = id, saveChangesError = true });
}
}

Se a entidade tiver dados relacionados, eles também deverão ser excluídos. Verifique se a exclusão em cascata
está configurada no banco de dados. Com essa abordagem para a exclusão de entidade, o EF talvez não perceba
que há entidades relacionadas a serem excluídas.
Atualizar a exibição Excluir
Em Views/Student/Delete.cshtml, adicione uma mensagem de erro entre o cabeçalho h2 e o cabeçalho h3,
conforme mostrado no seguinte exemplo:
<h2>Delete</h2>
<p class="text-danger">@ViewData["ErrorMessage"]</p>
<h3>Are you sure you want to delete this?</h3>

Execute o aplicativo, selecione a guia Alunos e, em seguida, clique em um hiperlink Excluir:

Clique em Excluir. A página Índice será exibida sem o aluno excluído. (Você verá um exemplo de código de
tratamento de erro em ação no tutorial sobre simultaneidade.)

Fechando conexões de banco de dados


Para liberar os recursos contidos em uma conexão de banco de dados, a instância de contexto precisa ser
descartada assim que possível quando você tiver terminado. A injeção de dependência interna do ASP.NET Core
cuida dessa tarefa para você.
Em Startup.cs, chame o método de extensão AddDbContext para provisionar a classe DbContext no contêiner de
DI do ASP.NET. Esse método define o tempo de vida do serviço como Scoped por padrão. Scoped significa que o
tempo de vida do objeto de contexto coincide com o tempo de vida da solicitação da Web, e o método Dispose
será chamado automaticamente ao final da solicitação da Web.

Manipulando transações
Por padrão, o Entity Framework implementa transações de forma implícita. Em cenários em que são feitas
alterações em várias linhas ou tabelas e, em seguida, SaveChanges é chamado, o Entity Framework verifica
automaticamente se todas as alterações tiveram êxito ou se falharam. Se algumas alterações forem feitas pela
primeira vez e, em seguida, ocorrer um erro, essas alterações serão revertidas automaticamente. Para cenários
em que você precisa de mais controle – por exemplo, se desejar incluir operações feitas fora do Entity Framework
em uma transação –, consulte Transações.

Consultas sem controle


Quando um contexto de banco de dados recupera linhas de tabela e cria objetos de entidade que as representam,
por padrão, ele controla se as entidades em memória estão em sincronia com o que está no banco de dados. Os
dados em memória atuam como um cache e são usados quando uma entidade é atualizada. Esse cache costuma
ser desnecessário em um aplicativo Web porque as instâncias de contexto são normalmente de curta duração
(uma nova é criada e descartada para cada solicitação) e o contexto que lê uma entidade normalmente é
descartado antes que essa entidade seja usada novamente.
Desabilite o controle de objetos de entidade em memória chamando o método AsNoTracking . Os cenários típicos
em que talvez você deseje fazer isso incluem os seguintes:
Durante o tempo de vida do contexto, não é necessário atualizar entidades nem que o EF carregue
automaticamente as propriedades de navegação com entidades recuperadas por consultas separadas.
Com frequência, essas condições são atendidas nos métodos de ação HttpGet de um controlador.
Você está executando uma consulta que recupera um volume grande de dados e apenas uma pequena
parte dos dados retornados será atualizada. Pode ser mais eficiente desativar o controle para a consulta
grande e executar uma consulta posteriormente para as poucas entidades que precisam ser atualizadas.
Você deseja anexar uma entidade para atualizá-la, mas anteriormente, recuperou a mesma entidade para
uma finalidade diferente. Como a entidade já está sendo controlada pelo contexto de banco de dados, não
é possível anexar a entidade que você deseja alterar. Uma maneira de lidar com essa situação é chamar
AsNoTracking na consulta anterior.

Para obter mais informações, consulte Controle vs. Sem controle.

Resumo
Agora, você tem um conjunto completo de páginas que executam operações CRUD simples para entidades
Student. No próximo tutorial, você expandirá a funcionalidade da página Índice adicionando classificação,
filtragem e paginação.

Anterior Próximo
ASP.NET Core MVC com EF Core – classificação,
filtro, paginação – 3 de 10
08/05/2018 • 26 min to read • Edit Online

Por Tom Dykstra e Rick Anderson


O aplicativo web de exemplo Contoso University demonstra como criar aplicativos web do ASP.NET Core MVC
usando o Entity Framework Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o
primeiro tutorial da série.
No tutorial anterior, você implementou um conjunto de páginas da Web para operações CRUD básicas para
entidades Student. Neste tutorial você adicionará as funcionalidades de classificação, filtragem e paginação à
página Índice de Alunos. Você também criará uma página que faz um agrupamento simples.
A ilustração a seguir mostra a aparência da página quando você terminar. Os títulos de coluna são links que o
usuário pode clicar para classificar por essa coluna. Clicar em um título de coluna alterna repetidamente entre a
ordem de classificação ascendente e descendente.

Adicionar links de classificação de coluna à página Índice de Alunos


Para adicionar uma classificação à página Índice de Alunos, você alterará o método Index do controlador Alunos
e adicionará o código à exibição Índice de Alunos.
Adicionar a funcionalidade de classificação ao método Index
Em StudentsController.cs, substitua o método Index pelo seguinte código:
public async Task<IActionResult> Index(string sortOrder)
{
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
var students = from s in _context.Students
select s;
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}
return View(await students.AsNoTracking().ToListAsync());
}

Esse código recebe um parâmetro sortOrder da cadeia de caracteres de consulta na URL. O valor de cadeia de
caracteres de consulta é fornecido pelo ASP.NET Core MVC como um parâmetro para o método de ação. O
parâmetro será uma cadeia de caracteres "Name" ou "Date", opcionalmente, seguido de um sublinhado e a cadeia
de caracteres "desc" para especificar a ordem descendente. A ordem de classificação crescente é padrão.
Na primeira vez que a página Índice é solicitada, não há nenhuma cadeia de caracteres de consulta. Os alunos são
exibidos em ordem ascendente por sobrenome, que é o padrão, conforme estabelecido pelo caso fall-through na
instrução switch . Quando o usuário clica em um hiperlink de título de coluna, o valor sortOrder apropriado é
fornecido na cadeia de caracteres de consulta.
Os dois elementos ViewData (NameSortParm e DateSortParm) são usados pela exibição para configurar os
hiperlinks de título de coluna com os valores de cadeia de caracteres de consulta apropriados.

public async Task<IActionResult> Index(string sortOrder)


{
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
var students = from s in _context.Students
select s;
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}
return View(await students.AsNoTracking().ToListAsync());
}

Essas são instruções ternárias. A primeira delas especifica que o parâmetro sortOrder é nulo ou vazio,
NameSortParm deve ser definido como "name_desc"; caso contrário, ele deve ser definido como uma cadeia de
caracteres vazia. Essas duas instruções permitem que a exibição defina os hiperlinks de título de coluna da
seguinte maneira:

ORDEM DE CLASSIFICAÇÃO ATUAL HIPERLINK DO SOBRENOME HIPERLINK DE DATA

Sobrenome ascendente descending ascending

Sobrenome descendente ascending ascending

Data ascendente ascending descending

Data descendente ascending ascending

O método usa o LINQ to Entities para especificar a coluna pela qual classificar. O código cria uma variável
IQueryable antes da instrução switch, modifica-a na instrução switch e chama o método ToListAsync após a
instrução switch . Quando você cria e modifica variáveis IQueryable , nenhuma consulta é enviada para o banco
de dados. A consulta não é executada até que você converta o objeto IQueryable em uma coleção chamando um
método, como ToListAsync . Portanto, esse código resulta em uma única consulta que não é executada até a
instrução return View .
Este código pode ficar detalhado com um grande número de colunas. O último tutorial desta série mostra como
escrever um código que permite que você passe o nome da coluna OrderBy em uma variável de cadeia de
caracteres.
Adicionar hiperlinks de título de coluna à exibição Índice de Alunos
Substitua o código em Views/Students/Index.cshtml pelo código a seguir para adicionar hiperlinks de título de
coluna. As linhas alteradas são realçadas.
@model IEnumerable<ContosoUniversity.Models.Student>

@{
ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
<a asp-action="Index" asp-route-
sortOrder="@ViewData["NameSortParm"]">@Html.DisplayNameFor(model => model.LastName)</a>
</th>
<th>
@Html.DisplayNameFor(model => model.FirstMidName)
</th>
<th>
<a asp-action="Index" asp-route-
sortOrder="@ViewData["DateSortParm"]">@Html.DisplayNameFor(model => model.EnrollmentDate)</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</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>

Esse código usa as informações nas propriedades ViewData para configurar hiperlinks com os valores de cadeia
de caracteres de consulta apropriados.
Execute o aplicativo, selecione a guia Alunos e, em seguida, clique nos títulos de coluna Sobrenome e Data de
Registro para verificar se a classificação funciona.
Adicionar uma Caixa de Pesquisa à página Índice de Alunos
Para adicionar a filtragem à página Índice de Alunos, você adicionará uma caixa de texto e um botão Enviar à
exibição e fará alterações correspondentes no método Index . A caixa de texto permitirá que você insira uma
cadeia de caracteres a ser pesquisada nos campos de nome e sobrenome.
Adicionar a funcionalidade de filtragem a método Index
Em StudentsController.cs, substitua o método Index pelo código a seguir (as alterações são realçadas).

public async Task<IActionResult> Index(string sortOrder, string searchString)


{
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
ViewData["CurrentFilter"] = searchString;

var students = from s in _context.Students


select s;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}
return View(await students.AsNoTracking().ToListAsync());
}
Você adicionou um parâmetro searchString ao método Index . O valor de cadeia de caracteres de pesquisa é
recebido em uma caixa de texto que você adicionará à exibição Índice. Você também adicionou à instrução LINQ
uma cláusula Where, que seleciona somente os alunos cujo nome ou sobrenome contém a cadeia de caracteres
de pesquisa. A instrução que adiciona a cláusula Where é executada somente se há um valor a ser pesquisado.

OBSERVAÇÃO
Aqui você está chamando o método Where em um objeto IQueryable , e o filtro será processado no servidor. Em alguns
cenários, você pode chamar o método Where como um método de extensão em uma coleção em memória. (Por exemplo,
suponha que você altere a referência a _context.Students , de modo que em vez de um DbSet do EF, ela referencie um
método de repositório que retorna uma coleção IEnumerable .) O resultado normalmente é o mesmo, mas em alguns
casos pode ser diferente.
Por exemplo, a implementação do .NET Framework do método Contains executa uma comparação que diferencia
maiúsculas de minúsculas por padrão, mas no SQL Server, isso é determinado pela configuração de agrupamento da
instância do SQL Server. Por padrão, essa configuração diferencia maiúsculas de minúsculas. Você pode chamar o método
ToUpper para fazer com que o teste diferencie maiúsculas de minúsculas de forma explícita: Where(s =>
s.LastName.ToUpper().Contains (searchString.ToUpper()). Isso garantirá que os resultados permaneçam os mesmos se você
alterar o código mais tarde para usar um repositório que retorna uma coleção IEnumerable em vez de um objeto
IQueryable . (Quando você chama o método Contains em uma coleção IEnumerable , obtém a implementação do
.NET Framework; quando chama-o em um objeto IQueryable , obtém a implementação do provedor de banco de dados.)
No entanto, há uma penalidade de desempenho para essa solução. O código ToUpper colocará uma função na cláusula
WHERE da instrução TSQL SELECT. Isso pode impedir que o otimizador use um índice. Considerando que o SQL geralmente
é instalado como não diferenciando maiúsculas e minúsculas, é melhor evitar o código ToUpper até você migrar para um
armazenamento de dados que diferencia maiúsculas de minúsculas.

Adicionar uma Caixa de Pesquisa à exibição Índice de Alunos


Em Views/Student/Index.cshtml, adicione o código realçado imediatamente antes da marcação de tabela de
abertura para criar uma legenda, uma caixa de texto e um botão Pesquisar.

<p>
<a asp-action="Create">Create New</a>
</p>

<form asp-action="Index" method="get">


<div class="form-actions no-color">
<p>
Find by name: <input type="text" name="SearchString" value="@ViewData["currentFilter"]" />
<input type="submit" value="Search" class="btn btn-default" /> |
<a asp-action="Index">Back to Full List</a>
</p>
</div>
</form>

<table class="table">

Esse código usa o auxiliar de marcação <form> para adicionar o botão e a caixa de texto de pesquisa. Por padrão,
o auxiliar de marcação <form> envia dados de formulário com um POST, o que significa que os parâmetros são
passados no corpo da mensagem HTTP e não na URL como cadeias de consulta. Quando você especifica HTTP
GET, os dados de formulário são passados na URL como cadeias de consulta, o que permite aos usuários marcar
a URL. As diretrizes do W3C recomendam o uso de GET quando a ação não resulta em uma atualização.
Execute o aplicativo, selecione a guia Alunos, insira uma cadeia de caracteres de pesquisa e clique em Pesquisar
para verificar se a filtragem está funcionando.
Observe que a URL contém a cadeia de caracteres de pesquisa.

http://localhost:5813/Students?SearchString=an

Se você marcar essa página, obterá a lista filtrada quando usar o indicador. A adição de method="get" à marcação
form é o que fez com que a cadeia de caracteres de consulta fosse gerada.

Neste estágio, se você clicar em um link de classificação de título de coluna perderá o valor de filtro inserido na
caixa Pesquisa. Você corrigirá isso na próxima seção.

Adicionar a funcionalidade de paginação à página Índice de Alunos


Para adicionar a paginação à página Índice de alunos, você criará uma classe PaginatedList que usa as
instruções Skip e Take para filtrar os dados no servidor, em vez de recuperar sempre todas as linhas da tabela.
Em seguida, você fará outras alterações no método Index e adicionará botões de paginação à exibição Index . A
ilustração a seguir mostra os botões de paginação.
Na pasta do projeto, crie PaginatedList.cs e, em seguida, substitua o código de modelo pelo código a seguir.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity
{
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }

public PaginatedList(List<T> items, int count, int pageIndex, int pageSize)


{
PageIndex = pageIndex;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);

this.AddRange(items);
}

public bool HasPreviousPage


{
get
{
return (PageIndex > 1);
}
}

public bool HasNextPage


{
get
{
return (PageIndex < TotalPages);
}
}

public static async Task<PaginatedList<T>> CreateAsync(IQueryable<T> source, int pageIndex, int


pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
return new PaginatedList<T>(items, count, pageIndex, pageSize);
}
}
}

O método CreateAsync nesse código usa o tamanho da página e o número da página e aplica as instruções
Skip e Take ao IQueryable . Quando ToListAsync for chamado no IQueryable , ele retornará uma Lista que
contém somente a página solicitada. As propriedades HasPreviousPage e HasNextPage podem ser usadas para
habilitar ou desabilitar os botões de paginação Anterior e Próximo.
Um método CreateAsync é usado em vez de um construtor para criar o objeto PaginatedList<T> , porque os
construtores não podem executar um código assíncrono.

Adicionar a funcionalidade de paginação ao método Index


Em StudentsController.cs, substitua o método Index pelo código a seguir.
public async Task<IActionResult> Index(
string sortOrder,
string currentFilter,
string searchString,
int? page)
{
ViewData["CurrentSort"] = sortOrder;
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";

if (searchString != null)
{
page = 1;
}
else
{
searchString = currentFilter;
}

ViewData["CurrentFilter"] = searchString;

var students = from s in _context.Students


select s;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}

int pageSize = 3;
return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), page ?? 1, pageSize));
}

Esse código adiciona um parâmetro de número de página, um parâmetro de ordem de classificação atual e um
parâmetro de filtro atual à assinatura do método.

public async Task<IActionResult> Index(


string sortOrder,
string currentFilter,
string searchString,
int? page)

Na primeira vez que a página for exibida, ou se o usuário ainda não tiver clicado em um link de paginação ou
classificação, todos os parâmetros serão nulos. Se um link de paginação receber um clique, a variável de página
conterá o número da página a ser exibido.
O elemento ViewData chamado CurrentSort fornece à exibição a ordem de classificação atual, pois isso precisa
ser incluído nos links de paginação para manter a ordem de classificação igual durante a paginação.
O elemento ViewData chamado CurrentFilter fornece à exibição a cadeia de caracteres de filtro atual. Esse valor
precisa ser incluído nos links de paginação para manter as configurações de filtro durante a paginação e precisa
ser restaurado para a caixa de texto quando a página é exibida novamente.
Se a cadeia de caracteres de pesquisa for alterada durante a paginação, a página precisará ser redefinida como 1,
porque o novo filtro pode resultar na exibição de dados diferentes. A cadeia de caracteres de pesquisa é alterada
quando um valor é inserido na caixa de texto e o botão Enviar é pressionado. Nesse caso, o parâmetro
searchString não é nulo.

if (searchString != null)
{
page = 1;
}
else
{
searchString = currentFilter;
}

Ao final do método Index , o método PaginatedList.CreateAsync converte a consulta de alunos em uma única
página de alunos de um tipo de coleção compatível com paginação. A única página de alunos é então passada
para a exibição.

return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), page ?? 1, pageSize));

O método PaginatedList.CreateAsync usa um número de página. Os dois pontos de interrogação representam o


operador de união de nulo. O operador de união de nulo define um valor padrão para um tipo que permite valor
nulo; a expressão (page ?? 1) significa retornar o valor de page se ele tiver um valor ou retornar 1 se page for
nulo.

Adicionar links de paginação à exibição Índice de Alunos


Em Views/Students/Index.cshtml, substitua o código existente pelo código a seguir. As alterações são realçadas.

@model PaginatedList<ContosoUniversity.Models.Student>

@{
ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
<a asp-action="Create">Create New</a>
</p>

<form asp-action="Index" method="get">


<div class="form-actions no-color">
<p>
Find by name: <input type="text" name="SearchString" value="@ViewData["currentFilter"]" />
<input type="submit" value="Search" class="btn btn-default" /> |
<a asp-action="Index">Back to Full List</a>
</p>
</div>
</form>

<table class="table">
<thead>
<tr>
<th>
<a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]" asp-route-
<a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]" asp-route-
currentFilter="@ViewData["CurrentFilter"]">Last Name</a>
</th>
<th>
First Name
</th>
<th>
<a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]" asp-route-
currentFilter="@ViewData["CurrentFilter"]">Enrollment Date</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</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>

@{
var prevDisabled = !Model.HasPreviousPage ? "disabled" : "";
var nextDisabled = !Model.HasNextPage ? "disabled" : "";
}

<a asp-action="Index"
asp-route-sortOrder="@ViewData["CurrentSort"]"
asp-route-page="@(Model.PageIndex - 1)"
asp-route-currentFilter="@ViewData["CurrentFilter"]"
class="btn btn-default @prevDisabled">
Previous
</a>
<a asp-action="Index"
asp-route-sortOrder="@ViewData["CurrentSort"]"
asp-route-page="@(Model.PageIndex + 1)"
asp-route-currentFilter="@ViewData["CurrentFilter"]"
class="btn btn-default @nextDisabled">
Next
</a>

A instrução @modelna parte superior da página especifica que a exibição agora obtém um objeto
PaginatedList<T> , em vez de um objeto List<T> .

Os links de cabeçalho de coluna usam a cadeia de caracteres de consulta para passar a cadeia de caracteres de
pesquisa atual para o controlador, de modo que o usuário possa classificar nos resultados do filtro:

<a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]" asp-route-currentFilter


="@ViewData["CurrentFilter"]">Enrollment Date</a>

Os botões de paginação são exibidos por auxiliares de marcação:


<a asp-action="Index"
asp-route-sortOrder="@ViewData["CurrentSort"]"
asp-route-page="@(Model.PageIndex - 1)"
asp-route-currentFilter="@ViewData["CurrentFilter"]"
class="btn btn-default @prevDisabled">
Previous
</a>

Execute o aplicativo e acesse a página Alunos.

Clique nos links de paginação em ordens de classificação diferentes para verificar se a paginação funciona. Em
seguida, insira uma cadeia de caracteres de pesquisa e tente fazer a paginação novamente para verificar se ela
também funciona corretamente com a classificação e filtragem.

Criar uma página Sobre que mostra as estatísticas de Alunos


Para a página Sobre do site da Contoso University, você exibirá quantos alunos se registraram para cada data de
registro. Isso exige agrupamento e cálculos simples nos grupos. Para fazer isso, você fará o seguinte:
Criar uma classe de modelo de exibição para os dados que você precisa passar para a exibição.
Modificar o método About no controlador Home.
Modificar a exibição Sobre.
Criar o modelo de exibição
Crie uma pasta SchoolViewModels na pasta Models.
Na nova pasta, adicione um arquivo de classe EnrollmentDateGroup.cs e substitua o código de modelo pelo
seguinte código:
using System;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models.SchoolViewModels
{
public class EnrollmentDateGroup
{
[DataType(DataType.Date)]
public DateTime? EnrollmentDate { get; set; }

public int StudentCount { get; set; }


}
}

Modificar o controlador Home


Em HomeController.cs, adicione o seguinte usando as instruções na parte superior do arquivo:

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Data;
using ContosoUniversity.Models.SchoolViewModels;

Adicione uma variável de classe ao contexto de banco de dados imediatamente após a chave de abertura da
classe e obtenha uma instância do contexto da DI do ASP.NET Core:

public class HomeController : Controller


{
private readonly SchoolContext _context;

public HomeController(SchoolContext context)


{
_context = context;
}

Substitua o método About pelo seguinte código:

public async Task<ActionResult> About()


{
IQueryable<EnrollmentDateGroup> data =
from student in _context.Students
group student by student.EnrollmentDate into dateGroup
select new EnrollmentDateGroup()
{
EnrollmentDate = dateGroup.Key,
StudentCount = dateGroup.Count()
};
return View(await data.AsNoTracking().ToListAsync());
}

A instrução LINQ agrupa as entidades de alunos por data de registro, calcula o número de entidades em cada
grupo e armazena os resultados em uma coleção de objetos de modelo de exibição EnrollmentDateGroup .

OBSERVAÇÃO
Na versão 1.0 do Entity Framework Core, todo o conjunto de resultados é retornado para o cliente e o agrupamento é feito
no cliente. Em alguns cenários, isso pode criar problemas de desempenho. Teste o desempenho com volumes de dados de
produção e, se necessário, use o SQL bruto para fazer o agrupamento no servidor. Para obter informações sobre como usar
o SQL bruto, veja o último tutorial desta série.
Modificar a exibição Sobre
Substitua o código no arquivo Views/Home/About.cshtml pelo seguinte código:

@model IEnumerable<ContosoUniversity.Models.SchoolViewModels.EnrollmentDateGroup>

@{
ViewData["Title"] = "Student Body Statistics";
}

<h2>Student Body Statistics</h2>

<table>
<tr>
<th>
Enrollment Date
</th>
<th>
Students
</th>
</tr>

@foreach (var item in Model)


{
<tr>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
@item.StudentCount
</td>
</tr>
}
</table>

Execute o aplicativo e acesse a página Sobre. A contagem de alunos para cada data de registro é exibida em uma
tabela.

Resumo
Neste tutorial, você viu como realizar classificação, filtragem, paginação e agrupamento. No próximo tutorial,
você aprenderá a manipular as alterações do modelo de dados usando migrações.

A N T E R IO R P R Ó X IM O
Migrações - Tutorial EF Core com ASP.NET Core
MVC(4 de 10)
03/03/2018 • 15 min to read • Edit Online

Por Tom Dykstra e Rick Anderson


O aplicativo Web de exemplo Contoso University demonstra como criar aplicativos Web ASP.NET Core MVC
usando o Entity Framework Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o
primeiro tutorial da série.
Neste tutorial, você começa usando o recurso de migrações do EF Core para o gerenciamento de alterações do
modelo de dados. Em tutoriais seguintes, você adicionará mais migrações conforme você alterar o modelo de
dados.

Introdução às migrações
Quando você desenvolve um novo aplicativo, o modelo de dados é alterado com frequência e, sempre que o
modelo é alterado, ele fica fora de sincronia com o banco de dados. Você começou estes tutoriais configurando
o Entity Framework para criar o banco de dados, caso ele não exista. Em seguida, sempre que você alterar o
modelo de dados – adicionar, remover, alterar classes de entidade ou alterar a classe DbContext –, poderá excluir
o banco de dados e o EF criará um novo que corresponde ao modelo e o propagará com os dados de teste.
Esse método de manter o banco de dados em sincronia com o modelo de dados funciona bem até que você
implante o aplicativo em produção. Quando o aplicativo é executado em produção, ele normalmente armazena
os dados que você deseja manter, e você não quer perder tudo sempre que fizer uma alteração, como a adição
de uma nova coluna. O recurso Migrações do EF Core resolve esse problema, permitindo que o EF atualize o
esquema de banco de dados em vez de criar um novo banco de dados.

Pacotes NuGet do Entity Framework Core para migrações


Para trabalhar com migrações, use o PMC (Console do Gerenciador de Pacotes) ou a CLI (interface de linha de
comando). Esses tutoriais mostram como usar comandos da CLI. Encontre informações sobre o PMC no final
deste tutorial.
As ferramentas do EF para a CLI (interface de linha de comando) são fornecidas em
Microsoft.EntityFrameworkCore.Tools.DotNet. Para instalar esse pacote, adicione-o à coleção
DotNetCliToolReference no arquivo .csproj, conforme mostrado. Observação: é necessário instalar este pacote
editando o arquivo .csproj; não é possível usar o comando install-package ou a GUI do Gerenciador de
Pacotes. Edite o arquivo .csproj clicando com o botão direito do mouse no nome do projeto no Gerenciador de
Soluções e selecionando Editar ContosoUniversity.csproj.

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

(Neste exemplo, os números de versão eram atuais no momento em que o tutorial foi escrito.)

Alterar a cadeia de conexão


No arquivo appsettings.json, altere o nome do banco de dados na cadeia de conexão para ContosoUniversity2
ou outro nome que você ainda não usou no computador que está sendo usado.

{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity2;Trusted_Connection=True;MultipleActiveResultSets=true"
},

Essa alteração configura o projeto, de modo que a primeira migração crie um novo banco de dados. Isso não é
necessário para começar a trabalhar com migrações, mas você verá posteriormente por que essa é uma boa
ideia.

OBSERVAÇÃO
Como alternativa à alteração do nome do banco de dados, você pode excluir o banco de dados. Use o SSOX (Pesquisador
de Objetos do SQL Server) ou o comando database drop da CLI:

dotnet ef database drop

A seção a seguir explica como executar comandos da CLI.

Criar uma migração inicial


Salve as alterações e compile o projeto. Em seguida, abra uma janela Comando e navegue para a pasta do
projeto. Esta é uma maneira rápida de fazer isso:
No Gerenciador de Soluções, clique com o botão direito do mouse no projeto e escolha Abrir no
Explorador de Arquivos no menu de contexto.

Insira "cmd" na barra de endereços e pressione Enter.


Insira o seguinte comando na janela de comando:

dotnet ef migrations add InitialCreate

Você verá uma saída semelhante à seguinte na janela Comando:

info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
User profile is available. Using 'C:\Users\username\AppData\Local\ASP.NET\DataProtection-Keys' as key
repository and Windows DPAPI to encrypt keys at rest.
info: Microsoft.EntityFrameworkCore.Infrastructure[100403]
Entity Framework Core 2.0.0-rtm-26452 initialized 'SchoolContext' using provider
'Microsoft.EntityFrameworkCore.SqlServer' with options: None
Done. To undo this action, use 'ef migrations remove'

OBSERVAÇÃO
Se você receber uma mensagem de erro Nenhum comando "dotnet-ef" executável correspondente encontrado, consulte
esta postagem no blog para ajudar a solucionar o problema.

Se você receber uma mensagem de erro "Não é possível acessar o arquivo... ContosoUniversity.dll porque ele
está sendo usado por outro processo", localize o ícone do IIS Express na Bandeja do Sistema do Windows,
clique com o botão direito do mouse nele e, em seguida, clique em ContosoUniversity > Parar Site.

Examinar os métodos Up e Down


Quando você executou o comando migrations add , o EF gerou o código que criará o banco de dados do zero.
Esse código está localizado na pasta Migrations, no arquivo chamado <timestamp>_InitialCreate.cs. O método
Up da classe InitialCreate cria as tabelas de banco de dados que correspondem aos conjuntos de entidades
do modelo de dados, e o método Down exclui-as, conforme mostrado no exemplo a seguir.
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Course",
columns: table => new
{
CourseID = table.Column<int>(nullable: false),
Credits = table.Column<int>(nullable: false),
Title = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Course", x => x.CourseID);
});

// Additional code not shown


}

protected override void Down(MigrationBuilder migrationBuilder)


{
migrationBuilder.DropTable(
name: "Enrollment");
// Additional code not shown
}
}

As migrações chamam o método Up para implementar as alterações do modelo de dados para uma migração.
Quando você insere um comando para reverter a atualização, as Migrações chamam o método Down .
Esse código destina-se à migração inicial que foi criada quando você inseriu o comando
migrations add InitialCreate . O parâmetro de nome da migração ("InitialCreate" no exemplo) é usado para o
nome do arquivo e pode ser o que você desejar. É melhor escolher uma palavra ou frase que resume o que está
sendo feito na migração. Por exemplo, você pode nomear uma migração posterior "AddDepartmentTable".
Se você criou a migração inicial quando o banco de dados já existia, o código de criação de banco de dados é
gerado, mas ele não precisa ser executado porque o banco de dados já corresponde ao modelo de dados.
Quando você implantar o aplicativo em outro ambiente no qual o banco de dados ainda não existe, esse código
será executado para criar o banco de dados; portanto, é uma boa ideia testá-lo primeiro. É por isso que você
alterou o nome do banco de dados na cadeia de conexão anteriormente – para que as migrações possam criar
um novo do zero.

Examinar o instantâneo do modelo de dados


As migrações também criam um instantâneo do esquema de banco de dados atual em
Migrations/SchoolContextModelSnapshot.cs. Esta é a aparência do código:
[DbContext(typeof(SchoolContext))]
partial class SchoolContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
modelBuilder
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452")
.HasAnnotation("SqlServer:ValueGenerationStrategy",
SqlServerValueGenerationStrategy.IdentityColumn);

modelBuilder.Entity("ContosoUniversity.Models.Course", b =>
{
b.Property<int>("CourseID");

b.Property<int>("Credits");

b.Property<string>("Title");

b.HasKey("CourseID");

b.ToTable("Course");
});

// Additional code for Enrollment and Student tables not shown

modelBuilder.Entity("ContosoUniversity.Models.Enrollment", b =>
{
b.HasOne("ContosoUniversity.Models.Course", "Course")
.WithMany("Enrollments")
.HasForeignKey("CourseID")
.OnDelete(DeleteBehavior.Cascade);

b.HasOne("ContosoUniversity.Models.Student", "Student")
.WithMany("Enrollments")
.HasForeignKey("StudentID")
.OnDelete(DeleteBehavior.Cascade);
});
}
}

Como o esquema de banco de dados atual é representado no código, o EF Core não precisa interagir com o
banco de dados para criar migrações. Quando você adiciona uma migração, o EF determina o que foi alterado,
comparando o modelo de dados com o arquivo de instantâneo. O EF interage com o banco de dados somente
quando é necessário atualizar o banco de dados.
O arquivo de instantâneo precisa ser mantido em sincronia com as migrações que o criam, de modo que não
seja possível remover uma migração apenas excluindo o arquivo chamado <timestamp>_<migrationname>.cs.
Se você excluir esse arquivo, as migrações restantes ficarão fora de sincronia com o arquivo de instantâneo do
banco de dados. Para excluir a última migração adicionada, use o comando dotnet ef migrations remove.

Aplicar a migração ao banco de dados


Na janela Comando, insira o comando a seguir para criar o banco de dados e tabelas nele.

dotnet ef database update

A saída do comando é semelhante ao comando migrations add , exceto que os logs para os comandos SQL que
configuram o banco de dados são exibidos. A maioria dos logs é omitida na seguinte saída de exemplo. Se você
preferir não ver esse nível de detalhe em mensagens de log, altere o nível de log no arquivo
appsettings.Development.json. Para obter mais informações, consulte Introdução ao log.
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
User profile is available. Using 'C:\Users\username\AppData\Local\ASP.NET\DataProtection-Keys' as key
repository and Windows DPAPI to encrypt keys at rest.
info: Microsoft.EntityFrameworkCore.Infrastructure[100403]
Entity Framework Core 2.0.0-rtm-26452 initialized 'SchoolContext' using provider
'Microsoft.EntityFrameworkCore.SqlServer' with options: None
info: Microsoft.EntityFrameworkCore.Database.Command[200101]
Executed DbCommand (467ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
CREATE DATABASE [ContosoUniversity2];
info: Microsoft.EntityFrameworkCore.Database.Command[200101]
Executed DbCommand (20ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE [__EFMigrationsHistory] (
[MigrationId] nvarchar(150) NOT NULL,
[ProductVersion] nvarchar(32) NOT NULL,
CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
);

<logs omitted for brevity>

info: Microsoft.EntityFrameworkCore.Database.Command[200101]
Executed DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20170816151242_InitialCreate', N'2.0.0-rtm-26452');
Done.

Use o Pesquisador de Objetos do SQL Server para inspecionar o banco de dados como você fez no primeiro
tutorial. Você observará a adição de uma tabela __EFMigrationsHistory que controla quais migrações foram
aplicadas ao banco de dados. Exiba os dados dessa tabela e você verá uma linha para a primeira migração. (O
último log no exemplo de saída da CLI anterior mostra a instrução INSERT que cria essa linha.)
Execute o aplicativo para verificar se tudo ainda funciona como antes.

CLI (interface de linha de comando) vs. PMC (Console do


Gerenciador de Pacotes)
As ferramentas do EF para gerenciamento de migrações estão disponíveis por meio dos comandos da CLI do
.NET Core ou de cmdlets do PowerShell na janela PMC (Console do Gerenciador de Pacotes) do Visual Studio.
Este tutorial mostra como usar a CLI, mas você poderá usar o PMC se preferir.
Os comandos do EF para os comandos do PMC estão no pacote Microsoft.EntityFrameworkCore.Tools. Este
pacote já está incluído no metapacote Microsoft.AspNetCore.All e, portanto, não é necessário instalá-lo.
Importante: esse não é o mesmo pacote que é instalado para a CLI com a edição do arquivo .csproj. O nome
deste termina com Tools , ao contrário do nome do pacote da CLI que termina com Tools.DotNet .
Para obter mais informações sobre os comandos da CLI, consulte CLI do .NET Core.
Para obter mais informações sobre os comandos do PMC, consulte Console do Gerenciador de Pacotes (Visual
Studio).

Resumo
Neste tutorial, você viu como criar e aplicar sua primeira migração. No próximo tutorial, você começará
examinando tópicos mais avançados com a expansão do modelo de dados. Ao longo do processo, você criará e
aplicará migrações adicionais.

Anterior Próximo
Criar um modelo de dados complexos - Tutorial do
EF Core com ASP.NET Core MVC (5 de 10)
03/03/2018 • 52 min to read • Edit Online

Por Tom Dykstra e Rick Anderson


O aplicativo Web de exemplo Contoso University demonstra como criar aplicativos Web ASP.NET Core MVC
usando o Entity Framework Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o
primeiro tutorial da série.
Nos tutoriais anteriores, você trabalhou com um modelo de dados simples composto por três entidades. Neste
tutorial, você adicionará mais entidades e relações e personalizará o modelo de dados especificando formatação,
validação e regras de mapeamento de banco de dados.
Quando terminar, as classes de entidade formarão o modelo de dados concluído mostrado na seguinte
ilustração:
Personalizar o modelo de dados usando atributos
Nesta seção, você verá como personalizar o modelo de dados usando atributos que especificam formatação,
validação e regras de mapeamento de banco de dados. Em seguida, em várias seções a seguir, você criará o
modelo de dados Escola completo com a adição de atributos às classes já criadas e criação de novas classes para
os demais tipos de entidade no modelo.
O atributo DataType
Para datas de registro de alunos, todas as páginas da Web atualmente exibem a hora junto com a data, embora
tudo o que você deseje exibir nesse campo seja a data. Usando atributos de anotação de dados, você pode fazer
uma alteração de código que corrigirá o formato de exibição em cada exibição que mostra os dados. Para ver um
exemplo de como fazer isso, você adicionará um atributo à propriedade EnrollmentDate na classe Student .
Em Models/Student.cs, adicione uma instrução using ao namespace System.ComponentModel.DataAnnotations e
adicione os atributos DataType e DisplayFormat à EnrollmentDate propriedade, conforme mostrado no seguinte
exemplo:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }

public ICollection<Enrollment> Enrollments { get; set; }


}
}

O atributo DataType é usado para especificar um tipo de dados mais específico do que o tipo intrínseco de
banco de dados. Nesse caso, apenas desejamos acompanhar a data, não a data e a hora. A Enumeração
DataType fornece muitos tipos de dados, como Date, Time, PhoneNumber, Currency, EmailAddress e muito
mais. O atributo DataType também pode permitir que o aplicativo forneça automaticamente recursos
específicos a um tipo. Por exemplo, um link mailto: pode ser criado para DataType.EmailAddress e um seletor
de data pode ser fornecido para DataType.Date em navegadores que dão suporte a HTML5. O atributo
DataType emite atributos data- HTML 5 (pronunciados “data dash”) que são reconhecidos pelos navegadores
HTML 5. Os atributos DataType não fornecem nenhuma validação.
DataType.Date não especifica o formato da data exibida. Por padrão, o campo de dados é exibido de acordo com
os formatos padrão com base nas CultureInfo do servidor.
O atributo DisplayFormat é usado para especificar explicitamente o formato de data:

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

A configuração ApplyFormatInEditMode especifica que a formatação também deve ser aplicada quando o valor é
exibido em uma caixa de texto para edição. (Talvez você não deseje ter isso em alguns campos – por exemplo,
para valores de moeda, provavelmente, você não deseja exibir o símbolo de moeda na caixa de texto para
edição.)
Use Você pode usar o atributo DisplayFormat por si só, mas geralmente é uma boa ideia usar o atributo
DataType também. O atributo DataType transmite a semântica dos dados, ao invés de apresentar como
renderizá-lo em uma tela, e oferece os seguintes benefícios que você não obtém com DisplayFormat :
O navegador pode habilitar os recursos do HTML5 (por exemplo, mostrar um controle de calendário, o
símbolo de moeda apropriado à localidade, links de email, uma validação de entrada do lado do cliente,
etc.).
Por padrão, o navegador renderizará os dados usando o formato correto de acordo com a localidade.
Para obter mais informações, consulte a documentação do auxiliar de marcação <input>.
Execute o aplicativo, acesse a página Índice de Alunos e observe que as horas não são mais exibidas nas datas de
registro. O mesmo será verdadeiro para qualquer exibição que usa o modelo Aluno.

O atributo StringLength
Você também pode especificar regras de validação de dados e mensagens de erro de validação usando atributos.
O atributo StringLength define o tamanho máximo do banco de dados e fornece validação do lado do cliente e
do lado do servidor para o ASP.NET MVC. Você também pode especificar o tamanho mínimo da cadeia de
caracteres nesse atributo, mas o valor mínimo não tem nenhum impacto sobre o esquema de banco de dados.
Suponha que você deseje garantir que os usuários não insiram mais de 50 caracteres em um nome. Para
adicionar essa limitação, adicione atributos StringLength às propriedades LastName e FirstMidName , conforme
mostrado no seguinte exemplo:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }

public ICollection<Enrollment> Enrollments { get; set; }


}
}

O atributo StringLength não impedirá que um usuário insira um espaço em branco em um nome. Use o
atributo RegularExpression para aplicar restrições à entrada. Por exemplo, o seguinte código exige que o
primeiro caractere esteja em maiúscula e os caracteres restantes estejam em ordem alfabética:

[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]

O atributo MaxLength fornece uma funcionalidade semelhante ao atributo StringLength , mas não fornece a
validação do lado do cliente.
Agora, o modelo de banco de dados foi alterado de uma forma que exige uma alteração no esquema de banco
de dados. Você usará migrações para atualizar o esquema sem perda dos dados que podem ter sido adicionados
ao banco de dados usando a interface do usuário do aplicativo.
Salve as alterações e compile o projeto. Em seguida, abra a janela Comando na pasta do projeto e insira os
seguintes comandos:

dotnet ef migrations add MaxLengthOnNames

dotnet ef database update

O comando migrations add alerta que pode ocorrer perda de dados, pois a alteração torna o tamanho máximo
mais curto para duas colunas. As migrações criam um arquivo chamado <timeStamp>_MaxLengthOnNames.cs.
Esse arquivo contém o código no método Up que atualizará o banco de dados para que ele corresponda ao
modelo de dados atual. O comando database update executou esse código.
O carimbo de data/hora prefixado ao nome do arquivo de migrações é usado pelo Entity Framework para
ordenar as migrações. Crie várias migrações antes de executar o comando de atualização de banco de dados e,
em seguida, todas as migrações são aplicadas na ordem em que foram criadas.
Execute o aplicativo, selecione a guia Alunos, clique em Criar Novo e insira um nome com mais de 50
caracteres. Quando você clica em Criar, a validação do lado do cliente mostra uma mensagem de erro.
O atributo Column
Você também pode usar atributos para controlar como as classes e propriedades são mapeadas para o banco de
dados. Suponha que você tenha usado o nome FirstMidName para o campo de nome porque o campo também
pode conter um sobrenome. Mas você deseja que a coluna do banco de dados seja nomeada FirstName , pois os
usuários que escreverão consultas ad hoc no banco de dados estão acostumados com esse nome. Para fazer
esse mapeamento, use o atributo Column .
O atributo Column especifica que quando o banco de dados for criado, a coluna da tabela Student que é
mapeada para a propriedade FirstMidName será nomeada FirstName . Em outras palavras, quando o código se
referir a Student.FirstMidName , os dados serão obtidos ou atualizados na coluna FirstName da tabela Student .
Se você não especificar nomes de coluna, elas receberão o mesmo nome da propriedade.
No arquivo Student.cs, adicione uma instrução using a System.ComponentModel.DataAnnotations.Schema e
adicione o atributo de nome de coluna à propriedade FirstMidName , conforme mostrado no seguinte código
realçado:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }

public ICollection<Enrollment> Enrollments { get; set; }


}
}

A adição do atributo Column altera o modelo que dá suporte ao SchoolContext e, portanto, ele não corresponde
ao banco de dados.
Salve as alterações e compile o projeto. Em seguida, abra a janela Comando na pasta do projeto e insira os
seguintes comandos para criar outra migração:

dotnet ef migrations add ColumnFirstName

dotnet ef database update

No Pesquisador de Objetos do SQL Server, abra o designer de tabela Aluno clicando duas vezes na tabela
Aluno.

Antes de você aplicar as duas primeiras migrações, as colunas de nome eram do tipo nvarchar(MAX). Agora elas
são nvarchar(50) e o nome da coluna foi alterado de FirstMidName para FirstName.
OBSERVAÇÃO
Se você tentar compilar antes de concluir a criação de todas as classes de entidade nas seções a seguir, poderá receber
erros do compilador.

Alterações finais na entidade Student

Em Models/Student.cs, substitua o código que você adicionou anteriormente pelo código a seguir. As alterações
são realçadas.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}

public ICollection<Enrollment> Enrollments { get; set; }


}
}

O atributo Required
O atributo Required torna as propriedades de nome campos obrigatórios. O atributo Required não é
necessário para tipos que permitem valor nulo como tipos de valor (DateTime, int, double, float, etc.). Tipos que
não podem ser nulos são tratados automaticamente como campos obrigatórios.
Remova o atributo Required e substitua-o por um parâmetro de tamanho mínimo para o atributo StringLength
:

[Display(Name = "Last Name")]


[StringLength(50, MinimumLength=1)]
public string LastName { get; set; }

O atributo Display
O atributo Display especifica que a legenda para as caixas de texto deve ser "Nome", "Sobrenome", "Nome
Completo" e "Data de Registro", em vez do nome de a propriedade em cada instância (que não tem nenhum
espaço entre as palavras).
A propriedade calculada FullName
FullName é uma propriedade calculada que retorna um valor criado pela concatenação de duas outras
propriedades. Portanto, ela tem apenas um acessador get e nenhuma coluna FullName será gerada no banco de
dados.

Criar a entidade Instructor

Crie Models/Instructor.cs, substituindo o código de modelo com o seguinte código:


using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Instructor
{
public int ID { get; set; }

[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }

[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }

[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }

[Display(Name = "Full Name")]


public string FullName
{
get { return LastName + ", " + FirstMidName; }
}

public ICollection<CourseAssignment> CourseAssignments { get; set; }


public OfficeAssignment OfficeAssignment { get; set; }
}
}

Observe que várias propriedades são as mesmas nas entidades Student e Instructor. No tutorial Implementando
a herança mais adiante nesta série, você refatorará esse código para eliminar a redundância.
Coloque vários atributos em uma linha, de modo que você também possa escrever os atributos HireDate da
seguinte maneira:

[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",


ApplyFormatInEditMode = true)]

As propriedades de navegação CourseAssignments e OfficeAssignment


As propriedades CourseAssignments e OfficeAssignment são propriedades de navegação.
Um instrutor pode ministrar qualquer quantidade de cursos e, portanto, CourseAssignments é definido como
uma coleção.

public ICollection<CourseAssignment> CourseAssignments { get; set; }

Se uma propriedade de navegação pode conter várias entidades, o tipo precisa ser uma lista na qual as entradas
podem ser adicionadas, excluídas e atualizadas. Especifique ICollection<T> ou um tipo, como List<T> ou
HashSet<T> . Se você especificar ICollection<T> , o EF criará uma coleção HashSet<T> por padrão.

O motivo pelo qual elas são entidades CourseAssignment é explicado abaixo na seção sobre relações muitos para
muitos.
As regras de negócio do Contoso Universidade indicam que um instrutor pode ter apenas, no máximo, um
escritório; portanto, a propriedade OfficeAssignment contém uma única entidade OfficeAssignment (que pode
ser nulo se o escritório não está atribuído).

public OfficeAssignment OfficeAssignment { get; set; }

Criar a entidade OfficeAssignment

Crie Models/OfficeAssignment.cs com o seguinte código:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class OfficeAssignment
{
[Key]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }

public Instructor Instructor { get; set; }


}
}

O atributo Key
Há uma relação um para zero ou um entre as entidades Instructor e OfficeAssignment. Uma atribuição de
escritório existe apenas em relação ao instrutor ao qual ela é atribuída e, portanto, sua chave primária também é
a chave estrangeira da entidade Instructor. Mas o Entity Framework não pode reconhecer InstructorID
automaticamente como a chave primária dessa entidade porque o nome não segue a convenção de
nomenclatura ID ou classnameID. Portanto, o atributo Key é usado para identificá-la como a chave:

[Key]
public int InstructorID { get; set; }

Você também pode usar o atributo Key se a entidade tem sua própria chave primária, mas você deseja atribuir
um nome de propriedade que não seja classnameID ou ID.
Por padrão, o EF trata a chave como não gerada pelo banco de dados porque a coluna destina-se a uma relação
de identificação.
A propriedade de navegação Instructor
A entidade Instructor tem uma propriedade de navegação OfficeAssignment que permite valor nulo (porque um
instrutor pode não ter uma atribuição de escritório), e a entidade OfficeAssignment tem uma propriedade de
navegação Instructor que não permite valor nulo (porque uma atribuição de escritório não pode existir sem
um instrutor – InstructorID não permite valor nulo). Quando uma entidade Instructor tiver uma entidade
OfficeAssignment relacionada, cada entidade terá uma referência à outra em sua propriedade de navegação.
Você pode colocar um atributo [Required] na propriedade de navegação Instructor para especificar que deve
haver um instrutor relacionado, mas não precisa fazer isso porque a chave estrangeira InstructorID (que
também é a chave para esta tabela) não permite valor nulo.

Modificar a entidade Course

Em Models/Course.cs, substitua o código que você adicionou anteriormente pelo código a seguir. As alterações
são realçadas.

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }

[StringLength(50, MinimumLength = 3)]


public string Title { get; set; }

[Range(0, 5)]
public int Credits { get; set; }

public int DepartmentID { get; set; }

public Department Department { get; set; }


public ICollection<Enrollment> Enrollments { get; set; }
public ICollection<CourseAssignment> CourseAssignments { get; set; }
}
}

A entidade de curso tem uma propriedade de chave estrangeira DepartmentID que aponta para a entidade
Department relacionada e ela tem uma propriedade de navegação Department .
O Entity Framework não exige que você adicione uma propriedade de chave estrangeira ao modelo de dados
quando você tem uma propriedade de navegação para uma entidade relacionada. O EF cria chaves estrangeiras
no banco de dados sempre que elas são necessárias e cria automaticamente propriedades de sombra para elas.
No entanto, ter a chave estrangeira no modelo de dados pode tornar as atualizações mais simples e mais
eficientes. Por exemplo, quando você busca uma entidade de curso a ser editada, a entidade Department é nula
se você não carregá-la; portanto, quando você atualiza a entidade de curso, você precisa primeiro buscar a
entidade Department. Quando a propriedade de chave estrangeira DepartmentID está incluída no modelo de
dados, você não precisa buscar a entidade Department antes da atualização.
O atributo DatabaseGenerated
O atributo DatabaseGenerated com o parâmetro None na propriedade CourseID especifica que os valores de
chave primária são fornecidos pelo usuário, em vez de serem gerados pelo banco de dados.

[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }

Por padrão, o Entity Framework pressupõe que os valores de chave primária sejam gerados pelo banco de
dados. É isso que você quer na maioria dos cenários. No entanto, para entidades Course, você usará um número
de curso especificado pelo usuário como uma série 1000 de um departamento, uma série 2000 para outro
departamento e assim por diante.
O atributo DatabaseGenerated também pode ser usado para gerar valores padrão, como no caso de colunas de
banco de dados usadas para registrar a data em que uma linha foi criada ou atualizada. Para obter mais
informações, consulte Propriedades geradas.
Propriedades de navegação e de chave estrangeira
As propriedades de navegação e de chave estrangeira na entidade Course refletem as seguintes relações:
Um curso é atribuído a um departamento e, portanto, há uma propriedade de chave estrangeira DepartmentID e
de navegação Department pelas razões mencionadas acima.

public int DepartmentID { get; set; }


public Department Department { get; set; }

Um curso pode ter qualquer quantidade de estudantes inscritos; portanto, a propriedade de navegação
Enrollments é uma coleção:

public ICollection<Enrollment> Enrollments { get; set; }

Um curso pode ser ministrado por vários instrutores; portanto, a propriedade de navegação CourseAssignments
é uma coleção (o tipo CourseAssignment é explicado posteriormente):

public ICollection<CourseAssignment> CourseAssignments { get; set; }

Criar a entidade Department

Crie Models/Department.cs com o seguinte código:


using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }

[StringLength(50, MinimumLength = 3)]


public string Name { get; set; }

[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }

[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }

public int? InstructorID { get; set; }

public Instructor Administrator { get; set; }


public ICollection<Course> Courses { get; set; }
}
}

O atributo Column
Anteriormente, você usou o atributo Column para alterar o mapeamento de nome de coluna. No código da
entidade Department, o atributo Column está sendo usado para alterar o mapeamento de tipo de dados SQL, do
modo que a coluna seja definida usando o tipo de dinheiro do SQL Server no banco de dados:

[Column(TypeName="money")]
public decimal Budget { get; set; }

O mapeamento de coluna geralmente não é necessário, pois o Entity Framework escolhe o tipo de dados do
SQL Server apropriado com base no tipo CLR definido para a propriedade. O tipo decimal CLR é mapeado
para um tipo decimal SQL Server. Mas, nesse caso, você sabe que a coluna armazenará os valores de moeda e
o tipo de dados dinheiro é mais apropriado para isso.
Propriedades de navegação e de chave estrangeira
As propriedades de navegação e de chave estrangeira refletem as seguintes relações:
Um departamento pode ou não ter um administrador, e um administrador é sempre um instrutor. Portanto, a
propriedade InstructorID é incluída como a chave estrangeira na entidade Instructor e um ponto de
interrogação é adicionado após a designação de tipo int para marcar a propriedade como uma propriedade
que permite valor nulo. A propriedade de navegação é chamada Administrator , mas contém uma entidade
Instructor:

public int? InstructorID { get; set; }


public Instructor Administrator { get; set; }

Um departamento pode ter vários cursos e, portanto, há uma propriedade de navegação Courses:
public ICollection<Course> Courses { get; set; }

OBSERVAÇÃO
Por convenção, o Entity Framework habilita a exclusão em cascata para chaves estrangeiras que não permitem valor nulo e
em relações muitos para muitos. Isso pode resultar em regras de exclusão em cascata circular, que causará uma exceção
quando você tentar adicionar uma migração. Por exemplo, se você não definiu a propriedade Department.InstructorID
como uma propriedade que permite valor nulo, o EF configura uma regra de exclusão em cascata para excluir o instrutor
quando você exclui o departamento, que não é o que você deseja que aconteça. Se as regras de negócio exigissem que a
propriedade InstructorID não permitisse valor nulo, você precisaria usar a seguinte instrução de API fluente para
desabilitar a exclusão em cascata na relação:

modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)

Modificar a entidade Enrollment

Em Models/Enrollment.cs, substitua o código que você adicionou anteriormente pelo seguinte código:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}

public class Enrollment


{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
[DisplayFormat(NullDisplayText = "No grade")]
public Grade? Grade { get; set; }

public Course Course { get; set; }


public Student Student { get; set; }
}
}

Propriedades de navegação e de chave estrangeira


As propriedades de navegação e de chave estrangeira refletem as seguintes relações:
Um registro destina-se a um único curso e, portanto, há uma propriedade de chave estrangeira CourseID e uma
propriedade de navegação Course :

public int CourseID { get; set; }


public Course Course { get; set; }

Um registro destina-se a um único aluno e, portanto, há uma propriedade de chave estrangeira StudentID e
uma propriedade de navegação Student :

public int StudentID { get; set; }


public Student Student { get; set; }

Relações muitos para muitos


Há uma relação muitos para muitos entre as entidades Student e Course e a entidade Enrollment funciona como
uma tabela de junção muitos para muitos com conteúdo no banco de dados. "Com conteúdo" significa que a
tabela Registro contém dados adicionais além das chaves estrangeiras para as tabelas unidas (nesse caso, uma
chave primária e uma propriedade Grade).
A ilustração a seguir mostra a aparência dessas relações em um diagrama de entidades. (Esse diagrama foi
gerado usando o Entity Framework Power Tools para o EF 6.x; a criação do diagrama não faz parte do tutorial,
mas está apenas sendo usada aqui como uma ilustração.)

Cada linha de relação tem um 1 em uma extremidade e um asterisco (*) na outra, indicando uma relação um
para muitos.
Se a tabela Registro não incluir informações de nota, ela apenas precisará conter as duas chaves estrangeiras
CourseID e StudentID. Nesse caso, ela será uma tabela de junção de muitos para muitos sem conteúdo (ou uma
tabela de junção pura) no banco de dados. As entidades Instructor e Course têm esse tipo de relação muitos
para muitos e a próxima etapa é criar uma classe de entidade para funcionar como uma tabela de junção sem
conteúdo.
(O EF 6.x é compatível com tabelas de junção implícita para relações muitos para muitos, ao contrário do EF
Core. Para obter mais informações, confira a discussão no repositório GitHub do EF Core.)

A entidade CourseAssignment

Crie Models/CourseAssignment.cs com o seguinte código:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class CourseAssignment
{
public int InstructorID { get; set; }
public int CourseID { get; set; }
public Instructor Instructor { get; set; }
public Course Course { get; set; }
}
}

Unir nomes de entidade


Uma tabela de junção é necessária no banco de dados para a relação muitos para muitos de Instrutor para
Cursos, e ela precisa ser representada por um conjunto de entidades. É comum nomear uma entidade de junção
EntityName1EntityName2 , que, nesse caso, será CourseInstructor . No entanto, recomendamos que você escolha
um nome que descreve a relação. Modelos de dados começam simples e aumentam, com junções não referentes
a conteúdo obtendo com frequência o conteúdo mais tarde. Se você começar com um nome descritivo de
entidade, não precisará alterar o nome posteriormente. O ideal é que a entidade de junção tenha seu próprio
nome natural (possivelmente, uma única palavra) no domínio de negócios. Por exemplo, Manuais e Clientes
podem ser vinculados por meio de Classificações. Para essa relação, CourseAssignment é uma escolha melhor
que CourseInstructor .
Chave composta
Como as chaves estrangeiras não permitem valor nulo e, juntas, identificam exclusivamente cada linha da tabela,
não é necessário ter uma chave primária. As propriedades InstructorID e CourseID devem funcionar como uma
chave primária composta. A única maneira de identificar chaves primárias compostas no EF é usando a API
fluente (isso não pode ser feito por meio de atributos). Você verá como configurar a chave primária composta na
próxima seção.
A chave composta garante que, embora você possa ter várias linhas para um curso e várias linhas para um
instrutor, não poderá ter várias linhas para o mesmo instrutor e curso. A entidade de junção Enrollment define
sua própria chave primária e, portanto, duplicatas desse tipo são possíveis. Para evitar essas duplicatas, você
pode adicionar um índice exclusivo nos campos de chave estrangeira ou configurar Enrollment com uma chave
primária composta semelhante a CourseAssignment . Para obter mais informações, consulte Índices.
Atualizar o contexto de banco de dados
Adicione o seguinte código realçado ao arquivo Data/SchoolContext.cs:

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}

public DbSet<Course> Courses { get; set; }


public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
public DbSet<CourseAssignment> CourseAssignments { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
modelBuilder.Entity<Department>().ToTable("Department");
modelBuilder.Entity<Instructor>().ToTable("Instructor");
modelBuilder.Entity<OfficeAssignment>().ToTable("OfficeAssignment");
modelBuilder.Entity<CourseAssignment>().ToTable("CourseAssignment");

modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
}
}
}

Esse código adiciona novas entidades e configura a chave primária composta da entidade CourseAssignment.

Alternativa de API fluente para atributos


O código no método OnModelCreating da classe DbContext usa a API fluente para configurar o comportamento
do EF. A API é chamada "fluente" porque costuma ser usada pelo encadeamento de uma série de chamadas de
método em uma única instrução, como neste exemplo da documentação do EF Core:

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}

Neste tutorial, você usa a API fluente somente para o mapeamento de banco de dados que não é possível fazer
com atributos. No entanto, você também pode usar a API fluente para especificar a maioria das regras de
formatação, validação e mapeamento que pode ser feita por meio de atributos. Alguns atributos como
MinimumLength não podem ser aplicados com a API fluente. Conforme mencionado anteriormente,
MinimumLength não altera o esquema; apenas aplica uma regra de validação do lado do cliente e do servidor.

Alguns desenvolvedores preferem usar a API fluente exclusivamente para que possam manter suas classes de
entidade "limpas". Combine atributos e a API fluente se desejar. Além disso, há algumas personalizações que
podem ser feitas apenas com a API fluente, mas em geral, a prática recomendada é escolher uma dessas duas
abordagens e usar isso com o máximo de consistência possível. Se usar as duas, observe que sempre que
houver um conflito, a API fluente substituirá atributos.
Para obter mais informações sobre atributos vs. API fluente, consulte Métodos de configuração.

Diagrama de entidade mostrando relações


A ilustração a seguir mostra o diagrama criado pelo Entity Framework Power Tools para o modelo Escola
concluído.

Além das linhas de relação um-para-muitos (1 para *), você pode ver a linha de relação um para zero ou um (1
para 0..1) entre as entidades Instructor e OfficeAssignment e a linha de relação zero-ou-um-para-muitos (0..1
para *) entre as entidades Instructor e Department.

Propagar o banco de dados com os dados de teste


Substitua o código no arquivo Data/DbInitializer.cs pelo código a seguir para fornecer dados de semente para as
novas entidades criadas.

using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
//context.Database.EnsureCreated();

// Look for any students.


if (context.Students.Any())
{
return; // DB has been seeded
}

var students = new Student[]


{
new Student { FirstMidName = "Carson", LastName = "Alexander",
EnrollmentDate = DateTime.Parse("2010-09-01") },
new Student { FirstMidName = "Meredith", LastName = "Alonso",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Arturo", LastName = "Anand",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Gytis", LastName = "Barzdukas",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Yan", LastName = "Li",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Peggy", LastName = "Justice",
EnrollmentDate = DateTime.Parse("2011-09-01") },
new Student { FirstMidName = "Laura", LastName = "Norman",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Nino", LastName = "Olivetto",
EnrollmentDate = DateTime.Parse("2005-09-01") }
};

foreach (Student s in students)


{
context.Students.Add(s);
}
context.SaveChanges();

var instructors = new Instructor[]


{
new Instructor { FirstMidName = "Kim", LastName = "Abercrombie",
HireDate = DateTime.Parse("1995-03-11") },
new Instructor { FirstMidName = "Fadi", LastName = "Fakhouri",
HireDate = DateTime.Parse("2002-07-06") },
new Instructor { FirstMidName = "Roger", LastName = "Harui",
HireDate = DateTime.Parse("1998-07-01") },
new Instructor { FirstMidName = "Candace", LastName = "Kapoor",
HireDate = DateTime.Parse("2001-01-15") },
new Instructor { FirstMidName = "Roger", LastName = "Zheng",
HireDate = DateTime.Parse("2004-02-12") }
};

foreach (Instructor i in instructors)


{
context.Instructors.Add(i);
}
context.SaveChanges();

var departments = new Department[]


{
new Department { Name = "English", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Abercrombie").ID },
new Department { Name = "Mathematics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID },
new Department { Name = "Engineering", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Harui").ID },
new Department { Name = "Economics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID }
};

foreach (Department d in departments)


{
context.Departments.Add(d);
}
context.SaveChanges();

var courses = new Course[]


{
new Course {CourseID = 1050, Title = "Chemistry", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID
},
new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
},
new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
},
new Course {CourseID = 1045, Title = "Calculus", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
},
new Course {CourseID = 3141, Title = "Trigonometry", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
},
new Course {CourseID = 2021, Title = "Composition", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
},
new Course {CourseID = 2042, Title = "Literature", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
},
};

foreach (Course c in courses)


{
context.Courses.Add(c);
}
context.SaveChanges();

var officeAssignments = new OfficeAssignment[]


{
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID,
Location = "Smith 17" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Harui").ID,
Location = "Gowan 27" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID,
Location = "Thompson 304" },
};

foreach (OfficeAssignment o in officeAssignments)


{
context.OfficeAssignments.Add(o);
}
context.SaveChanges();

var courseInstructors = new CourseAssignment[]


var courseInstructors = new CourseAssignment[]
{
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Kapoor").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Fakhouri").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Literature" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
},
};

foreach (CourseAssignment ci in courseInstructors)


{
context.CourseAssignments.Add(ci);
}
context.SaveChanges();

var enrollments = new Enrollment[]


{
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
Grade = Grade.A
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
Grade = Grade.C
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
Grade = Grade.B
},
new Enrollment {
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "