You are on page 1of 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 == "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();
}
}
}

Como você viu no primeiro tutorial, a maioria do código apenas cria novos objetos de entidade e carrega dados
de exemplo em propriedades, conforme necessário, para teste. Observe como as relações muitos para muitos
são tratadas: o código cria relações com a criação de entidades nos conjuntos de entidades de junção
Enrollments e CourseAssignment .

Adicionar uma migração


Salve as alterações e compile o projeto. Em seguida, abra a janela Comando na pasta do projeto e insira o
comando migrations add (não execute ainda o comando de atualização de banco de dados):

dotnet ef migrations add ComplexDataModel

Você receberá 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 você tiver tentado executar o comando database update neste ponto (não faça isso ainda), receberá o
seguinte erro:

A instrução ALTER TABLE entrou em conflito com a restrição FOREIGN KEY


"FK_dbo.Course_dbo.Department_DepartmentID". O conflito ocorreu no banco de dados
"ContosoUniversity", tabela "dbo.Departamento", coluna 'DepartmentID'.

Às vezes, quando você executa migrações com os dados existentes, precisa inserir dados de stub no banco de
dados para atender às restrições de chave estrangeira. O código gerado no método Up adiciona uma chave
estrangeira DepartmentID que não permite valor nulo para a tabela Curso. Se já houver linhas na tabela Curso
quando o código for executado, a operação AddColumn falhará, porque o SQL Server não saberá qual valor deve
ser colocado na coluna que não pode ser nulo. Para este tutorial, você executará a migração em um novo banco
de dados, mas em um aplicativo de produção, você precisará fazer com que a migração manipule os dados
existentes. Portanto, as instruções a seguir mostram um exemplo de como fazer isso.
Para fazer a migração funcionar com os dados existentes, você precisa alterar o código para fornecer à nova
coluna um valor padrão e criar um departamento de stub chamado "Temp" para atuar como o departamento
padrão. Como resultado, as linhas Curso existentes serão todas relacionadas ao departamento "Temp" após a
execução do método Up .
Abra o arquivo {timestamp }_ComplexDataModel.cs.
Comente a linha de código que adiciona a coluna DepartmentID à tabela Curso.

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 seguinte código realçado após o código que cria a tabela Departamento:
migrationBuilder.CreateTable(
name: "Department",
columns: table => new
{
DepartmentID = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy",
SqlServerValueGenerationStrategy.IdentityColumn),
Budget = table.Column<decimal>(type: "money", nullable: false),
InstructorID = table.Column<int>(nullable: true),
Name = table.Column<string>(maxLength: 50, nullable: true),
StartDate = table.Column<DateTime>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Department", x => x.DepartmentID);
table.ForeignKey(
name: "FK_Department_Instructor_InstructorID",
column: x => x.InstructorID,
principalTable: "Instructor",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
});

migrationBuilder.Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00,


GETDATE())");
// Default value for FK points to department created above, with
// defaultValue changed to 1 in following AddColumn statement.

migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
nullable: false,
defaultValue: 1);

Em um aplicativo de produção, você escreverá código ou scripts para adicionar linhas Departamento e
relacionar linhas Curso às novas linhas Departamento. Em seguida, você não precisará mais do departamento
"Temp" ou do valor padrão na coluna Course.DepartmentID.
Salve as alterações e compile o projeto.

Alterar a cadeia de conexão e atualizar o banco de dados


Agora, você tem novo código na classe DbInitializer que adiciona dados de semente para as novas entidades a
um banco de dados vazio. Para fazer com que o EF crie um novo banco de dados vazio, altere o nome do banco
de dados na cadeia de conexão em appsettings.json para ContosoUniversity3 ou para outro nome que você
ainda não usou no computador que está sendo usado.

{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity3;Trusted_Connection=True;MultipleActiveResultSets=true"
},

Salve as alterações em appsettings.json.


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

Depois que você tiver alterado o nome do banco de dados ou excluído o banco de dados, execute o comando
database update na janela Comando para executar as migrações.

dotnet ef database update

Execute o aplicativo para fazer com que o método DbInitializer.Initialize execute e popule o novo banco de
dados.
Abra o banco de dados no SSOX, como você fez anteriormente, e expanda o nó Tabelas para ver se todas as
tabelas foram criadas. (Se você ainda tem o SSOX aberto do momento anterior, clique no botão Atualizar.)

Execute o aplicativo para disparar o código inicializador que propaga o banco de dados.
Clique com o botão direito do mouse na tabela CourseAssignment e selecione Exibir Dados para verificar se
existem dados nela.
Resumo
Agora você tem um modelo de dados mais complexo e um banco de dados correspondente. No tutorial a seguir,
você aprenderá mais sobre como acessar dados relacionados.

Anterior Próximo
ASP.NET Core MVC com o EF Core – ler dados
relacionados – 6 de 10
08/05/2018 • 25 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ê concluiu o modelo de dados Escola. Neste tutorial, você lerá e exibirá dados
relacionados – ou seja, os dados que o Entity Framework carrega nas propriedades de navegação.
As ilustrações a seguir mostram as páginas com as quais você trabalhará.
Carregamento adiantado, explícito e lento de dados relacionados
Há várias maneiras pelas quais um software ORM (Object-Relational Mapping), como o Entity Framework, pode
carregar dados relacionados nas propriedades de navegação de uma entidade:
Carregamento adiantado. Quando a entidade é lida, os dados relacionados são recuperados com ela.
Normalmente, isso resulta em uma única consulta de junção que recupera todos os dados necessários.
Especifique o carregamento adiantado no Entity Framework Core usando os métodos Include e
ThenInclude .

Recupere alguns dos dados em consultas separadas e o EF "corrigirá" as propriedades de navegação. Ou


seja, o EF adiciona de forma automática as entidades recuperadas separadamente no local em que
pertencem nas propriedades de navegação de entidades recuperadas anteriormente. Para a consulta que
recupera dados relacionados, você pode usar o método Load em vez de um método que retorna uma
lista ou um objeto, como ToList ou Single .

Carregamento explícito. Quando a entidade é lida pela primeira vez, os dados relacionados não são
recuperados. Você escreve o código que recupera os dados relacionados se eles são necessários. Como
no caso do carregamento adiantado com consultas separadas, o carregamento explícito resulta no envio
de várias consultas ao banco de dados. A diferença é que, com o carregamento explícito, o código
especifica as propriedades de navegação a serem carregadas. No Entity Framework Core 1.1, você pode
usar o método Load para fazer o carregamento explícito. Por exemplo:

Carregamento lento. Quando a entidade é lida pela primeira vez, os dados relacionados não são
recuperados. No entanto, na primeira vez que você tenta acessar uma propriedade de navegação, os
dados necessários para essa propriedade de navegação são recuperados automaticamente. Uma consulta
é enviada ao banco de dados sempre que você tenta obter dados de uma propriedade de navegação pela
primeira vez. O Entity Framework Core 1.0 não dá suporte ao carregamento lento.
Considerações sobre desempenho
Se você sabe que precisa de dados relacionados para cada entidade recuperada, o carregamento adiantado
costuma oferecer o melhor desempenho, porque uma única consulta enviada para o banco de dados é
geralmente mais eficiente do que consultas separadas para cada entidade recuperada. Por exemplo, suponha
que cada departamento tenha dez cursos relacionados. O carregamento adiantado de todos os dados
relacionados resultará em apenas uma única consulta ( junção) e uma única viagem de ida e volta para o banco
de dados. Uma consulta separada para cursos de cada departamento resultará em onze viagens de ida e volta
para o banco de dados. As viagens de ida e volta extras para o banco de dados são especialmente prejudiciais ao
desempenho quando a latência é alta.
Por outro lado, em alguns cenários, consultas separadas são mais eficientes. O carregamento adiantado de todos
os dados relacionados em uma consulta pode fazer com que uma junção muito complexa seja gerada, que o
SQL Server não consegue processar com eficiência. Ou se precisar acessar as propriedades de navegação de
uma entidade somente para um subconjunto de um conjunto de entidades que está sendo processado, consultas
separadas poderão ter um melhor desempenho, pois o carregamento adiantado de tudo desde o início recupera
mais dados do que você precisa. Se o desempenho for crítico, será melhor testar o desempenho das duas
maneiras para fazer a melhor escolha.

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 do departamento
ao qual o curso é atribuído. Para exibir o nome do departamento atribuído em uma lista de cursos, você precisa
obter a propriedade Name da entidade Department que está na propriedade de navegação Course.Department .
Crie um controlador chamado CoursesController para o tipo de entidade Course, usando as mesmas opções
para o scaffolder Controlador MVC com exibições, usando o Entity Framework que você usou
anteriormente para o controlador Alunos, conforme mostrado na seguinte ilustração:

Abra CoursesController.cs e examine o método Index . O scaffolding automático especificou o carregamento


adiantado para a propriedade de navegação Department usando o método Include .
Substitua o método Index pelo seguinte código, que usa um nome mais apropriado para o IQueryable que
retorna as entidades Course ( courses em vez de schoolContext ):

public async Task<IActionResult> Index()


{
var courses = _context.Courses
.Include(c => c.Department)
.AsNoTracking();
return View(await courses.ToListAsync());
}

Abra Views/Courses/Index.cshtml e substitua o código de modelo pelo código a seguir. As alterações são
realçadas:
@model IEnumerable<ContosoUniversity.Models.Course>

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

<h2>Courses</h2>

<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.CourseID)
</th>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Credits)
</th>
<th>
@Html.DisplayNameFor(model => model.Department)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<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-action="Edit" asp-route-id="@item.CourseID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.CourseID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.CourseID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Você fez as seguintes alterações no código gerado por 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 e você deseja mostrá-la.
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.

Criar uma página Instrutores que mostra Cursos e Registros


Nesta seção, você criará um controlador e uma exibição para a entidade Instructor para exibir a página
Instrutores:
Essa página lê e exibe dados relacionados das seguintes maneiras:
A lista de instrutores exibe dados relacionados da entidade OfficeAssignment. As entidades Instructor e
OfficeAssignment estão em uma relação um para zero ou um. Você usará o carregamento adiantado para
as entidades OfficeAssignment. Conforme explicado anteriormente, o carregamento adiantado é
geralmente mais eficiente quando você precisa dos dados relacionados para todas as linhas recuperadas
da tabela primária. Nesse caso, você deseja exibir atribuições de escritório para todos os instrutores
exibidos.
Quando o usuário seleciona um instrutor, as entidades Course relacionadas são exibidas. As entidades
Instructor e Course estão em uma relação muitos para muitos. Você usará o carregamento adiantado
para as entidades Course e suas entidades Department relacionadas. Nesse caso, consultas separadas
podem ser mais eficientes porque você precisa de cursos somente para o instrutor selecionado. No
entanto, 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, dados relacionados do conjunto de entidades Enrollments são
exibidos. As entidades Course e Enrollment estão em uma relação um para muitos. Você usará consultas
separadas para entidades Enrollment e suas entidades Student relacionadas.
Criar um modelo de exibição para a exibição Índice de Instrutor
A página Instrutores mostra dados de três tabelas diferentes. Portanto, você criará um modelo de exibição que
inclui três propriedades, cada uma contendo os dados de uma das tabelas.
Na pasta SchoolViewModels, crie InstructorIndexData.cs e substitua o código existente pelo 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; }
}
}

Criar exibições e o controlador Instrutor


Crie um controlador Instrutores com ações de leitura/gravação do EF, conforme mostrado na seguinte
ilustração:

Abra InstructorsController.cs e adicione um usando a instrução para o namespace ViewModels:

using ContosoUniversity.Models.SchoolViewModels;

Substitua o método Index pelo código a seguir para fazer o carregamento adiantado de dados relacionados e
colocá-los no modelo de exibição.
public async Task<IActionResult> Index(int? id, int? courseID)
{
var viewModel = new InstructorIndexData();
viewModel.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();

if (id != null)
{
ViewData["InstructorID"] = id.Value;
Instructor instructor = viewModel.Instructors.Where(
i => i.ID == id.Value).Single();
viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
}

if (courseID != null)
{
ViewData["CourseID"] = courseID.Value;
viewModel.Enrollments = viewModel.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}

return View(viewModel);
}

O método aceita dados de rota opcionais ( id ) e um parâmetro de cadeia de caracteres de consulta ( courseID )
que fornece os valores de ID do curso e do instrutor selecionados. Os parâmetros são fornecidos pelos
hiperlinks Selecionar na página.
O código começa com a criação de uma instância do modelo de exibição e colocando-a na lista de instrutores. O
código especifica o carregamento adiantado para as propriedades de navegação Instructor.OfficeAssignment e
Instructor.CourseAssignments . Dentro da propriedade CourseAssignments , a propriedade Course é carregada e,
dentro dela, as propriedades Enrollments e Department são carregadas e, dentro de cada entidade Enrollment ,
a propriedade Student é carregada.

viewModel.Instructors = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();

Como a exibição sempre exige a entidade OfficeAssignment, é mais eficiente buscar isso na mesma consulta. As
entidades Course são necessárias quando um instrutor é selecionado na página da Web; portanto, uma única
consulta é melhor do que várias consultas apenas se a página é exibida com mais frequência com um curso
selecionado do que sem ele.
O código repete CourseAssignments e Course porque você precisa de duas propriedades de Course . A primeira
cadeia de caracteres de chamadas ThenInclude obtém CourseAssignment.Course , Course.Enrollments e
Enrollment.Student .

viewModel.Instructors = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();

Nesse ponto do código, outro ThenInclude se refere às propriedades de navegação de Student , que não é
necessário. Mas a chamada a Include é reiniciada com propriedades Instructor e, portanto, você precisa
passar pela cadeia novamente, dessa vez, especificando Course.Department em vez de Course.Enrollments .

viewModel.Instructors = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();

O código a seguir é executado quando o instrutor é selecionado. 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)
{
ViewData["InstructorID"] = id.Value;
Instructor instructor = viewModel.Instructors.Where(
i => i.ID == id.Value).Single();
viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
}

O método Where retorna uma coleção, mas nesse caso, os critérios passado para esse método resultam no
retorno de apenas uma única entidade Instructor. O método Single converte a coleção em uma única entidade
Instructor, que fornece acesso à propriedade CourseAssignments dessa entidade. A propriedade
CourseAssignments contém entidades CourseAssignment , das quais você deseja apenas entidades Course
relacionadas.
Use o método Single em uma coleção quando souber que a coleção terá apenas um item. O método Single
gera uma exceção se a coleção passada para ele 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. No entanto, nesse caso,
isso ainda resultará em uma exceção (da tentativa de encontrar uma propriedade Courses em uma referência
nula), e a mensagem de exceção menos claramente indicará a causa do problema. Quando você chama o
método Single , também pode passar a condição Where, em vez de chamar o método Where separadamente:

.Single(i => i.ID == id.Value)

Em vez de:

.Where(I => i.ID == id.Value).Single()

Em seguida, se um curso foi selecionado, o curso selecionado é recuperado na lista de cursos no modelo de
exibição. Em seguida, a propriedade Enrollments do modelo de exibição é carregada com as entidades
Enrollment da propriedade de navegação Enrollments desse curso.

if (courseID != null)
{
ViewData["CourseID"] = courseID.Value;
viewModel.Enrollments = viewModel.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}

Modificar a exibição Índice de Instrutor


Em Views/Instructors/Index.cshtml, substitua o código de modelo pelo código a seguir. As alterações são
realçadas.
@model ContosoUniversity.Models.SchoolViewModels.InstructorIndexData

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

<h2>Instructors</h2>

<p>
<a asp-action="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.Instructors)
{
string selectedRow = "";
if (item.ID == (int?)ViewData["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-action="Index" asp-route-id="@item.ID">Select</a> |
<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>

Você fez as seguintes alterações no código existente:


Alterou a classe de modelo para InstructorIndexData .
Alterou o título de página de Índice para 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.ID == (int?)ViewData["InstructorID"])
{
selectedRow = "success";
}
<tr class="@selectedRow">

Adicionou um novo hiperlink rotulado Selecionar imediatamente antes dos outros links em cada linha, o
que faz com que a ID do instrutor selecionado seja enviada para o método Index .

<a asp-action="Index" asp-route-id="@item.ID">Select</a> |

Execute o aplicativo e selecione a guia Instrutores. A página exibe a propriedade Location das entidades
OfficeAssignment relacionadas e uma célula de tabela vazia quando não há nenhuma entidade
OfficeAssignment relacionada.

No arquivo Views/Instructors/Index.cshtml, após o elemento de tabela de fechamento (ao final do arquivo),


adicione o código a seguir. Esse código exibe uma lista de cursos relacionados a um instrutor quando um
instrutor é selecionado.

@if (Model.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.Courses)


{
string selectedRow = "";
if (item.CourseID == (int?)ViewData["CourseID"])
{
selectedRow = "success";
}
<tr class="@selectedRow">
<td>
@Html.ActionLink("Select", "Index", new { courseID = item.CourseID })
</td>
<td>
@item.CourseID
</td>
<td>
@item.Title
</td>
<td>
@item.Department.Name
</td>
</tr>
}

</table>
}

Esse código lê a propriedade Courses do modelo de exibição para exibir uma lista de cursos. Também fornece
um hiperlink Selecionar que envia a ID do curso selecionado para o método de ação Index .
Atualize a página e selecione um instrutor. Agora, você verá uma grade que exibe os cursos atribuídos ao
instrutor selecionado, e para cada curso, verá o nome do departamento atribuído.
Após o bloco de código que você acabou de adicionar, adicione o código a seguir. Isso exibe uma lista dos alunos
que estão registrados em um curso quando esse curso é selecionado.

@if (Model.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.Enrollments)
{
<tr>
<td>
@item.Student.FullName
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
}

Esse código lê a propriedade Enrollments do modelo de exibição para exibir uma lista dos alunos registrados no
curso.
Atualize a página novamente e selecione um instrutor. Em seguida, selecione um curso para ver a lista de alunos
registrados e suas notas.

Carregamento explícito
Quando você recuperou a lista de instrutores em InstructorsController.cs, você especificou o carregamento
adiantado para a propriedade de navegação CourseAssignments .
Suponha que os usuários esperados raramente desejem ver registros em um curso e um instrutor selecionados.
Nesse caso, talvez você deseje carregar os dados de registro somente se eles forem solicitados. Para ver um
exemplo de como fazer carregamento explícito, substitua o método Index pelo código a seguir, que remove o
carregamento adiantado para Enrollments e carrega essa propriedade de forma explícita. As alterações de
código são realçadas.
public async Task<IActionResult> Index(int? id, int? courseID)
{
var viewModel = new InstructorIndexData();
viewModel.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.OrderBy(i => i.LastName)
.ToListAsync();

if (id != null)
{
ViewData["InstructorID"] = id.Value;
Instructor instructor = viewModel.Instructors.Where(
i => i.ID == id.Value).Single();
viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
}

if (courseID != null)
{
ViewData["CourseID"] = courseID.Value;
var selectedCourse = viewModel.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();
}
viewModel.Enrollments = selectedCourse.Enrollments;
}

return View(viewModel);
}

O novo código remove as chamadas do método ThenInclude para dados de registro do código que recupera as
entidades do instrutor. Se um curso e um instrutor são selecionados, o código realçado recupera entidades
Enrollment para o curso selecionado e as entidades Student para cada Enrollment.
Execute que o aplicativo, acesse a página Índice de Instrutores agora e você não verá nenhuma diferença no que
é exibido na página, embora você tenha alterado a maneira como os dados são recuperados.

Resumo
Agora, você usou o carregamento adiantado com uma consulta e com várias consultas para ler dados
relacionados nas propriedades de navegação. No próximo tutorial, você aprenderá a atualizar dados
relacionados.

A N T E R IO R P R Ó X IM O
ASP.NET Core MVC com o EF Core – atualizar dados
relacionados – 7 de 10
08/05/2018 • 30 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ê exibiu dados relacionados; neste tutorial, você atualizará dados relacionados pela
atualização dos campos de chave estrangeira e das propriedades de navegação.
As ilustrações a seguir mostram algumas das páginas com as quais você trabalhará.
Personalizar as páginas Criar e Editar dos cursos
Quando uma nova entidade de curso é criada, ela precisa ter uma relação com um departamento existente. Para
facilitar isso, o código gerado por scaffolding inclui métodos do controlador e exibições Criar e Editar que incluem
uma lista suspensa para seleção do departamento. A lista suspensa define a propriedade de chave estrangeira
Course.DepartmentID , e isso é tudo o que o Entity Framework precisa para carregar a propriedade de navegação
Department com a entidade Department apropriada. Você usará o código gerado por scaffolding, mas o alterará
ligeiramente para adicionar tratamento de erro e classificação à lista suspensa.
Em CoursesController.cs, exclua os quatro métodos Create e Edit e substitua-os pelo seguinte código:

public IActionResult Create()


{
PopulateDepartmentsDropDownList();
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("CourseID,Credits,DepartmentID,Title")] Course course)
{
if (ModelState.IsValid)
{
_context.Add(course);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}

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


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

var course = await _context.Courses


.AsNoTracking()
.SingleOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}

var courseToUpdate = await _context.Courses


.SingleOrDefaultAsync(c => c.CourseID == id);

if (await TryUpdateModelAsync<Course>(courseToUpdate,
"",
c => c.Credits, c => c.DepartmentID, c => c.Title))
{
try
{
await _context.SaveChangesAsync();
}
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 RedirectToAction(nameof(Index));
}
PopulateDepartmentsDropDownList(courseToUpdate.DepartmentID);
return View(courseToUpdate);
}

Após o método HttpPost Edit , crie um novo método que carrega informações de departamento para a lista
suspensa.

private void PopulateDepartmentsDropDownList(object selectedDepartment = null)


{
var departmentsQuery = from d in _context.Departments
orderby d.Name
select d;
ViewBag.DepartmentID = new SelectList(departmentsQuery.AsNoTracking(), "DepartmentID", "Name",
selectedDepartment);
}

O método PopulateDepartmentsDropDownList obtém uma lista de todos os departamentos classificados por nome,
cria uma coleção SelectList para uma lista suspensa e passa a coleção para a exibição em ViewBag . O método
aceita o parâmetro selectedDepartment opcional que permite que o código de chamada especifique o item que
será selecionado quando a lista suspensa for renderizada. A exibição passará o nome "DepartmentID" para o
auxiliar de marcação <select> e o auxiliar então saberá que deve examinar o objeto ViewBag em busca de uma
SelectList chamada "DepartmentID".

O método HttpGet Create chama o método PopulateDepartmentsDropDownList sem definir o item selecionado,
porque um novo curso do departamento ainda não foi estabelecido:

public IActionResult Create()


{
PopulateDepartmentsDropDownList();
return View();
}
O método HttpGet Edit define o item selecionado, com base na ID do departamento já atribuído ao curso que
está sendo editado:

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


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

var course = await _context.Courses


.AsNoTracking()
.SingleOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}

Os métodos HttpPost para Create e Edit também incluem o código que define o item selecionado quando eles
exibem novamente a página após um erro. Isso garante que quando a página for exibida novamente para mostrar
a mensagem de erro, qualquer que tenha sido o departamento selecionado permaneça selecionado.
Adicionar .AsNoTracking aos métodos Details e Delete
Para otimizar o desempenho das páginas Detalhes do Curso e Excluir, adicione chamadas AsNoTracking aos
métodos HttpGet Details e Delete .

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


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

var course = await _context.Courses


.Include(c => c.Department)
.AsNoTracking()
.SingleOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}

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

var course = await _context.Courses


.Include(c => c.Department)
.AsNoTracking()
.SingleOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}

return View(course);
}

Modificar as exibições Curso


Em Views/Courses/Create.cshtml, adicione uma opção "Selecionar Departamento" à lista suspensa
Departamento, altere a legenda de DepartmentID para Departamento e adicione uma mensagem de
validação.

<div class="form-group">
<label asp-for="Department" class="control-label"></label>
<select asp-for="DepartmentID" class="form-control" asp-items="ViewBag.DepartmentID">
<option value="">-- Select Department --</option>
</select>
<span asp-validation-for="DepartmentID" class="text-danger" />

Em Views/Courses/Edit.cshtml, faça a mesma alteração no campo Departamento que você acabou de fazer em
Create.cshtml.
Também em Views/Courses/Edit.cshtml, adicione um campo de número de curso antes do campo Título. Como o
número de curso é a chave primária, ele é exibido, mas não pode ser alterado.

<div class="form-group">
<label asp-for="CourseID" class="control-label"></label>
<div>@Html.DisplayFor(model => model.CourseID)</div>
</div>

Já existe um campo oculto ( <input type="hidden"> ) para o número de curso na exibição Editar. A adição de um
auxiliar de marcação <label> não elimina a necessidade do campo oculto, porque ele não faz com que o número
de curso seja incluído nos dados postados quando o usuário clica em Salvar na página Editar.
Em Views/Courses/Delete.cshtml, adicione um campo de número de curso na parte superior e altere a ID do
departamento para o nome do departamento.
@model ContosoUniversity.Models.Course

@{
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.CourseID)
</dt>
<dd>
@Html.DisplayFor(model => model.CourseID)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd>
@Html.DisplayFor(model => model.Title)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Credits)
</dt>
<dd>
@Html.DisplayFor(model => model.Credits)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Department)
</dt>
<dd>
@Html.DisplayFor(model => model.Department.Name)
</dd>
</dl>

<form asp-action="Delete">
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-default" /> |
<a asp-action="Index">Back to List</a>
</div>
</form>
</div>

Em Views/Courses/Details.cshtml, faça a mesma alteração que você acabou de fazer para Delete.cshtml.
Testar as páginas Curso
Execute o aplicativo, selecione a guia Cursos, clique em Criar Novo e insira dados para um novo curso:
Clique em Criar. A página Índice de Cursos é exibida com o novo curso adicionado à lista. O nome do
departamento na lista de páginas de Índice é obtido da propriedade de navegação, mostrando que a relação foi
estabelecida corretamente.
Clique em Editar em um curso na página Índice de Cursos.
Altere dados na página e clique em Salvar. A página Índice de Cursos é exibida com os dados de cursos
atualizados.

Adicionar uma página Editar para instrutores


Quando você edita um registro de instrutor, deseja poder 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 que significa que o
código deve manipular as seguintes situações:
Se o usuário apagar a atribuição de escritório e ela originalmente tinha um valor, exclua a entidade
OfficeAssignment.
Se o usuário inserir um valor de atribuição de escritório e ele originalmente estava vazio, crie uma nova
entidade OfficeAssignment.
Se o usuário alterar o valor de uma atribuição de escritório, altere o valor em uma entidade
OfficeAssignment existente.
Atualizar o controlador Instrutores
Em InstructorsController.cs, altere o código no método HttpGet Edit para que ele carregue a propriedade de
navegação OfficeAssignment da entidade Instructor e chame AsNoTracking :
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}

var instructor = await _context.Instructors


.Include(i => i.OfficeAssignment)
.AsNoTracking()
.SingleOrDefaultAsync(m => m.ID == id);
if (instructor == null)
{
return NotFound();
}
return View(instructor);
}

Substitua o método HttpPost Edit pelo seguinte código para manipular atualizações de atribuição de escritório:

[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}

var instructorToUpdate = await _context.Instructors


.Include(i => i.OfficeAssignment)
.SingleOrDefaultAsync(s => s.ID == id);

if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
{
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
try
{
await _context.SaveChangesAsync();
}
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 RedirectToAction(nameof(Index));
}
return View(instructorToUpdate);
}

O código faz o seguinte:


Altera o nome do método para EditPost porque a assinatura agora é a mesma do método HttpGet Edit
(o atributo ActionName especifica que a URL /Edit/ ainda é usada).
Obtém a entidade Instructor atual do banco de dados usando o carregamento adiantado para a
propriedade de navegação OfficeAssignment . Isso é o mesmo que você fez no método HttpGet Edit .
Atualiza a entidade Instructor recuperada com valores do associador de modelos. A sobrecarga
TryUpdateModel permite que você adicione à lista de permissões as propriedades que você deseja incluir.
Isso impede o excesso de postagem, conforme explicado no segundo tutorial.

if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))

Se o local do escritório estiver em branco, a propriedade Instructor.OfficeAssignment será definida como


nula para que a linha relacionada na tabela OfficeAssignment seja excluída.

if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}

Salva as alterações no banco de dados.


Atualizar a exibição Editar Instrutor
Em Views/Instructors/Edit.cshtml, adicione um novo campo para editar o local do escritório, ao final, antes do
botão Salvar:

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

Execute o aplicativo, selecione a guia Instrutores e, em seguida, clique em Editar em um instrutor. Altere o Local
do Escritório e clique em Salvar.
Adicionar atribuições de Curso à página Editar Instrutor
Os instrutores podem ministrar a quantidade de cursos que desejarem. Agora, você aprimorará a página Editar
Instrutor adicionando a capacidade de alterar as atribuições de curso usando um grupo de caixas de seleção,
conforme mostrado na seguinte captura de tela:
A relação entre as entidades Course e Instructor é muitos para muitos. Para adicionar e remover relações,
adicione e remova entidades bidirecionalmente no conjunto de entidades de junção CourseAssignments.
A interface do usuário que permite alterar a quais cursos um instrutor é atribuído é um grupo de caixas de
seleção. Uma caixa de seleção é exibida para cada curso no banco de dados, e aqueles aos quais o instrutor está
atribuído no momento sã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ê desejará usar outro método
de apresentação dos dados na exibição, mas usará o mesmo método de manipulação de uma entidade de junção
para criar ou excluir relações.
Atualizar o controlador Instrutores
Para fornecer dados à exibição para a lista de caixas de seleção, você usará uma classe de modelo de exibição.
Crie AssignedCourseData.cs na pasta SchoolViewModels e substitua o código existente pelo seguinte código:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Models.SchoolViewModels
{
public class AssignedCourseData
{
public int CourseID { get; set; }
public string Title { get; set; }
public bool Assigned { get; set; }
}
}

Em InstructorsController.cs, substitua o método HttpGet Edit pelo código a seguir. As alterações são realçadas.

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


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

var instructor = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments).ThenInclude(i => i.Course)
.AsNoTracking()
.SingleOrDefaultAsync(m => m.ID == id);
if (instructor == null)
{
return NotFound();
}
PopulateAssignedCourseData(instructor);
return View(instructor);
}

private void PopulateAssignedCourseData(Instructor instructor)


{
var allCourses = _context.Courses;
var instructorCourses = new HashSet<int>(instructor.CourseAssignments.Select(c => c.CourseID));
var viewModel = new List<AssignedCourseData>();
foreach (var course in allCourses)
{
viewModel.Add(new AssignedCourseData
{
CourseID = course.CourseID,
Title = course.Title,
Assigned = instructorCourses.Contains(course.CourseID)
});
}
ViewData["Courses"] = viewModel;
}

O código adiciona o carregamento adiantado à propriedade de navegação Courses e chama o novo método
PopulateAssignedCourseData para fornecer informações para a matriz de caixa de seleção usando a classe de
modelo de exibição AssignedCourseData .
O código no método PopulateAssignedCourseData lê todas as entidades Course para carregar uma lista de cursos
usando a classe de modelo de exibição. Para cada curso, o código verifica se o curso existe na propriedade de
navegação Courses do instrutor. Para criar uma pesquisa eficiente ao verificar se um curso é atribuído ao
instrutor, os cursos atribuídos ao instrutor são colocados em uma coleção HashSet . A propriedade Assigned está
definida como verdadeiro para os cursos aos quais instrutor é atribuído. A exibição usará essa propriedade para
determinar quais caixas de seleção precisam ser exibidas como selecionadas. Por fim, a lista é passada para a
exibição em ViewData .
Em seguida, adicione o código que é executado quando o usuário clica em Salvar. Substitua o método EditPost
pelo código a seguir e adicione um novo método que atualiza a propriedade de navegação Courses da entidade
Instructor.

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int? id, string[] selectedCourses)
{
if (id == null)
{
return NotFound();
}

var instructorToUpdate = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.SingleOrDefaultAsync(m => m.ID == id);

if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
{
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
try
{
await _context.SaveChangesAsync();
}
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 RedirectToAction(nameof(Index));
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
PopulateAssignedCourseData(instructorToUpdate);
return View(instructorToUpdate);
}
private void UpdateInstructorCourses(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 = instructorToUpdate.CourseAssignments.SingleOrDefault(i =>
i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}

A assinatura do método agora é diferente do método HttpGet Edit e, portanto, o nome do método é alterado de
EditPost para Edit novamente.

Como a exibição não tem uma coleção de entidades Course, o associador de modelos não pode atualizar
automaticamente a propriedade de navegação CourseAssignments . Em vez de usar o associador de modelos para
atualizar a propriedade de navegação CourseAssignments , faça isso no novo método UpdateInstructorCourses .
Portanto, você precisa excluir a propriedade CourseAssignments da associação de modelos. Isso não exige
nenhuma alteração no código que chama TryUpdateModel , porque você está usando a sobrecarga da lista de
permissões e CourseAssignments não está na lista de inclusões.
Se nenhuma caixa de seleção foi marcada, o código em UpdateInstructorCourses inicializa a propriedade de
navegação CourseAssignments com uma coleção vazia e retorna:
private void UpdateInstructorCourses(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 = instructorToUpdate.CourseAssignments.SingleOrDefault(i =>
i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}

Em seguida, o código executa um loop em todos os cursos no banco de dados e verifica cada curso em relação
àqueles atribuídos no momento ao instrutor e em relação àqueles que foram selecionados na exibição. Para
facilitar pesquisas eficientes, as últimas duas coleções são armazenadas em objetos HashSet .
Se a caixa de seleção para um curso foi marcada, mas o curso não está na propriedade de navegação
Instructor.CourseAssignments , o curso é adicionado à coleção na propriedade de navegação.
private void UpdateInstructorCourses(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 = instructorToUpdate.CourseAssignments.SingleOrDefault(i =>
i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}

Se a caixa de seleção para um curso não foi marcada, mas o curso está na propriedade de navegação
Instructor.CourseAssignments , o curso é removido da propriedade de navegação.
private void UpdateInstructorCourses(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 = instructorToUpdate.CourseAssignments.SingleOrDefault(i =>
i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}

Atualizar as exibições Instrutor


Em Views/Instructors/Edit.cshtml, adicione um campo Cursos com uma matriz de caixas de seleção, adicionando
o código a seguir imediatamente após os elementos div para o campo Escritório e antes do elemento div
para o botão Salvar.

OBSERVAÇÃO
Quando você colar o código no Visual Studio, as quebras de linha serão alteradas de uma forma que divide o código.
Pressione Ctrl+Z uma vez para desfazer a formatação automática. Isso corrigirá 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, ou você receberá um erro de tempo de
execução. Com o bloco de novo código selecionado, pressione Tab três vezes para alinhar o novo código com o código
existente. Verifique o status deste problema aqui.
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;
List<ContosoUniversity.Models.SchoolViewModels.AssignedCourseData> courses =
ViewBag.Courses;

foreach (var course in courses)


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

Esse código cria uma tabela HTML que contém três colunas. Em cada coluna há uma caixa de seleção, seguida de
uma legenda que consiste no número e título do curso. Todas as caixas de seleção têm o mesmo nome
("selectedCourses"), o que informa ao associador de modelos de que elas devem ser tratadas como um grupo. O
atributo de valor de cada caixa de seleção é definido com o valor de CourseID . Quando a página é postada, o
associador de modelos passa uma matriz para o controlador que consiste nos valores CourseID para apenas as
caixas de seleção marcadas.
Quando as caixas de seleção são inicialmente renderizadas, aquelas que se destinam aos cursos atribuídos ao
instrutor têm atributos marcados, que os seleciona (exibe-os como marcados).
Execute o aplicativo, selecione a guia Instrutores e clique em Editar em um instrutor para ver a página Editar.
Altere algumas atribuições de curso e clique em Salvar. As alterações feitas 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 necessários.

Atualizar a página Excluir


Em InstructorsController.cs, exclua o método DeleteConfirmed e insira o código a seguir em seu lugar.
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(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 RedirectToAction(nameof(Index));
}

Este código faz as seguintes alterações:


Executa o carregamento adiantado para a propriedade de navegação CourseAssignments . Você precisa
incluir isso ou o EF não reconhecerá as entidades CourseAssignment relacionadas e não as excluirá. Para
evitar a necessidade de lê-las aqui, você pode configurar 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.

Adicionar local de escritório e cursos para a página Criar


Em InstructorsController.cs, exclua os métodos HttpGet e HttpPost Create e, em seguida, adicione o seguinte
código em seu lugar:
public IActionResult Create()
{
var instructor = new Instructor();
instructor.CourseAssignments = new List<CourseAssignment>();
PopulateAssignedCourseData(instructor);
return View();
}

// POST: Instructors/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("FirstMidName,HireDate,LastName,OfficeAssignment")] Instructor
instructor, string[] selectedCourses)
{
if (selectedCourses != null)
{
instructor.CourseAssignments = new List<CourseAssignment>();
foreach (var course in selectedCourses)
{
var courseToAdd = new CourseAssignment { InstructorID = instructor.ID, CourseID =
int.Parse(course) };
instructor.CourseAssignments.Add(courseToAdd);
}
}
if (ModelState.IsValid)
{
_context.Add(instructor);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
PopulateAssignedCourseData(instructor);
return View(instructor);
}

Esse código é semelhante ao que você viu nos métodos Edit , exceto que inicialmente nenhum curso está
selecionado. O método HttpGet Create chama o método PopulateAssignedCourseData , não porque pode haver
cursos selecionados, mas para fornecer uma coleção vazia para o loop foreach na exibição (caso contrário, o
código de exibição gera uma exceção de referência nula).
O método HttpPost Create adiciona cada curso selecionado à propriedade de navegação CourseAssignments
antes que ele verifica se há erros de validação e adiciona o novo instrutor ao banco de dados. Os cursos são
adicionados, mesmo se há erros de modelo, de modo que quando houver erros de modelo (por exemplo, o
usuário inseriu uma data inválida) e a página for exibida novamente com uma mensagem de erro, as seleções de
cursos que foram feitas sejam restauradas automaticamente.
Observe que para poder adicionar cursos à propriedade de navegação CourseAssignments , é necessário inicializar
a propriedade como uma coleção vazia:

instructor.CourseAssignments = new List<CourseAssignment>();

Como alternativa a fazer isso no código do controlador, faça isso no modelo Instrutor alterando o getter de
propriedade para criar automaticamente a coleção se ela não existir, conforme mostrado no seguinte exemplo:
private ICollection<CourseAssignment> _courseAssignments;
public ICollection<CourseAssignment> CourseAssignments
{
get
{
return _courseAssignments ?? (_courseAssignments = new List<CourseAssignment>());
}
set
{
_courseAssignments = value;
}
}

Se você modificar a propriedade CourseAssignments dessa forma, poderá remover o código de inicialização de
propriedade explícita no controlador.
Em Views/Instructor/Create.cshtml, adicione uma caixa de texto de local do escritório e caixas de seleção para
cursos antes do botão Enviar. Como no caso da página Editar, corrija a formatação se o Visual Studio reformatar o
código quando você o colar.

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

<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;
List<ContosoUniversity.Models.SchoolViewModels.AssignedCourseData> courses =
ViewBag.Courses;

foreach (var course in courses)


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

Faça o teste executando o aplicativo e criando um instrutor.

Manipulando transações
Conforme explicado no tutorial do CRUD, o Entity Framework implementa transações de forma implícita. 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.
Resumo
Agora você concluiu a introdução ao trabalho com os dados relacionados. No próximo tutorial, você verá como
lidar com conflitos de simultaneidade.

A N T E R IO R P R Ó X IM O
ASP.NET Core MVC com EF Core – simultaneidade –
8 de 10
08/05/2018 • 32 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.
Nos tutoriais anteriores, você aprendeu a atualizar dados. Este tutorial mostra como lidar com conflitos quando
os mesmos usuários atualizam a mesma entidade simultaneamente.
Você criará páginas da Web que funcionam com a entidade Department e tratará erros de simultaneidade. As
ilustrações a seguir mostram as páginas Editar e Excluir, incluindo algumas mensagens exibidas se ocorre um
conflito de simultaneidade.
Conflitos de simultaneidade
Um conflito de simultaneidade ocorre quando um usuário exibe dados de uma entidade para editá-los e, em
seguida, outro usuário atualiza os mesmos dados da entidade antes que a primeira alteração do usuário seja
gravada no banco de dados. Se você não habilitar a detecção desses conflitos, a última pessoa que atualizar o
banco de dados substituirá as outras alterações do usuário. Em muitos aplicativos, esse risco é aceitável: se
houver poucos usuários ou poucas atualizações ou se não for realmente crítico se algumas alterações forem
substituídas, o custo de programação para simultaneidade poderá superar o benefício. Nesse caso, você não
precisa configurar o aplicativo para lidar com conflitos de simultaneidade.
Simultaneidade pessimista (bloqueio )
Se o aplicativo precisar evitar a perda acidental de dados em cenários de simultaneidade, uma maneira de fazer
isso será usar bloqueios de banco de dados. Isso é chamado de simultaneidade pessimista. Por exemplo, antes de
ler uma linha de um banco de dados, você solicita um bloqueio para o acesso somente leitura ou de atualização.
Se você bloquear uma linha para o acesso de atualização, nenhum outro usuário terá permissão para bloquear a
linha para o acesso somente leitura ou de atualização, porque ele obterá uma cópia dos dados que estão sendo
alterados. Se você bloquear uma linha para o acesso somente leitura, outros também poderão bloqueá-la para o
acesso somente leitura, mas não para atualização.
O gerenciamento de bloqueios traz desvantagens. Ele pode ser complexo de ser programado. Exige recursos de
gerenciamento de banco de dados significativos e pode causar problemas de desempenho, conforme o número
de usuários de um aplicativo aumenta. Por esses motivos, nem todos os sistemas de gerenciamento de banco de
dados dão suporte à simultaneidade pessimista. O Entity Framework Core não fornece nenhum suporte interno
para ele, e este tutorial não mostra como implementá-lo.
Simultaneidade otimista
A alternativa à simultaneidade pessimista é a simultaneidade otimista. Simultaneidade otimista significa permitir
que conflitos de simultaneidade ocorram e responder adequadamente se eles ocorrerem. Por exemplo, Alice visita
a página Editar Departamento e altera o valor do 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 retorna à página Índice.

Em seguida, 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.
Algumas das opções incluem o seguinte:
Controle qual propriedade um usuário modificou e atualize apenas as colunas correspondentes no banco
de dados.
No cenário de exemplo, nenhum dado é perdido, porque propriedades diferentes foram atualizadas pelos
dois usuários. Na próxima vez que alguém navegar pelo departamento de inglês, verá as alterações de
Alice e de Julio – a data de início de 1/9/2013 e um orçamento de zero dólar. Esse método de atualização
pode reduzir a quantidade de conflitos que podem resultar em perda de dados, mas ele não poderá evitar a
perda de dados se forem feitas alterações concorrentes à mesma propriedade de uma entidade. Se o Entity
Framework funciona dessa maneira depende de como o código de atualização é implementado.
Geralmente, isso não é prático em um aplicativo Web, porque pode exigir que você mantenha grandes
quantidades de estado para manter o controle de todos os valores de propriedade originais de uma
entidade, bem como novos valores. A manutenção de grandes quantidades de estado pode afetar o
desempenho do aplicativo, pois exige recursos do servidor ou deve ser incluída na própria página da Web
(por exemplo, em campos ocultos) ou em um cookie.
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 restaurado. Isso é chamado 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.) Conforme observado
na introdução a esta seção, se você não fizer nenhuma codificação para a manipulação de simultaneidade,
isso ocorrerá automaticamente.
Você pode impedir que as alterações de Julio sejam atualizadas no banco de dados.
Normalmente, você exibirá uma mensagem de erro, mostrará a ele o estado atual dos dados e permitirá a
ele aplicar as alterações novamente se ele ainda desejar fazê-las. 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 sobre o que está
acontecendo.
Detectando conflitos de simultaneidade
Resolva conflitos manipulando exceções DbConcurrencyException geradas pelo Entity Framework. Para saber
quando gerar essas exceções, o Entity Framework precisa poder detectar conflitos. Portanto, é necessário
configurar o banco de dados e o modelo de dados de forma adequada. Algumas opções para habilitar a detecção
de conflitos incluem as seguintes:
Na tabela de banco de dados, inclua uma coluna de acompanhamento que pode ser usada para determinar
quando uma linha é alterada. Em seguida, configure o Entity Framework para incluir essa coluna na
cláusula Where de comandos SQL Update ou Delete.
O tipo de dados da coluna de acompanhamento é normalmente rowversion . O valor rowversion é um
número sequencial que é incrementado sempre que a linha é atualizada. Em um comando Update ou
Delete, a cláusula Where inclui o valor original da coluna de acompanhamento (a versão de linha original).
Se a linha que está sendo atualizada tiver sido alterada por outro usuário, o valor da coluna rowversion
será diferente do valor original; portanto, a instrução Update ou Delete não poderá encontrar a linha a ser
atualizada devido à cláusula Where. Quando o Entity Framework descobre que nenhuma linha foi
atualizada pelo comando Update ou Delete (ou seja, quando o número de linhas afetadas é zero), ele
interpreta isso como um conflito de simultaneidade.
Configure o Entity Framework para incluir os valores originais de cada coluna na tabela na cláusula Where
de comandos Update e Delete.
Como a primeira opção, se nada na linha for alterado desde que a linha tiver sido lida pela primeira vez, a
cláusula Where não retornará uma linha a ser atualizada, o que o Entity Framework interpretará como um
conflito de simultaneidade. Para tabelas de banco de dados que têm muitas colunas, essa abordagem pode
resultar em cláusulas Where muito grandes e pode exigir que você mantenha grandes quantidades de
estado. Conforme observado anteriormente, manter grandes quantidades de estado pode afetar o
desempenho do aplicativo. Portanto, essa abordagem geralmente não é recomendada e não é o método
usado neste tutorial.
Caso deseje implementar essa abordagem, precisará marcar todas as propriedades de chave não primária
na entidade para as quais você deseja controlar a simultaneidade adicionando o atributo ConcurrencyCheck
a elas. Essa alteração permite que o Entity Framework inclua todas as colunas na cláusula Where do SQL
de instruções Update e Delete.
No restante deste tutorial, você adicionará uma propriedade de acompanhamento rowversion à entidade
Department, criará um controlador e exibições e testará para verificar se tudo está funcionando corretamente.

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 será incluída na cláusula Where de comandos Update e Delete
enviados ao banco de dados. O atributo é chamado Timestamp porque as versões anteriores do SQL Server
usavam um tipo de dados timestamp do SQL antes de o rowversion do SQL substituí-lo. O tipo do .NET para
rowversion é uma matriz de bytes.

Se preferir usar a API fluente, use o método IsConcurrencyToken (em Data/SchoolContext.cs) para especificar a
propriedade de acompanhamento, conforme mostrado no seguinte exemplo:

modelBuilder.Entity<Department>()
.Property(p => p.RowVersion).IsConcurrencyToken();

Adicionando uma propriedade, você alterou o modelo de banco de dados e, portanto, precisa fazer outra
migração.
Salve as alterações e compile o projeto e, em seguida, insira os seguintes comandos na janela Comando:
dotnet ef migrations add RowVersion

dotnet ef database update

Criar um controlador e exibições Departamentos


Gere por scaffolding um controlador e exibições Departamentos, como você fez anteriormente para Alunos,
Cursos e Instrutores.

No arquivo DepartmentsController.cs, altere todas as quatro ocorrências de "FirstMidName" para "FullName", de


modo que as listas suspensas do administrador do departamento contenham o nome completo do instrutor em
vez de apenas o sobrenome.

ViewData["InstructorID"] = new SelectList(_context.Instructors, "ID", "FullName", department.InstructorID);

Atualizar a exibição Índice de Departamentos


O mecanismo de scaffolding criou uma coluna RowVersion na exibição Índice, mas esse campo não deve ser
exibido.
Substitua o código em Views/Departments/Index.cshtml pelo código a seguir.
@model IEnumerable<ContosoUniversity.Models.Department>

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

<h2>Departments</h2>

<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Budget)
</th>
<th>
@Html.DisplayNameFor(model => model.StartDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Administrator)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<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>
<a asp-action="Edit" asp-route-id="@item.DepartmentID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.DepartmentID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.DepartmentID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Isso altera o título "Departamentos", exclui a coluna RowVersion e mostra o nome completo em vez de o nome
do administrador.

Atualizar os métodos Edit no controlador Departamentos


Nos métodos HttpGet Edit e Details , adicione AsNoTracking . No método HttpGet Edit , adicione o
carregamento adiantado ao Administrador.
var department = await _context.Departments
.Include(i => i.Administrator)
.AsNoTracking()
.SingleOrDefaultAsync(m => m.DepartmentID == id);

Substitua o código existente do método HttpPost Edit pelo seguinte código:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int? id, byte[] rowVersion)
{
if (id == null)
{
return NotFound();
}

var departmentToUpdate = await _context.Departments.Include(i => i.Administrator).SingleOrDefaultAsync(m


=> m.DepartmentID == id);

if (departmentToUpdate == null)
{
Department deletedDepartment = new Department();
await TryUpdateModelAsync(deletedDepartment);
ModelState.AddModelError(string.Empty,
"Unable to save changes. The department was deleted by another user.");
ViewData["InstructorID"] = new SelectList(_context.Instructors, "ID", "FullName",
deletedDepartment.InstructorID);
return View(deletedDepartment);
}

_context.Entry(departmentToUpdate).Property("RowVersion").OriginalValue = rowVersion;

if (await TryUpdateModelAsync<Department>(
departmentToUpdate,
"",
s => s.Name, s => s.StartDate, s => s.Budget, s => s.InstructorID))
{
try
{
await _context.SaveChangesAsync();
return RedirectToAction(nameof(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 changes. The department was deleted by another user.");
}
else
{
var databaseValues = (Department)databaseEntry.ToObject();

if (databaseValues.Name != clientValues.Name)
{
ModelState.AddModelError("Name", $"Current value: {databaseValues.Name}");
}
if (databaseValues.Budget != clientValues.Budget)
{
ModelState.AddModelError("Budget", $"Current value: {databaseValues.Budget:c}");
}
if (databaseValues.StartDate != clientValues.StartDate)
{
ModelState.AddModelError("StartDate", $"Current value: {databaseValues.StartDate:d}");
}
if (databaseValues.InstructorID != clientValues.InstructorID)
{
Instructor databaseInstructor = await _context.Instructors.SingleOrDefaultAsync(i => i.ID
== databaseValues.InstructorID);
ModelState.AddModelError("InstructorID", $"Current value:
{databaseInstructor?.FullName}");
}

ModelState.AddModelError(string.Empty, "The record you attempted to edit "


+ "was modified by another user after you got the original value. 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. Otherwise click the Back to List hyperlink.");
departmentToUpdate.RowVersion = (byte[])databaseValues.RowVersion;
ModelState.Remove("RowVersion");
}
}
}
ViewData["InstructorID"] = new SelectList(_context.Instructors, "ID", "FullName",
departmentToUpdate.InstructorID);
return View(departmentToUpdate);
}

O código começa com a tentativa de ler o departamento a ser atualizado. Se o método SingleOrDefaultAsync
retornar nulo, isso indicará que o departamento foi excluído por outro usuário. Nesse caso, o código usa os
valores de formulário postados para criar uma entidade de departamento, de modo que a página Editar possa ser
exibida novamente com uma mensagem de erro. Como alternativa, você não precisará recriar a entidade de
departamento se exibir apenas uma mensagem de erro sem exibir novamente os campos de departamento.
A exibição armazena o valor RowVersion original em um campo oculto e esse método recebe esse valor no
parâmetro rowVersion . Antes de chamar SaveChanges , você precisa colocar isso no valor da propriedade
RowVersion original na coleção OriginalValues da entidade.

_context.Entry(departmentToUpdate).Property("RowVersion").OriginalValue = rowVersion;

Em seguida, quando o Entity Framework criar um comando SQL UPDATE, esse comando incluirá uma cláusula
WHERE que procura uma linha que tem o valor RowVersion original. Se nenhuma linha for afetada pelo
comando UPDATE (nenhuma linha tem o valor RowVersion original), o Entity Framework gerará uma exceção
DbUpdateConcurrencyException .

O código no bloco catch dessa exceção obtém a entidade Department afetada que tem os valores atualizados da
propriedade Entries no objeto de exceção.

var exceptionEntry = ex.Entries.Single();

A coleção Entries terá apenas um objeto EntityEntry . Use esse objeto para obter os novos valores inseridos
pelo usuário e os valores de banco de dados atuais.

var clientValues = (Department)exceptionEntry.Entity;


var databaseEntry = exceptionEntry.GetDatabaseValues();

O código adiciona uma mensagem de erro personalizada a cada coluna que tem valores de banco de dados
diferentes do que o usuário inseriu na página Editar (apenas um campo é mostrado aqui para fins de brevidade).
var databaseValues = (Department)databaseEntry.ToObject();

if (databaseValues.Name != clientValues.Name)
{
ModelState.AddModelError("Name", $"Current value: {databaseValues.Name}");

Por fim, o código define o valor RowVersion do departmentToUpdate com o novo valor recuperado do banco de
dados. Esse novo valor RowVersion será armazenado no campo oculto quando a página Editar for exibida
novamente, e na próxima vez que o usuário clicar em Salvar, somente os erros de simultaneidade que ocorrem
desde a nova exibição da página Editar serão capturados.

departmentToUpdate.RowVersion = (byte[])databaseValues.RowVersion;
ModelState.Remove("RowVersion");

A instrução ModelState.Remove é obrigatória porque ModelState tem o valor RowVersion antigo. Na exibição, o
valor ModelState de um campo tem precedência sobre os valores de propriedade do modelo, quando ambos
estão presentes.

Atualizar a exibição Editar Departamento


Em Views/Departments/Edit.cshtml, faça as seguintes alterações:
Adicione um campo oculto para salvar o valor da propriedade RowVersion , imediatamente após o campo
oculto da propriedade DepartmentID .
Adicione uma opção "Selecionar Administrador" à lista suspensa.
@model ContosoUniversity.Models.Department

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

<h2>Edit</h2>

<h4>Department</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="DepartmentID" />
<input type="hidden" asp-for="RowVersion" />
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Budget" class="control-label"></label>
<input asp-for="Budget" class="form-control" />
<span asp-validation-for="Budget" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StartDate" class="control-label"></label>
<input asp-for="StartDate" class="form-control" />
<span asp-validation-for="StartDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="InstructorID" class="control-label"></label>
<select asp-for="InstructorID" class="form-control" asp-items="ViewBag.InstructorID">
<option value="">-- Select Administrator --</option>
</select>
<span asp-validation-for="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-action="Index">Back to List</a>
</div>

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

Testar conflitos de simultaneidade na página Editar


Execute o aplicativo e acesse a página Índice de Departamentos. Clique com o botão direito do mouse na
hiperlink Editar do departamento de inglês e selecione Abrir em uma nova guia e, em seguida, clique no
hiperlink Editar no departamento de inglês. As duas guias do navegador agora exibem as mesmas informações.
Altere um campo na primeira guia do navegador e clique em Salvar.
O navegador mostra a página Índice com o valor alterado.
Altere um campo na segunda guia do navegador.
Clique em Salvar. Você verá uma mensagem de erro:
Clique em Salvar novamente. O valor inserido na segunda guia do navegador foi salvo. Você verá os valores
salvos quando a página Índice for exibida.

Atualizar a página Excluir


Para a página Excluir, o Entity Framework detecta conflitos de simultaneidade causados pela edição por outra
pessoa do departamento de maneira semelhante. Quando o método HttpGet Delete exibe a exibição de
confirmação, a exibição inclui o valor RowVersion original em um campo oculto. Em seguida, esse valor estará
disponível para o método HttpPost Delete chamado quando o usuário confirmar a exclusão. Quando o Entity
Framework cria o comando SQL DELETE, ele inclui uma cláusula WHERE com o valor RowVersion original. Se o
comando não resultar em nenhuma linha afetada (o que significa que a linha foi alterada após a exibição da
página Confirmação de exclusão), uma exceção de simultaneidade será gerada e o método HttpGet Delete será
chamado com um sinalizador de erro definido como verdadeiro para exibir novamente a página de confirmação
com uma mensagem de erro. Também é possível que nenhuma linha tenha sido afetada porque a linha foi
excluída por outro usuário; portanto, nesse caso, nenhuma mensagem de erro é exibida.
Atualizar os métodos Excluir no controlador Departamentos
Em DepartmentsController.cs, substitua o método HttpGet Delete pelo seguinte código:
public async Task<IActionResult> Delete(int? id, bool? concurrencyError)
{
if (id == null)
{
return NotFound();
}

var department = await _context.Departments


.Include(d => d.Administrator)
.AsNoTracking()
.SingleOrDefaultAsync(m => m.DepartmentID == id);
if (department == null)
{
if (concurrencyError.GetValueOrDefault())
{
return RedirectToAction(nameof(Index));
}
return NotFound();
}

if (concurrencyError.GetValueOrDefault())
{
ViewData["ConcurrencyErrorMessage"] = "The record you attempted to delete "
+ "was modified by another user after you got the original values. "
+ "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. Otherwise "
+ "click the Back to List hyperlink.";
}

return View(department);
}

O método aceita um parâmetro opcional que indica se a página está sendo exibida novamente após um erro de
simultaneidade. Se esse sinalizador é verdadeiro e o departamento especificado não existe mais, isso indica que
ele foi excluído por outro usuário. Nesse caso, o código redireciona para a página Índice. Se esse sinalizador é
verdadeiro e o departamento existe, isso indica que ele foi alterado por outro usuário. Nesse caso, o código envia
uma mensagem de erro para a exibição usando ViewData .
Substitua o código no método HttpPost Delete (chamado DeleteConfirmed ) pelo seguinte código:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(Department department)
{
try
{
if (await _context.Departments.AnyAsync(m => m.DepartmentID == department.DepartmentID))
{
_context.Departments.Remove(department);
await _context.SaveChangesAsync();
}
return RedirectToAction(nameof(Index));
}
catch (DbUpdateConcurrencyException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction(nameof(Delete), new { concurrencyError = true, id = department.DepartmentID
});
}
}

No código gerado por scaffolding que acabou de ser substituído, esse método aceitou apenas uma ID de registro:
public async Task<IActionResult> DeleteConfirmed(int id)

Você alterou esse parâmetro para uma instância da entidade Departamento criada pelo associador de modelos.
Isso concede o acesso ao EF ao valor da propriedade RowVersion, além da chave de registro.

public async Task<IActionResult> Delete(Department department)

Você também alterou o nome do método de ação de DeleteConfirmed para Delete . O código gerado por
scaffolding usou o nome DeleteConfirmed para fornecer ao método HttpPost uma assinatura exclusiva. (O CLR
exige que métodos sobrecarregados tenham diferentes parâmetros de método.) Agora que as assinaturas são
exclusivas, você pode continuar com a convenção MVC e usar o mesmo nome para os métodos de exclusão
HttpPost e HttpGet.
Se o departamento já foi excluído, o método AnyAsync retorna falso e o aplicativo apenas volta para o método de
Índice.
Se um erro de simultaneidade é capturado, o código exibe novamente a página Confirmação de exclusão e
fornece um sinalizador que indica que ela deve exibir uma mensagem de erro de simultaneidade.
Atualizar a exibição Excluir
Em Views/Departments/Delete.cshtml, substitua o código gerado por scaffolding pelo seguinte código que
adiciona um campo de mensagem de erro e campos ocultos às propriedades DepartmentID e RowVersion. As
alterações são realçadas.
@model ContosoUniversity.Models.Department

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

<h2>Delete</h2>

<p class="text-danger">@ViewData["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.Name)
</dt>
<dd>
@Html.DisplayFor(model => model.Name)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Budget)
</dt>
<dd>
@Html.DisplayFor(model => model.Budget)
</dd>
<dt>
@Html.DisplayNameFor(model => model.StartDate)
</dt>
<dd>
@Html.DisplayFor(model => model.StartDate)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Administrator)
</dt>
<dd>
@Html.DisplayFor(model => model.Administrator.FullName)
</dd>
</dl>

<form asp-action="Delete">
<input type="hidden" asp-for="DepartmentID" />
<input type="hidden" asp-for="RowVersion" />
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-default" /> |
<a asp-action="Index">Back to List</a>
</div>
</form>
</div>

Isso faz as seguintes alterações:


Adiciona uma mensagem de erro entre os cabeçalhos h2 e h3 .
Substitua FirstMidName por FullName no campo Administrador.
Remove o campo RowVersion.
Adiciona um campo oculto à propriedade RowVersion .

Execute o aplicativo e acesse a página Índice de Departamentos. Clique com o botão direito do mouse na
hiperlink Excluir do departamento de inglês e selecione Abrir em uma nova guia e, em seguida, na primeira
guia, clique no hiperlink Editar no departamento de inglês.
Na primeira janela, altere um dos valores e clique em Salvar:

Na segunda guia, clique em Excluir. Você verá a mensagem de erro de simultaneidade e os valores de
Departamento serão atualizados com o que está atualmente no banco de dados.
Se você clicar em Excluir novamente, será redirecionado para a página Índice, que mostra que o departamento
foi excluído.

Exibições Atualizar Detalhes e Criar


Opcionalmente, você pode limpar o código gerado por scaffolding nas exibições Detalhes e Criar.
Substitua o código em Views/Departments/Details.cshtml para excluir a coluna RowVersion e mostrar o nome
completo do Administrador.
@model ContosoUniversity.Models.Department

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

<h2>Details</h2>

<div>
<h4>Department</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd>
@Html.DisplayFor(model => model.Name)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Budget)
</dt>
<dd>
@Html.DisplayFor(model => model.Budget)
</dd>
<dt>
@Html.DisplayNameFor(model => model.StartDate)
</dt>
<dd>
@Html.DisplayFor(model => model.StartDate)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Administrator)
</dt>
<dd>
@Html.DisplayFor(model => model.Administrator.FullName)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.DepartmentID">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>

Substitua o código em Views/Departments/Create.cshtml para adicionar uma opção Selecionar à lista suspensa.
@model ContosoUniversity.Models.Department

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

<h2>Create</h2>

<h4>Department</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Budget" class="control-label"></label>
<input asp-for="Budget" class="form-control" />
<span asp-validation-for="Budget" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StartDate" class="control-label"></label>
<input asp-for="StartDate" class="form-control" />
<span asp-validation-for="StartDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="InstructorID" class="control-label"></label>
<select asp-for="InstructorID" class="form-control" asp-items="ViewBag.InstructorID">
<option value="">-- Select Administrator --</option>
</select>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>

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

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

Resumo
Isso conclui a introdução à manipulação de conflitos de simultaneidade. Para obter mais informações sobre como
lidar com a simultaneidade no EF Core, consulte Conflitos de simultaneidade. O próximo tutorial mostra como
implementar a herança de tabela por hierarquia para as entidades Instructor e Student.

A N T E R IO R P R Ó X IM O
ASP.NET Core MVC com EF Core – herança – 9 de
10
08/05/2018 • 14 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
primeiro tutorial na série.
No tutorial anterior, você tratou exceções de simultaneidade. Este tutorial mostrará como implementar a herança
no modelo de dados.
Na programação orientada a objeto, você pode usar a herança para facilitar a reutilização de código. Neste
tutorial, você alterará as classes Instructor e Student , de modo que elas derivem de uma classe base Person
que contém propriedades, como LastName , comuns a instrutores e alunos. Você não adicionará nem alterará as
páginas da Web, mas alterará uma parte do código, e essas alterações serão refletidas automaticamente no
banco de dados.

Opções para o mapeamento de herança para as tabelas de banco de


dados
As classes Instructor e Student no modelo de dados Escola têm várias propriedades idênticas:

Suponha que você deseje eliminar o código redundante para as propriedades compartilhadas pelas entidades
Instructor e Student . Ou você deseja escrever um serviço que pode formatar nomes sem se preocupar se o
nome foi obtido de um instrutor ou um aluno. Você pode criar uma classe base Person que contém apenas
essas propriedades compartilhadas e, em seguida, fazer com que as classes Instructor e Student herdem
dessa classe base, conforme mostrado na seguinte ilustração:
Há várias maneiras pelas quais essa estrutura de herança pode ser representada no banco de dados. Você pode
ter uma tabela Person que inclui informações sobre alunos e instrutores em uma única tabela. Algumas das
colunas podem se aplicar somente a instrutores (HireDate), algumas somente a alunos (EnrollmentDate) e
outras a ambos (LastName, FirstName). Normalmente, você terá uma coluna discriminatória para indicar qual
tipo cada linha representa. Por exemplo, a coluna discriminatória pode ter "Instrutor" para instrutores e "Aluno"
para alunos.

Esse padrão de geração de uma estrutura de herança de entidade com base em uma tabela de banco de dados
individual é chamado de herança TPH (tabela por hierarquia).
Uma alternativa é fazer com que o banco de dados se pareça mais com a estrutura de herança. Por exemplo,
você pode ter apenas os campos de nome na tabela Pessoa e as tabelas separadas Instrutor e Aluno com os
campos de data.

Esse padrão de criação de uma tabela de banco de dados para cada classe de entidade é chamado de herança
TPT (tabela por tipo).
Outra opção é mapear todos os tipos não abstratos para tabelas individuais. Todas as propriedades de uma
classe, incluindo propriedades herdadas, são mapeadas para colunas da tabela correspondente. Esse padrão é
chamado de herança TPC (Tabela por Classe Concreta). Se você tiver implementado a herança TPC para as
classes Person, Student e Instructor, conforme mostrado anteriormente, as tabelas Aluno e Instrutor não
parecerão diferentes após a implementação da herança do que antes.
Em geral, os padrões de herança TPC e TPH oferecem melhor desempenho do que os padrões de herança TPT,
porque os padrões TPT podem resultar em consultas de junção complexas.
Este tutorial demonstra como implementar a herança TPH. TPH é o único padrão de herança compatível com o
Entity Framework Core. O que você fará é criar uma classe Person , alterar as classes Instructor e Student
para que elas derivem de Person , adicionar a nova classe ao DbContext e criar uma migração.

DICA
Considere a possibilidade de salvar uma cópia do projeto antes de fazer as alterações a seguir. Em seguida, se você tiver
problemas e precisar recomeçar, será mais fácil começar do projeto salvo, em vez de reverter as etapas executadas para
este tutorial ou voltar ao início da série inteira.

Criar a classe Person


Na pasta Models, crie Person.cs e substitua o código de modelo pelo seguinte código:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public abstract class Person
{
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; }

[Display(Name = "Full Name")]


public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
}
}

Fazer com que as classes Student e Instructor herdem de Person


Em Instructor.cs, derive a classe Instructor da classe Person e remova os campos de nome e chave. O código será
semelhante ao seguinte exemplo:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Instructor : Person
{
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }

public ICollection<CourseAssignment> CourseAssignments { get; set; }


public OfficeAssignment OfficeAssignment { get; set; }
}
}

Faça as mesmas alterações em Student.cs.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Student : Person
{
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }

public ICollection<Enrollment> Enrollments { get; set; }


}
}

Adicionar o tipo de entidade Person ao modelo de dados


Adicione o tipo de entidade Person a SchoolContext.cs. As novas linhas são realçadas.
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; }
public DbSet<Person> People { 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<Person>().ToTable("Person");

modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
}
}
}

Isso é tudo o que o Entity Framework precisa para configurar a herança de tabela por hierarquia. Como você
verá, quando o banco de dados for atualizado, ele terá uma tabela Pessoa no lugar das tabelas Aluno e Instrutor.

Criar e personalizar o código de migração


Salve as alterações e compile o projeto. Em seguida, abra a janela Comando na pasta do projeto e insira o
seguinte comando:

dotnet ef migrations add Inheritance

Não execute o comando database update ainda. Esse comando resultará em perda de dados porque ele
removerá a tabela Instrutor e renomeará a tabela Aluno como Pessoa. Você precisa fornecer o código
personalizado para preservar os dados existentes.
Abra Migrations/<timestamp>_Inheritance.cs e substitua o método Up pelo seguinte código:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Enrollment_Student_StudentID",
table: "Enrollment");

migrationBuilder.DropIndex(name: "IX_Enrollment_StudentID", table: "Enrollment");

migrationBuilder.RenameTable(name: "Instructor", newName: "Person");


migrationBuilder.AddColumn<DateTime>(name: "EnrollmentDate", table: "Person", nullable: true);
migrationBuilder.AddColumn<string>(name: "Discriminator", table: "Person", nullable: false, maxLength:
128, defaultValue: "Instructor");
migrationBuilder.AlterColumn<DateTime>(name: "HireDate", table: "Person", nullable: true);
migrationBuilder.AddColumn<int>(name: "OldId", table: "Person", nullable: true);

// Copy existing Student data into new Person table.


migrationBuilder.Sql("INSERT INTO dbo.Person (LastName, FirstName, HireDate, EnrollmentDate,
Discriminator, OldId) SELECT LastName, FirstName, null AS HireDate, EnrollmentDate, 'Student' AS
Discriminator, ID AS OldId FROM dbo.Student");
// Fix up existing relationships to match new PK's.
migrationBuilder.Sql("UPDATE dbo.Enrollment SET StudentId = (SELECT ID FROM dbo.Person WHERE OldId =
Enrollment.StudentId AND Discriminator = 'Student')");

// Remove temporary key


migrationBuilder.DropColumn(name: "OldID", table: "Person");

migrationBuilder.DropTable(
name: "Student");

migrationBuilder.CreateIndex(
name: "IX_Enrollment_StudentID",
table: "Enrollment",
column: "StudentID");

migrationBuilder.AddForeignKey(
name: "FK_Enrollment_Person_StudentID",
table: "Enrollment",
column: "StudentID",
principalTable: "Person",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
}

Este código é responsável pelas seguintes tarefas de atualização de banco de dados:


Remove as restrições de chave estrangeira e índices que apontam para a tabela Aluno.
Renomeia a tabela Instrutor como Pessoa e faz as alterações necessárias para que ela armazene dados de
Aluno:
Adiciona EnrollmentDate que permite valo nulo para os alunos.
Adiciona a coluna Discriminatória para indicar se uma linha refere-se a um aluno ou um instrutor.
Faz com que HireDate permita valor nulo, pois linhas de alunos não terão datas de contratação.
Adiciona um campo temporário que será usado para atualizar chaves estrangeiras que apontam para
alunos. Quando você copiar os alunos para a tabela Person, eles receberão novos valores de chave
primária.
Copia os dados da tabela Aluno para a tabela Pessoa. Isso faz com que os alunos recebam novos valores
de chave primária.
Corrige valores de chave estrangeira que apontam para alunos.
Recria restrições de chave estrangeira e índices, agora apontando-os para a tabela Person.
(Se você tiver usado o GUID, em vez de inteiro como o tipo de chave primária, os valores de chave primária dos
alunos não precisarão ser alterados e várias dessas etapas poderão ser omitidas.)
Execute o comando database update :

dotnet ef database update

(Em um sistema de produção, você fará as alterações correspondentes no método Down , caso já tenha usado
isso para voltar à versão anterior do banco de dados. Para este tutorial, você não usará o método Down .)

OBSERVAÇÃO
É possível receber outros erros ao fazer alterações de esquema em um banco de dados que contém dados existentes. Se
você receber erros de migração que não consegue resolver, altere o nome do banco de dados na cadeia de conexão ou
exclua o banco de dados. Com um novo banco de dados, não há nenhum dado a ser migrado e o comando de atualização
de banco de dados terá uma probabilidade maior de ser concluído sem erros. Para excluir o banco de dados, use o SSOX
ou execute o comando database drop da CLI.

Testar com a herança implementada


Execute o aplicativo e teste várias páginas. Tudo funciona da mesma maneira que antes.
No Pesquisador de Objetos do SQL Server, expanda Data Connections/SchoolContext e, em seguida,
Tabelas e você verá que as tabelas Aluno e Instrutor foram substituídas por uma tabela Pessoa. Abra o designer
de tabela Pessoa e você verá que ela contém todas as colunas que costumavam estar nas tabelas Aluno e
Instrutor.

Clique com o botão direito do mouse na tabela Person e, em seguida, clique em Mostrar Dados da Tabela
para ver a coluna discriminatória.
Resumo
Você implementou a herança de tabela por hierarquia para as classes Person , Student e Instructor . Para
obter mais informações sobre a herança no Entity Framework Core, consulte Herança. No próximo tutorial, você
verá como lidar com uma variedade de cenários relativamente avançados do Entity Framework.

A N T E R IO R P R Ó X IM O
Tópicos avançados - Tutorial do EF Core com
ASP.NET Core MVC (10 de 10)
28/02/2018 • 24 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ê implementou a herança de tabela por hierarquia. Este tutorial apresenta vários tópicos
que são úteis para consideração quando você vai além dos conceitos básicos de desenvolvimento de aplicativos
Web ASP.NET Core que usam o Entity Framework Core.

Consultas SQL brutas


Uma das vantagens de usar o Entity Framework é que ele evita vincular o código de forma muito próxima a um
método específico de armazenamento de dados. Ele faz isso pela geração de consultas SQL e comandos para
você, que também libera você da necessidade de escrevê-los. Mas há casos excepcionais em que você precisa
executar consultas SQL específicas criadas manualmente. Para esses cenários, a API do Code First do Entity
Framework inclui métodos que permitem passar comandos SQL diretamente para o banco de dados. Você tem
as seguintes opções no EF Core 1.0:
Use o método DbSet.FromSql para consultas que retornam tipos de entidade. Os objetos retornados
precisam ser do tipo esperado pelo objeto DbSet e são controlados automaticamente pelo contexto de
banco de dados, a menos que você desative o controle.
Use o Database.ExecuteSqlCommand para comandos que não sejam de consulta.
Caso precise executar uma consulta que retorna tipos que não são entidades, use o ADO.NET com a conexão de
banco de dados fornecida pelo EF. Os dados retornados não são controlados pelo contexto de banco de dados,
mesmo se esse método é usado para recuperar tipos de entidade.
Como é sempre verdadeiro quando você executa comandos SQL em um aplicativo Web, é necessário tomar
precauções para proteger o site contra ataques de injeção de SQL. Uma maneira de fazer isso é usar consultas
parametrizadas para garantir que as cadeias de caracteres enviadas por uma página da Web não possam ser
interpretadas como comandos SQL. Neste tutorial, você usará consultas parametrizadas ao integrar a entrada do
usuário a uma consulta.

Chamar uma consulta que retorna entidades


A classe DbSet<TEntity> fornece um método que você pode usar para executar uma consulta que retorna uma
entidade do tipo TEntity . Para ver como isso funciona, você alterará o código no método Details do
controlador Departamento.
Em DepartmentsController.cs, no método Details , substitua o código que recupera um departamento com uma
chamada de método FromSql , conforme mostrado no seguinte código realçado:
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}

string query = "SELECT * FROM Department WHERE DepartmentID = {0}";


var department = await _context.Departments
.FromSql(query, id)
.Include(d => d.Administrator)
.AsNoTracking()
.SingleOrDefaultAsync();

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

return View(department);
}

Para verificar se o novo código funciona corretamente, selecione a guia Departamentos e, em seguida,
Detalhes de um dos departamentos.

Chamar uma consulta que retorna outros tipos


Anteriormente, você criou uma grade de estatísticas de alunos para a página Sobre que mostrava o número de
alunos para cada data de registro. Você obteve os dados do conjunto de entidades Students ( _context.Students )
e usou o LINQ para projetar os resultados em uma lista de objetos de modelo de exibição EnrollmentDateGroup .
Suponha que você deseje gravar o próprio SQL em vez de usar LINQ. Para fazer isso, você precisa executar uma
consulta SQL que retorna algo diferente de objetos de entidade. No EF Core 1.0, uma maneira de fazer isso é
escrever um código ADO.NET e obter a conexão de banco de dados do EF.
Em HomeController.cs, substitua o método About pelo seguinte código:
public async Task<ActionResult> About()
{
List<EnrollmentDateGroup> groups = new List<EnrollmentDateGroup>();
var conn = _context.Database.GetDbConnection();
try
{
await conn.OpenAsync();
using (var command = conn.CreateCommand())
{
string query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount "
+ "FROM Person "
+ "WHERE Discriminator = 'Student' "
+ "GROUP BY EnrollmentDate";
command.CommandText = query;
DbDataReader reader = await command.ExecuteReaderAsync();

if (reader.HasRows)
{
while (await reader.ReadAsync())
{
var row = new EnrollmentDateGroup { EnrollmentDate = reader.GetDateTime(0), StudentCount
= reader.GetInt32(1) };
groups.Add(row);
}
}
reader.Dispose();
}
}
finally
{
conn.Close();
}
return View(groups);
}

Adicionar uma instrução using:

using System.Data.Common;

Execute o aplicativo e acesse a página Sobre. Ela exibe os mesmos dados que antes.

Chamar uma consulta de atualização


Suponha que os administradores do Contoso University desejem executar alterações globais no banco de dados,
como alterar o número de créditos para cada curso. Se a universidade tiver uma grande quantidade de cursos,
poderá ser ineficiente recuperá-los como entidades e alterá-los individualmente. Nesta seção, você implementará
uma página da Web que permite ao usuário especificar um fator pelo qual alterar o número de créditos para
todos os cursos e você fará a alteração executando uma instrução SQL UPDATE. A página da Web será
semelhante à seguinte ilustração:

Em CoursesController.cs, adicione métodos UpdateCourseCredits para HttpGet e HttpPost:

public IActionResult UpdateCourseCredits()


{
return View();
}

[HttpPost]
public async Task<IActionResult> UpdateCourseCredits(int? multiplier)
{
if (multiplier != null)
{
ViewData["RowsAffected"] =
await _context.Database.ExecuteSqlCommandAsync(
"UPDATE Course SET Credits = Credits * {0}",
parameters: multiplier);
}
return View();
}

Quando o controlador processa uma solicitação HttpGet, nada é retornado em ViewData["RowsAffected"] , e a


exibição mostra uma caixa de texto vazia e um botão Enviar, conforme mostrado na ilustração anterior.
Quando o botão Atualizar recebe um clique, o método HttpPost é chamado e multiplicador tem o valor inserido
na caixa de texto. Em seguida, o código executa o SQL que atualiza os cursos e retorna o número de linhas
afetadas para a exibição em ViewData . Quando a exibição obtém um valor RowsAffected , ela mostra o número
de linhas atualizadas.
No Gerenciador de Soluções, clique com o botão direito do mouse na pasta Views/Courses e, em seguida,
clique em Adicionar > Novo Item.
Na caixa de diálogo Adicionar Novo Item, clique em ASP.NET em Instalado no painel esquerdo, clique em
Página de Exibição MVC e nomeie a nova exibição UpdateCourseCredits.cshtml.
Em Views/Courses/UpdateCourseCredits.cshtml, substitua o código de modelo pelo seguinte código:
@{
ViewBag.Title = "UpdateCourseCredits";
}

<h2>Update Course Credits</h2>

@if (ViewData["RowsAffected"] == null)


{
<form asp-action="UpdateCourseCredits">
<div class="form-actions no-color">
<p>
Enter a number to multiply every course's credits by: @Html.TextBox("multiplier")
</p>
<p>
<input type="submit" value="Update" class="btn btn-default" />
</p>
</div>
</form>
}
@if (ViewData["RowsAffected"] != null)
{
<p>
Number of rows updated: @ViewData["RowsAffected"]
</p>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>

Execute o método UpdateCourseCredits selecionando a guia Cursos, adicionando, em seguida,


"/UpdateCourseCredits" ao final da URL na barra de endereços do navegador (por exemplo:
http://localhost:5813/Courses/UpdateCourseCredits ). Insira um número na caixa de texto:

Clique em Atualizar. O número de linhas afetadas é exibido:


Clique em Voltar para a Lista para ver a lista de cursos com o número revisado de créditos.
Observe que o código de produção deve garantir que as atualizações sempre resultem em dados válidos. O
código simplificado mostrado aqui pode multiplicar o número de créditos o suficiente para resultar em números
maiores que 5. (A propriedade Credits tem um atributo [Range(0, 5)] .) A consulta de atualização funciona,
mas os dados inválidos podem causar resultados inesperados em outras partes do sistema que supõem que o
número de créditos seja 5 ou inferior.
Para obter mais informações sobre consultas SQL brutas, consulte Consultas SQL brutas.

Examinar o SQL enviado ao banco de dados


Às vezes, é útil poder ver as consultas SQL reais que são enviadas ao banco de dados. A funcionalidade de log
interno do ASP.NET Core é usada automaticamente pelo EF Core para gravar logs que contêm o SQL de
consultas e atualizações. Nesta seção, você verá alguns exemplos de log do SQL.
Abra StudentsController.cs e, no método Details , defina um ponto de interrupção na instrução
if (student == null) .

Execute o aplicativo no modo de depuração e acesse a página Detalhes de um aluno.


Acesse a janela de Saída mostrando a saída de depuração e você verá a consulta:

Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (56ms) [Parameters=


[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT TOP(2) [s].[ID], [s].[Discriminator], [s].[FirstName], [s].[LastName], [s].[EnrollmentDate]
FROM [Person] AS [s]
WHERE ([s].[Discriminator] = N'Student') AND ([s].[ID] = @__id_0)
ORDER BY [s].[ID]
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (122ms) [Parameters=
[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT [s.Enrollments].[EnrollmentID], [s.Enrollments].[CourseID], [s.Enrollments].[Grade], [s.Enrollments].
[StudentID], [e.Course].[CourseID], [e.Course].[Credits], [e.Course].[DepartmentID], [e.Course].[Title]
FROM [Enrollment] AS [s.Enrollments]
INNER JOIN [Course] AS [e.Course] ON [s.Enrollments].[CourseID] = [e.Course].[CourseID]
INNER JOIN (
SELECT TOP(1) [s0].[ID]
FROM [Person] AS [s0]
WHERE ([s0].[Discriminator] = N'Student') AND ([s0].[ID] = @__id_0)
ORDER BY [s0].[ID]
) AS [t] ON [s.Enrollments].[StudentID] = [t].[ID]
ORDER BY [t].[ID]

Você observará algo aqui que pode ser surpreendente: o SQL seleciona até 2 linhas ( TOP(2) ) da tabela Person.
O método SingleOrDefaultAsync não é resolvido para uma 1 linha no servidor. Eis o porquê:
Se a consulta retorna várias linhas, o método retorna nulo.
Para determinar se a consulta retorna várias linhas, o EF precisa verificar se ela retorna pelo menos 2.
Observe que você não precisa usar o modo de depuração e parar em um ponto de interrupção para obter a
saída de log na janela de Saída. É apenas um modo conveniente de parar o log no ponto em que você deseja
examinar a saída. Se você não fizer isso, o log continuará e você precisará rolar para baixo para localizar as
partes de seu interesse.

Padrões de repositório e unidade de trabalho


Muitos desenvolvedores escrevem um código para implementar padrões de repositório e unidade de trabalho
como um wrapper em torno do código que funciona com o Entity Framework. Esses padrões destinam-se a criar
uma camada de abstração entre a camada de acesso a dados e a camada da lógica de negócios de um aplicativo.
A implementação desses padrões pode ajudar a isolar o aplicativo de alterações no armazenamento de dados e
pode facilitar o teste de unidade automatizado ou TDD (desenvolvimento orientado por testes). No entanto,
escrever um código adicional para implementar esses padrões nem sempre é a melhor escolha para aplicativos
que usam o EF, por vários motivos:
A própria classe de contexto do EF isola o código de código específico a um armazenamento de dados.
A classe de contexto do EF pode atuar como uma classe de unidade de trabalho para as atualizações de
banco de dados feitas com o EF.
O EF inclui recursos para implementar o TDD sem escrever um código de repositório.
Para obter informações sobre como implementar os padrões de repositório e unidade de trabalho, consulte a
versão do Entity Framework 5 desta série de tutoriais.
O Entity Framework Core implementa um provedor de banco de dados em memória que pode ser usado para
teste. Para obter mais informações, consulte Testando com InMemory.

Detecção automática de alterações


O Entity Framework determina como uma entidade foi alterada (e, portanto, quais atualizações precisam ser
enviadas ao banco de dados), comparando os valores atuais de uma entidade com os valores originais. Os
valores originais são armazenados quando a entidade é consultada ou anexada. Alguns dos métodos que
causam a detecção automática de alterações são os seguintes:
DbContext.SaveChanges
DbContext.Entry
ChangeTracker.Entries
Se você estiver controlando um grande número de entidades e chamar um desses métodos muitas vezes em um
loop, poderá obter melhorias significativas de desempenho desativando temporariamente a detecção automática
de alterações usando a propriedade ChangeTracker.AutoDetectChangesEnabled . Por exemplo:

_context.ChangeTracker.AutoDetectChangesEnabled = false;

Código-fonte e planos de desenvolvimento do Entity Framework Core


A fonte do Entity Framework Core está localizada em https://github.com/aspnet/EntityFrameworkCore. O
repositório do EF Core contém builds noturnos, acompanhamento de questões, especificações de recurso, notas
de reuniões de design e o roteiro para desenvolvimento futuro. Arquive ou encontre bugs e contribua.
Embora o código-fonte seja aberto, há suporte completo para o Entity Framework Core como um produto
Microsoft. A equipe do Microsoft Entity Framework mantém controle sobre quais contribuições são aceitas e
testa todas as alterações de código para garantir a qualidade de cada versão.

Fazer engenharia reversa do banco de dados existente


Para fazer engenharia reversa de um modelo de dados, incluindo classes de entidade de um banco de dados
existente, use o comando scaffold-dbcontext. Consulte o tutorial de introdução.

Usar o LINQ dinâmico para simplificar o código de seleção de


classificação
O terceiro tutorial desta série mostra como escrever um código LINQ embutindo nomes de colunas em código
em uma instrução switch . Com duas colunas para escolha, isso funciona bem, mas se você tiver muitas colunas,
o código poderá ficar detalhado. Para resolver esse problema, use o método EF.Property para especificar o
nome da propriedade como uma cadeia de caracteres. Para usar essa abordagem, substitua o método Index no
StudentsController 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) ? "LastName_desc" : "";
ViewData["DateSortParm"] =
sortOrder == "EnrollmentDate" ? "EnrollmentDate_desc" : "EnrollmentDate";

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

if (string.IsNullOrEmpty(sortOrder))
{
sortOrder = "LastName";
}

bool descending = false;


if (sortOrder.EndsWith("_desc"))
{
sortOrder = sortOrder.Substring(0, sortOrder.Length - 5);
descending = true;
}

if (descending)
{
students = students.OrderByDescending(e => EF.Property<object>(e, sortOrder));
}
else
{
students = students.OrderBy(e => EF.Property<object>(e, sortOrder));
}

int pageSize = 3;
return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(),
page ?? 1, pageSize));
}

Próximas etapas
Isso conclui esta série de tutoriais sobre como usar o Entity Framework Core em um aplicativo ASP.NET MVC.
Para obter mais informações sobre o EF Core, consulte a documentação do Entity Framework Core. Um manual
também está disponível: Entity Framework Core in Action (Entity Framework Core em ação).
Para obter informações sobre como implantar um aplicativo Web, consulte Hospedar e implantar.
Para obter informações sobre outros tópicos relacionados ao ASP.NET Core MVC, como autenticação e
autorização, consulte a documentação do ASP.NET Core.

Agradecimentos
Tom Dykstra e Rick Anderson (twitter @RickAndMSFT) escreveram este tutorial. Rowan Miller, Diego Vega e
outros membros da equipe do Entity Framework auxiliaram com revisões de código e ajudaram com problemas
de depuração que surgiram durante a codificação para os tutoriais.

Erros comuns
ContosoUniversity.dll usada por outro processo
Mensagem de erro:

Não é possível abrir '...bin\Debug\netcoreapp1.0\ContosoUniversity.dll' para gravação – 'O processo não


pode acessar o arquivo '...\bin\Debug\netcoreapp1.0\ContosoUniversity.dll' porque ele está sendo usado por
outro processo.

Solução:
Pare o site no IIS Express. Acesse a Bandeja do Sistema do Windows, localize o IIS Express e clique com o botão
direito do mouse em seu ícone, selecione o site da Contoso University e, em seguida, clique em Parar Site.
Migração gerada por scaffolding sem nenhum código nos métodos Up e Down
Possível causa:
Os comandos da CLI do EF não fecham e salvam arquivos de código automaticamente. Se você tiver alterações
não salvas ao executar o comando migrations add , o EF não encontrará as alterações.
Solução:
Execute o comando migrations remove , salve as alterações de código e execute o comando migrations add
novamente.
Erros durante a execução da atualização de banco de dados
É possível receber outros erros ao fazer alterações de esquema em um banco de dados que contém dados
existentes. Se você receber erros de migração que não consegue resolver, altere o nome do banco de dados na
cadeia de conexão ou exclua o banco de dados. Com um novo banco de dados, não há nenhum dado a ser
migrado e o comando de atualização de banco de dados terá uma probabilidade muito maior de ser concluído
sem erros.
A abordagem mais simples é renomear o banco de dados em appsettings.json. Na próxima vez que você
executar database update , um novo banco de dados será criado.
Para excluir um banco de dados no SSOX, clique com o botão direito do mouse no banco de dados, clique
Excluir e, em seguida, na caixa de diálogo Excluir Banco de Dados, selecione Fechar conexões existentes e
clique em OK.
Para excluir um banco de dados usando a CLI, execute o comando database drop da CLI:
dotnet ef database drop

Erro ao localizar a instância do SQL Server


Mensagem de erro:

Ocorreu um erro relacionado à rede ou específico a uma instância ao estabelecer uma conexão com o SQL
Server. O servidor não foi encontrado ou não estava acessível. Verifique se o nome da instância está correto
e se o SQL Server está configurado para permitir conexões remotas. (provedor: Adaptadores de Rede do
SQL, erro: 26 – Erro ao Localizar Servidor/Instância Especificada)

Solução:
Verifique a cadeia de conexão. Se você excluiu o arquivo de banco de dados manualmente, altere o nome do
banco de dados na cadeia de caracteres de construção para começar novamente com um novo banco de dados.

Anterior
Tutoriais da plataforma cruzada ASP.NET Core
31/01/2018 • 1 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 Mac
Páginas do Razor com o VS Code
Criar um aplicativo Web MVC ASP.NET Core
Aplicativo Web com o Visual Studio para Mac
Aplicativo Web MVC ASP.NET Core com o Visual Studio Code no Mac ou Linux

Criar APIs da Web


Criar uma API Web com o ASP.NET Core
API Web com o Visual Studio para Mac
API Web com o Visual Studio Code
Criar um aplicativo Web de Páginas do Razor com o
ASP.NET Core no macOS com o Visual Studio para
Mac
27/04/2018 • 1 min to read • Edit Online

Esse é um trabalho em andamento.


Esta 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
no macOS.
1. Introdução às Páginas do Razor no macOS
2. Adicionar um modelo a um aplicativo de Páginas do Razor
3. Páginas do Razor geradas por scaffolding
4. Trabalhar com SQLite
5. Atualizar as páginas
6. Adicionar pesquisa
Até a próxima seção ser concluída, execute a versão do Visual Studio para Windows.
1. Adicionar um novo campo
2. Adicionar validação
Introdução a Páginas do Razor no ASP.NET Core
com o Visual Studio para Mac
31/01/2018 • 4 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.
Recomendamos que você examine a Introdução a Páginas do Razor antes de iniciar este tutorial. Páginas do Razor
é a maneira recomendada para criar a interface do usuário para aplicativos Web no ASP.NET Core.

Pré-requisitos
Instale o seguinte:
SDK do .NET Core 2.0.0 ou posterior
Visual Studio para Mac

Criar um aplicativo Web do Razor


Em um terminal, execute os seguintes comandos:

dotnet new razor -o RazorPagesMovie


cd RazorPagesMovie
dotnet run

Os comandos anteriores usam a CLI do .NET Core para criar e executar um projeto de Páginas do Razor. Abra um
navegador em http://localhost:5000 para exibir o aplicativo.
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.

Abrir o projeto
Pressione Ctrl + C para encerrar o aplicativo.
No Visual Studio, selecione Arquivo > Abrir e, em seguida, selecione o arquivo RazorPagesMovie.csproj.
Iniciar o aplicativo
No Visual Studio, selecione Executar > Iniciar Sem Depuração para iniciar o aplicativo. O Visual Studio inicia o
IIS Express, inicia um navegador e navega para http://localhost:5000 .
No próximo tutorial, adicionaremos um modelo para o projeto.

Próximo: adicionando um modelo


Adicionando um modelo para um aplicativo de
Páginas do Razor no ASP.NET Core com o Visual
Studio para Mac
31/01/2018 • 8 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 e então
selecione Adicionar > Nova Pasta. Nomeie a pasta Models.
Clique com o botão direito do mouse na pasta Modelos e, em seguida, selecione Adicionar > Novo
Arquivo.
Na caixa de diálogo Novo Arquivo:
Selecione Geral no painel esquerdo.
Selecione Classe Vazia no painel central.
Nomeie a classe Movie e selecione Novo.
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": "Data Source=MvcMovie.db"
}
}

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.UseSqlite(Configuration.GetConnectionString("MovieContext")));
services.AddMvc();
}

Clique com o botão direito do mouse em uma linha curvada vermelha, por exemplo, MovieContext na linha
services.AddDbContext<MovieContext>(options => . Selecione Correção Rápida > using
RazorPagesMovie.Models;. O Visual studio adiciona a instrução using.
Compile o projeto para verificar se não há erros.
Pacotes NuGet do Entity Framework Core para migrações
As ferramentas do EF para a CLI (interface de linha de comando) são fornecidas em
Microsoft.EntityFrameworkCore.Tools.DotNet. Clique no link Microsoft.EntityFrameworkCore.Tools.DotNet para
obter o número de versão a ser usado. Para instalar esse pacote, adicione-o à coleção DotNetCliToolReference no
arquivo .csproj. 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.
Para editar um arquivo .csproj:
Selecione Arquivo > Abrir, e, em seguida, selecione o arquivo .csproj.
Selecione Opções.
Alterar Abrir com para Editor de código-fonte.

Adicione a referência da ferramenta Microsoft.EntityFrameworkCore.Tools.DotNet para o segundo <ItemGroup>.:

<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.2" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.1" />
</ItemGroup>
</Project>

Os números de versão mostrados no código a seguir estavam corretos no momento da gravação.

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


Na linha de comando, execute os seguintes comandos de CLI do .NET Core:

dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design


dotnet restore
dotnet ef migrations add InitialCreate
dotnet ef database update
O comando add package instala as ferramentas necessárias para executar o mecanismo de scaffolding.
O comando ef migrations add InitialCreate 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
InitialCreate é 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 ef database update 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 um erro similar ao seguinte, verifique se você executou migrações e atualizou o banco de dados:

An unhandled exception occurred while processing the request.


'no such table: Movie'.

Adicionar os arquivos de páginas/filmes ao projeto


No Visual Studio, clique com o botão direito do mouse na pasta Páginas e selecione Adicionar > Adicionar
Pasta Existente.
Selecione a pasta Filmes.
Na caixa de diálogo Selecionar arquivos para incluir no projeto, selecione Incluir Todos.
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
02/02/2018 • 12 min to read • Edit Online

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 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 SQLite e a propagação do banco de dados.

Anterior: adicionando um modelo Próximo: SQLite


Trabalhando com SQLite e páginas Razor
08/05/2018 • 3 min to read • Edit Online

Por Rick Anderson


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.UseSqlite(Configuration.GetConnectionString("MovieContext")));
services.AddMvc();
}

SQLite
O site do SQLite afirma:

O SQLite é um mecanismo de banco de dados SQL independente, de alta confiabilidade, inserido, completo e
de domínio público. O SQLite é o mecanismo de banco de dados mais usado no mundo.

Há muitas ferramentas de terceiros que podem ser baixadas para gerenciar e exibir um banco de dados SQLite. A
imagem abaixo é do Navegador do DB para SQLite. Se você tiver uma ferramenta favorita do SQLite, deixe um
comentário sobre o que mais gosta dela.
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.


if (context.Movie.Any())
{
return; // DB has been seeded.
}

Adicionar o inicializador de semeadura


Adicione o inicializador de semeadura ao 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 (para que o método de semeadura seja executado). Interrompa e inicie o
aplicativo para propagar o banco de dados.
O aplicativo mostra os dados propagados.
A N T E R IO R : A D IC IO N A N D O U M P R Ó X IM O : A T U A L IZ A R A S
M ODELO P Á G IN A S
Atualizar as páginas geradas
08/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;
using System.ComponentModel.DataAnnotations;

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; }
}
}
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 SQLite Adicionando uma pesquisa


Adicionando pesquisa a um aplicativo de Páginas
Razor
08/05/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:

@{
Layout = "_Layout";
}

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.

A N T E R IO R : A T U A L IZ A N D O A S P R Ó X IM O : A D IC IO N A N D O U M N O V O
P Á G IN A S CAM PO
Criar um aplicativo Web de Páginas do Razor com o
ASP.NET Core e o Visual Studio Code
27/04/2018 • 1 min to read • Edit Online

Esse é um trabalho em andamento.


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 Code.
1. Introdução às Páginas do Razor com o VSCode
2. Adicionar um modelo a um aplicativo de Páginas do Razor
3. Páginas do Razor geradas por scaffolding
4. Trabalhar com SQLite
5. Atualizar as páginas
6. Adicionar pesquisa
Até a próxima seção ser concluída, execute a versão do Visual Studio para Windows.
1. Adicionar um novo campo
2. Adicionar validação
Introdução a Páginas do Razor no ASP.NET Core
com o Visual Studio Code
31/01/2018 • 5 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.
Recomendamos que você conclua a Introdução a Páginas do Razor antes de iniciar este tutorial. Páginas do Razor
é a maneira recomendada para criar a interface do usuário para aplicativos Web no ASP.NET Core.

Pré-requisitos
Instale o seguinte:
SDK do .NET Core 2.0.0 ou posterior
Visual Studio Code
Extensão C# do VS Code

Criar um aplicativo Web do Razor


Em um terminal, execute os seguintes comandos:

dotnet new razor -o RazorPagesMovie


cd RazorPagesMovie
dotnet run

Os comandos anteriores usam a CLI do .NET Core para criar e executar um projeto de Páginas do Razor. Abra um
navegador em http://localhost:5000 para exibir o aplicativo.
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.

Abrir o projeto
Pressione Ctrl + C para encerrar o aplicativo.
No Visual Studio Code (VSCode), selecione Arquivo > Abrir Pasta e, em seguida, selecione a pasta
RazorPagesMovie.
Selecione Sim para a mensagem de Aviso "Os ativos necessários para criar e depurar estão ausentes no
'RazorPagesMovie'. Deseja adicioná-los?"
Selecione Restaurar para a mensagem Informativa "Há dependências não resolvidas".
Iniciar o aplicativo
Pressione Ctrl+F5 para iniciar o aplicativo sem depuração. Alternativamente, do menu Depurar, selecione Iniciar
Sem Depurar.
No próximo tutorial, adicionaremos um modelo para o projeto.

Próximo: adicionando um modelo


Adicionando um modelo para um aplicativo de
Páginas do Razor no ASP.NET Core com o Visual
Studio para Mac
31/01/2018 • 7 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


Adicione uma pasta denominada Modelos.
Adicionar uma classe denominada Movie.cs à pasta Modelos.
Adicione o código a seguir ao arquivo Models/Movie.cs:
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": "Data Source=MvcMovie.db"
}
}

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.UseSqlite(Configuration.GetConnectionString("MovieContext")));
services.AddMvc();
}

Crie o projeto para verificar se você não tem nenhum erro.


Pacotes NuGet do Entity Framework Core para migrações
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. 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 RazorPagesMovie.csproj:
Selecione Arquivo > Abrir Arquivo, e, em seguida, selecione o arquivo RazorPagesMovie.csproj.
Adicione a referência da ferramenta para o Microsoft.EntityFrameworkCore.Tools.DotNet ao segundo
<ItemGroup>:

<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.2" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.1" />
</ItemGroup>
</Project>

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


Na linha de comando, execute os seguintes comandos de CLI do .NET Core:
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet restore
dotnet ef migrations add InitialCreate
dotnet ef database update

O comando add package instala as ferramentas necessárias para executar o mecanismo de scaffolding.
O comando ef migrations add InitialCreate 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
InitialCreate é 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 ef database update executa o método Up no arquivo Migrations/<time-stamp>_InitialCreate.cs, que
cria o banco de dados.
Fazer scaffolding do modelo de filme
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:
Observação: execute o comando a seguir no Windows. Para MacOS e Linux, consulte o próximo
comando

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


referenceScriptLibraries

No MacOS e Linux, execute o seguinte comando:

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


referenceScriptLibraries

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 :
console 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 um erro similar ao seguinte, verifique se você executou migrações e atualizou o banco de dados:
An unhandled exception occurred while processing the request. 'no such table: Movie'. 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/02/2018 • 12 min to read • Edit Online

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 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 SQLite e a propagação do banco de dados.

Anterior: adicionando um modelo Próximo: SQLite


Trabalhando com SQLite e páginas Razor
08/05/2018 • 3 min to read • Edit Online

Por Rick Anderson


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.UseSqlite(Configuration.GetConnectionString("MovieContext")));
services.AddMvc();
}

SQLite
O site do SQLite afirma:

O SQLite é um mecanismo de banco de dados SQL independente, de alta confiabilidade, inserido, completo e
de domínio público. O SQLite é o mecanismo de banco de dados mais usado no mundo.

Há muitas ferramentas de terceiros que podem ser baixadas para gerenciar e exibir um banco de dados SQLite. A
imagem abaixo é do Navegador do DB para SQLite. Se você tiver uma ferramenta favorita do SQLite, deixe um
comentário sobre o que mais gosta dela.
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.


if (context.Movie.Any())
{
return; // DB has been seeded.
}

Adicionar o inicializador de semeadura


Adicione o inicializador de semeadura ao 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 (para que o método de semeadura seja executado). Interrompa e inicie o
aplicativo para propagar o banco de dados.
O aplicativo mostra os dados propagados.
A N T E R IO R : A D IC IO N A N D O U M P R Ó X IM O : A T U A L IZ A R A S
M ODELO P Á G IN A S
Atualizar as páginas geradas
08/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;
using System.ComponentModel.DataAnnotations;

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; }
}
}
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 SQLite Adicionando uma pesquisa


Adicionando pesquisa a um aplicativo de Páginas
Razor
08/05/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:

@{
Layout = "_Layout";
}

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.

A N T E R IO R : A T U A L IZ A N D O A S P R Ó X IM O : A D IC IO N A N D O U M N O V O
P Á G IN A S CAM PO
Criar um aplicativo Web com o ASP.NET Core MVC
no macOS com o Visual Studio para Mac
10/04/2018 • 1 min to read • Edit Online

Esta série de tutoriais ensina os conceitos básicos da criação de um aplicativo Web ASP.NET Core MVC usando o
Visual Studio para Mac.
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.
1. Introdução
2. Adicionar um controlador
3. Adicionar uma exibição
4. Adicionar um modelo
5. SQLite
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
para Mac
31/01/2018 • 3 min to read • Edit Online

Por Rick Anderson


Este tutorial ensina os conceitos básicos da criação de um aplicativo Web ASP.NET Core MVC usando o Visual
Studio para Mac.
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: Compilar um aplicativo ASP.NET Core MVC com o Visual Studio para Mac
Windows: Compilar um aplicativo ASP.NET Core MVC com o Visual Studio
Linux, macOS e Windows: Compilar um aplicativo ASP.NET Core MVC com o Visual Studio Code

Pré-requisitos
Este tutorial exige o SDK do .NET Core 2.0.0 ou posterior.
Instale o seguinte:
SDK do .NET Core 2.0.0 ou posterior
Visual Studio para Mac

Criar um aplicativo Web


No Visual Studio, selecione Arquivo > Nova Solução.
Selecione Aplicativo .NET Core > ASP.NET Core > Aplicativo Web > Avançar.

Nomeie o projeto MvcMovie e, em seguida, selecione Criar.


Iniciar o aplicativo
No Visual Studio, selecione Executar > Iniciar Sem Depuração para iniciar o aplicativo. O Visual Studio inicia
o Kestrel, inicia um navegador e navega para http://localhost:port , em que porta é um número da porta
escolhido aleatoriamente.
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. Quando você executar o aplicativo, verá um número da porta diferente.
Você pode iniciar o aplicativo no modo de depuração ou sem depuração no item de menu Executar.
O modelo padrão fornece os links 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.

Na próxima parte deste tutorial, você saberá mais sobre o MVC e começará a escrever um pouco de código.

Avançar
Adicionando um controlador a um aplicativo
ASP.NET Core MVC com o Visual Studio Code para
Mac
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.

Adicionar um controlador
No Gerenciador de Soluções, clique com o botão direito do mouse em Controladores > Adicionar > Novo
Arquivo.
Selecione ASP.NET Core e Classe do Controlador MVC.
Nomeie o controlador 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.

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.

Adicionar uma exibição


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 Arquivo.
Na caixa de diálogo Novo Arquivo:
Selecione Web no painel esquerdo.
Selecione Arquivo HTML vazio no painel central.
Digite Index.cshtml na caixa Nome.
Selecione Novo.
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 • 9 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


Clique com o botão direito do mouse na pasta Modelos e, em seguida, selecione Adicionar > Novo
Arquivo.
Na caixa de diálogo Novo Arquivo:
Selecione Geral no painel esquerdo.
Selecione Classe Vazia no painel central.
Nomeie a classe Movie e selecione Novo.
Adicione as seguintes propriedades à classe Movie :

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 seu aplicativo MVC.

Preparar o projeto para scaffolding


Clique com o botão direito do mouse no arquivo de projeto e, em seguida, selecione Ferramentas >
Editar Arquivo.

Adicione os pacotes NuGet realçados a seguir ao arquivo MvcMovie.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" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0"
/>
</ItemGroup>
</Project>

Salve o arquivo.
Crie um arquivo Models/MvcMovieContext.cs e adicione a seguinte classe MvcMovieContext : [!code-
csharpMain]
Abra o arquivo Startup.cs e adicione dois usings: [!code-csharpMain]
Adicione o contexto do banco de dados para o arquivo Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();

services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlite("Data Source=MvcMovie.db"));

Isso informa ao Entity Framework quais classes de modelo estão incluídas no modelo de dados. Você está
definindo um conjunto de entidades de objetos Movie, que serão representados no banco de dados como
uma tabela Movie.
Crie o projeto para verificar se não existem erros.

Faça o scaffolding do MovieController


Abra uma janela de terminal na pasta do projeto e execute os seguintes comandos:

dotnet restore
dotnet aspnet-codegenerator controller -name MoviesController -m Movie -dc MvcMovieContext --
relativeFolderPath Controllers --useDefaultLayout --referenceScriptLibraries

Se você obtiver o erro No executable found matching command "dotnet-aspnet-codegenerator", verify :


Você está no diretório do projeto. O diretório do projeto tem os arquivos Program.cs, Startup.cs e .csproj.
Sua versão do dotnet é 1.1 ou posterior. Execute dotnet para obter a versão.
Você adicionou o elemento <DotNetCliToolReference> ao arquivo MvcMovie.csproj.
O mecanismo de scaffolding cria o seguinte:
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 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.
Adicionar os arquivos ao Visual Studio
Adicione o arquivo MovieController.cs ao projeto do Visual Studio:
Clique com o botão direito do mouse na pasta Controladores e selecione Adicionar > Adicionar
Arquivos.
Selecione o arquivo MovieController.cs.
Adicione a pasta Filmes e as exibições:
Clique com o botão direito do mouse na pasta Exibições e selecione Adicionar > Adicionar Pasta
Existente.
Navegue até a pasta Exibições, selecione Exibições\Filmes e, em seguida, selecione Abrir.
Na caixa de diálogo Selecionar arquivos para adicionar de Filmes, selecione Incluir Todos e, em
seguida, OK.

Executar a migração inicial


Na linha de comando, execute os seguintes comandos de CLI do .NET Core:
dotnet ef migrations add InitialCreate
dotnet ef database update

O comando dotnet ef migrations add InitialCreate gera código para criar o esquema de banco de dados inicial.
O esquema é baseado no modelo especificado no DbContext (no arquivo Models/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 dotnet ef database update executa o método Up no arquivo Migrations/<time-
stamp>_InitialCreate.cs, que cria o banco de dados.

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.
Agora você tem um banco de dados e páginas para exibir, editar, atualizar e excluir dados. No próximo tutorial,
trabalharemos com o banco de dados.

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 SQLite em um projeto ASP.NET
Core MVC
08/05/2018 • 3 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.UseSqlite("Data Source=MvcMovie.db"));

SQLite
O site do SQLite afirma:

O SQLite é um mecanismo de banco de dados SQL independente, de alta confiabilidade, inserido, completo e
de domínio público. O SQLite é o mecanismo de banco de dados mais usado no mundo.

Há muitas ferramentas de terceiros que podem ser baixadas para gerenciar e exibir um banco de dados SQLite. A
imagem abaixo é do Navegador do DB para SQLite. Se você tiver uma ferramenta favorita do SQLite, deixe um
comentário sobre o que mais gosta dela.
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.


if (context.Movie.Any())
{
return; // DB has been seeded.
}

Adicionar o inicializador de semeadura


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 (para que o método de semeadura seja executado). Interrompa e inicie o
aplicativo para propagar o banco de dados.
O aplicativo mostra os dados propagados.
A N T E R IO R – A D IC IO N A R U M P R Ó X IM O – E X IB IÇ Õ E S E M É T O D O S D O
M ODELO C ON TROL A D OR
Exibições e métodos do controlador em um
aplicativo ASP.NET Core MVC
08/05/2018 • 14 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 a seguir) 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.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; }
}
}

Compile e execute o aplicativo.


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

A N T E R IO R – T R A B A L H A N D O C O M O P R Ó X IM O – A D IC IO N A R U M A
S Q L IT E P E S Q U IS A
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?}");
});

Observação: o SQLlite diferencia maiúsculas de minúsculas e, portanto, você precisará pesquisar “Ghost” e não
“ghost”.
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 .
Altere a marcação <form> na exibição Views\movie\Index.cshtml do Razor para especificar method="get" :
<form asp-controller="Movies" asp-action="Index" method="get">

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 – E X IB IÇ Õ E S E M É T O D O S D O P R Ó X IM O – A D IC IO N A R U M
C ON TROL A D OR CAM PO
Adicionando um novo campo
08/05/2018 • 5 min to read • Edit Online

Por Rick Anderson


Este tutorial adicionará um novo campo à tabela Movies . Removeremos o banco de dados e criaremos um novo
ao alterar o esquema (adicionar um novo campo). Este fluxo de trabalho funciona bem no início do
desenvolvimento quando não temos nenhum dado de produção para preservar.
Depois que o aplicativo for implantado e você tiver dados que precisa preservar, não poderá remover o BD
quando precisar alterar o esquema. As Migrações do Entity Framework Code First permitem atualizar o esquema
e migrar o banco de dados sem perder dados. As Migrações são um recurso popular ao usar o SQL Server, mas o
SQLite não dá suporte a muitas operações de esquema de migração e, portanto, apenas migrações muito simples
são possíveis. Consulte Limitações do SQLite para obter mais informações.

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

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 .


O aplicativo não funcionará até que atualizemos o BD para incluir o novo campo. Se você executá-lo agora, obterá
o seguinte SqliteException :

SqliteException: SQLite Error 1: 'no such column: m.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 Rating na tabela de banco de dados.)
Existem algumas abordagens para resolver o erro:
1. Remova o banco de dados e faça com que o Entity Framework recrie automaticamente o banco de dados
com base no novo esquema de classe de modelo. Com essa abordagem, você perde os dados existentes no
banco de dados – portanto, não é possível fazer isso com 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 manualmente 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, removeremos e recriaremos o banco de dados quando o esquema for alterado. Execute o
seguinte comando em um terminal para remover o BD:
dotnet ef database drop

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

Adicione o campo Rating às exibições Edit , Details e Delete .


Execute o aplicativo e verifique se você pode criar/editar/exibir filmes com um campo Rating . modelos.

A N T E R IO R – A D IC IO N A R U M A P R Ó X IM O – A D IC IO N A R
P E S Q U IS A V A L ID A Ç Ã O
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 – A D IC IO N A R U M P R Ó X IM O – E X A M IN A R O S M É T O D O S D E T A IL S E
CAM PO D E L E TE
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 um aplicativo ASP.NET Core MVC com o Visual
Studio Code
10/04/2018 • 1 min to read • Edit Online

Esta série de tutoriais ensina os conceitos básicos da criação de um aplicativo Web ASP.NET Core MVC usando o
Visual Studio Code.
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.
1. Introdução
2. Adicionar um controlador
3. Adicionar uma exibição
4. Adicionar um modelo
5. Trabalhar com SQLite
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 no Mac, Linux ou
Windows
31/01/2018 • 4 min to read • Edit Online

Por Rick Anderson


Este tutorial ensinará os conceitos básicos da criação de um aplicativo Web ASP.NET Core MVC usando o VS
Code (Visual Studio Code). O tutorial pressupõe que você já tenha familiaridade com o VS Code. Consulte
Introdução ao VS Code e Ajuda do Visual Studio Code para obter mais informações.
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: Criar um aplicativo ASP.NET Core MVC com o Visual Studio para Mac
Windows: Criar um aplicativo ASP.NET Core MVC com o Visual Studio
macOS, Linux e Windows: Criar um aplicativo ASP.NET Core MVC com o Visual Studio Code

Instalar o VS Code e o .NET Core


Este tutorial exige o SDK do .NET Core 2.0.0 ou posterior. Consulte este pdf para a versão ASP.NET Core 1.1.
Instale o seguinte:
SDK do .NET Core 2.0.0 ou posterior.
Visual Studio Code
Extensão C# do VS Code

Criar um aplicativo Web com o dotnet


Em um terminal, execute os seguintes comandos:

mkdir MvcMovie
cd MvcMovie
dotnet new mvc

Abra a pasta MvcMovie 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 “MvcMovie”. 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.

O VS Code inicia o servidor Web Kestrel e executa o aplicativo. Observe que a barra de endereços mostra
localhost:5000 e não algo como example.com . Isso ocorre porque localhost é o nome do host padrão do
computador local.
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.

Na próxima parte deste tutorial, saberemos mais sobre o MVC e começaremos a escrever um pouco de código.

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óximo – Adicionar um controlador


Adicionando um controlador a um aplicativo
ASP.NET Core MVC com o Visual Studio Code
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 VS Code, selecione o ícone EXPLORER e, em seguida, pressione Control (clique com o botão direito do
mouse) Controladores > Novo Arquivo e nomeie o novo arquivo HelloWorldController.cs.
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.

Anterior – Adicionar um controlador Próximo – Adicionar uma exibição


Adicionando uma exibição a um aplicativo ASP.NET
Core MVC
08/05/2018 • 14 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.
Adicione uma exibição Index ao HelloWorldController .
Adicione uma nova pasta chamada Views/HelloWorld.
Adicione um novo arquivo à pasta Views/HelloWorld chamada Index.cshtml.
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>
<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>
<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.
A N T E R IO R – A D IC IO N A R U M P R Ó X IM O – A D IC IO N A R U M
C ON TROL A D OR M ODELO
Adicione um modelo a um aplicativo ASP.NET Core
MVC
08/05/2018 • 8 min to read • Edit Online

Adicionando um modelo a um aplicativo ASP.NET Core


MVC
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


Adicionar uma classe denominada Movie.cs à pasta Modelos.
Adicione o código a seguir ao arquivo Models/Movie.cs:

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 aplicativo para verificar se você não tem nenhum erro e, por fim, você adicionou um Modelo ao seu
aplicativo MVC.

Preparar o projeto para scaffolding


Adicione os pacotes NuGet realçados a seguir ao arquivo MvcMovie.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" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0"
/>
</ItemGroup>
</Project>

Salve o arquivo e selecione Restaurar para a mensagem Informativa "Há dependências não resolvidas".
Crie um arquivo Models/MvcMovieContext.cs e adicione a seguinte classe MvcMovieContext :

using Microsoft.EntityFrameworkCore;

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

public DbSet<MvcMovie.Models.Movie> Movie { get; set; }


}
}

Abra o arquivo Startup.cs e adicione dois usings:

using Microsoft.EntityFrameworkCore;
using MvcMovie.Models;

namespace MvcMovie
{
public class Startup
{

Adicione o contexto do banco de dados para o arquivo Startup.cs:

public void ConfigureServices(IServiceCollection services)


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

services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlite("Data Source=MvcMovie.db"));

Isso informa ao Entity Framework quais classes de modelo estão incluídas no modelo de dados. Você está
definindo um conjunto de entidades de objetos Movie, que serão representados no banco de dados como
uma tabela Movie.
Crie o projeto para verificar se não existem erros.

Faça o scaffolding do MovieController


Abra uma janela de terminal na pasta do projeto e execute os seguintes comandos:

dotnet restore
dotnet aspnet-codegenerator controller -name MoviesController -m Movie -dc MvcMovieContext --
relativeFolderPath Controllers --useDefaultLayout --referenceScriptLibraries

O mecanismo de scaffolding cria o seguinte:


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

Executar a migração inicial


Na linha de comando, execute os seguintes comandos de CLI do .NET Core:

dotnet ef migrations add InitialCreate


dotnet ef database update

O comando dotnet ef migrations add InitialCreate gera código para criar o esquema de banco de dados inicial.
O esquema é baseado no modelo especificado no DbContext (no arquivo Models/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 dotnet ef database update executa o método Up no arquivo Migrations/<time-
stamp>_InitialCreate.cs, que cria o banco de dados.

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.
Agora você tem um banco de dados e páginas para exibir, editar, atualizar e excluir dados. No próximo tutorial,
trabalharemos com o banco de dados.
Recursos adicionais
Auxiliares de marcação
Globalização e localização

A N T E R IO R – A D IC IO N A R U M A P R Ó X IM O – T R A B A L H A N D O C O M
E X IB IÇ Ã O S Q L IT E
Trabalhando com o SQLite em um projeto ASP.NET
Core MVC
08/05/2018 • 3 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.UseSqlite("Data Source=MvcMovie.db"));

SQLite
O site do SQLite afirma:

O SQLite é um mecanismo de banco de dados SQL independente, de alta confiabilidade, inserido, completo e
de domínio público. O SQLite é o mecanismo de banco de dados mais usado no mundo.

Há muitas ferramentas de terceiros que podem ser baixadas para gerenciar e exibir um banco de dados SQLite. A
imagem abaixo é do Navegador do DB para SQLite. Se você tiver uma ferramenta favorita do SQLite, deixe um
comentário sobre o que mais gosta dela.
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.


if (context.Movie.Any())
{
return; // DB has been seeded.
}

Adicionar o inicializador de semeadura


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 (para que o método de semeadura seja executado). Interrompa e inicie o
aplicativo para propagar o banco de dados.
O aplicativo mostra os dados propagados.
A N T E R IO R – A D IC IO N A R U M P R Ó X IM O – E X IB IÇ Õ E S E M É T O D O S D O
M ODELO C ON TROL A D OR
Exibições e métodos do controlador
31/01/2018 • 14 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.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; }
}
}

Compile e execute o aplicativo.


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 – Trabalhando com o SQLite Próximo – Adicionar uma pesquisa


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

Observação: o SQLlite diferencia maiúsculas de minúsculas e, portanto, você precisará pesquisar “Ghost” e não
“ghost”.
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 .
Altere a marcação <form> na exibição Views\movie\Index.cshtml do Razor para especificar method="get" :
<form asp-controller="Movies" asp-action="Index" method="get">

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 – E X IB IÇ Õ E S E M É T O D O S D O P R Ó X IM O – A D IC IO N A R U M
C ON TROL A D OR CAM PO
Adicionando um novo campo
08/05/2018 • 5 min to read • Edit Online

Por Rick Anderson


Este tutorial adicionará um novo campo à tabela Movies . Removeremos o banco de dados e criaremos um novo
ao alterar o esquema (adicionar um novo campo). Este fluxo de trabalho funciona bem no início do
desenvolvimento quando não temos nenhum dado de produção para preservar.
Depois que o aplicativo for implantado e você tiver dados que precisa preservar, não poderá remover o BD
quando precisar alterar o esquema. As Migrações do Entity Framework Code First permitem atualizar o esquema
e migrar o banco de dados sem perder dados. As Migrações são um recurso popular ao usar o SQL Server, mas o
SQLite não dá suporte a muitas operações de esquema de migração e, portanto, apenas migrações muito simples
são possíveis. Consulte Limitações do SQLite para obter mais informações.

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

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 .


O aplicativo não funcionará até que atualizemos o BD para incluir o novo campo. Se você executá-lo agora, obterá
o seguinte SqliteException :

SqliteException: SQLite Error 1: 'no such column: m.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 Rating na tabela de banco de dados.)
Existem algumas abordagens para resolver o erro:
1. Remova o banco de dados e faça com que o Entity Framework recrie automaticamente o banco de dados
com base no novo esquema de classe de modelo. Com essa abordagem, você perde os dados existentes no
banco de dados – portanto, não é possível fazer isso com 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 manualmente 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, removeremos e recriaremos o banco de dados quando o esquema for alterado. Execute o
seguinte comando em um terminal para remover o BD:
dotnet ef database drop

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

Adicione o campo Rating às exibições Edit , Details e Delete .


Execute o aplicativo e verifique se você pode criar/editar/exibir filmes com um campo Rating . modelos.

A N T E R IO R – A D IC IO N A R U M A P R Ó X IM O – A D IC IO N A R
P E S Q U IS A V A L ID A Ç Ã O
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 – A D IC IO N A R U M P R Ó X IM O – E X A M IN A R O S M É T O D O S D E T A IL S E
CAM PO D E L E TE
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 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 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.
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
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 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.

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
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.
Conceitos básicos do ASP.NET Core
08/05/2018 • 14 min to read • Edit Online

Um aplicativo ASP.NET Core é um aplicativo de console que cria um servidor Web em seu método Main :
ASP.NET Core 2.x
ASP.NET Core 1.x

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;

namespace aspnetcoreapp
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}

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


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

O método Main invoca WebHost.CreateDefaultBuilder , que segue o padrão de construtor para criar um host de
aplicativo Web. O construtor tem métodos que definem o servidor Web (por exemplo, UseKestrel ) e a classe de
inicialização ( UseStartup ). No exemplo anterior, o servidor Web Kestrel é alocado automaticamente. O host Web
do ASP.NET Core tenta executar no IIS, se disponível. Outros servidores Web como HTTP.sys podem ser usados
ao chamar o método de extensão apropriado. UseStartup é explicado em mais detalhes na próxima seção.
IWebHostBuilder , o tipo de retorno da invocação de WebHost.CreateDefaultBuilder , fornece muitos métodos
opcionais. Alguns desses métodos incluem UseHttpSys para hospedar o aplicativo em HTTP.sys e UseContentRoot
para especificar o diretório de conteúdo raiz. Os métodos Build e Run compilam o objeto IWebHost que
hospeda o aplicativo e começa a escutar solicitações HTTP.

Inicialização
O método UseStartup em WebHostBuilder especifica a classe Startup para seu aplicativo:
ASP.NET Core 2.x
ASP.NET Core 1.x
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}

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


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

É na classe Startup que você define o pipeline de tratamento de solicitação e é nela que todos os serviços que o
aplicativo precisa estão configurados. A classe Startup deve ser pública e conter os seguintes métodos:

public class Startup


{
// This method gets called by the runtime. Use this method
// to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
}

// This method gets called by the runtime. Use this method


// to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app)
{
}
}

ConfigureServices define os Serviços usados pelo seu aplicativo (por exemplo, ASP.NET Core MVC, Entity
Framework Core, Identity etc.). Configure define o middleware para o pipeline de solicitação.
Para obter mais informações, veja Inicialização do aplicativo.

Raiz do conteúdo
A raiz do conteúdo é o caminho base para qualquer conteúdo usado pelo aplicativo, tal como exibições, Páginas
do Razor e ativos estáticos. Por padrão, a raiz do conteúdo é o mesmo caminho base do aplicativo para o
executável que hospeda o aplicativo.

Raiz da Web
A raiz Web de um aplicativo é o diretório do projeto que contém recursos públicos e estáticos como CSS,
JavaScript e arquivos de imagem.

Injeção de dependência (serviços)


Um serviço é um componente que é destinado ao consumo comum em um aplicativo. Os serviços são
disponibilizados por meio de DI (injeção de dependência). O ASP.NET Core inclui um contêiner nativo de IoC
(Inversão de Controle) que dá suporte a injeção de construtor por padrão. É possível substituir o contêiner nativo
padrão se desejar. Além do benefício de seu acoplamento flexível, a DI disponibiliza serviços por todo o seu
aplicativo (por exemplo, registrar em log).
Para obter mais informações, consulte Injeção de dependência.
Middleware
No ASP.NET Core, você compõe o pipeline de solicitação usando o middleware. O middleware do ASP.NET Core
executa lógica assíncrona em um HttpContext e então invoca o próximo middleware na sequência ou encerra a
solicitação diretamente. Um componente de middleware chamado "XYZ" é adicionado ao invocar um UseXYZ
método de extensão no método Configure .
O ASP.NET Core inclui um conjunto avançado de middleware interno:
Arquivos estáticos
Roteamento
Autenticação
Middleware de compactação de resposta
Middleware de regravação de URL
O middleware com base em OWIN está disponível para aplicativos do ASP.NET Core e é possível escrever seu
próprio middleware personalizado.
Para obter mais informações, consulte Middleware e OWIN (Interface da Web Aberta para .NET).

Iniciar solicitações HTTP


Para saber mais sobre como usar o IHttpClientFactory para acessar instâncias do HttpClient a fim de fazer
solicitações HTTP, confira Iniciar solicitações HTTP.

Ambientes
Os ambientes, como "Desenvolvimento" e "Produção", são uma noção de primeira classe no ASP.NET Core e
podem ser definidos usando variáveis de ambiente.
Para obter mais informações, veja Trabalhar com vários ambientes.

Configuração
O ASP.NET Core usa um modelo de configuração com base nos pares de nome-valor. O modelo de configuração
não baseado em System.Configuration ou em web.config. A configuração obtém as definições de um conjunto
ordenado de provedores de configuração. Os provedores internos de configuração dão suporte a uma variedade
de formatos de arquivo (XML, JSON, INI) e variáveis de ambiente para habilitar a configuração do ambiente. Você
também pode escrever seus próprios provedores de configuração personalizados.
Para obter mais informações, consulte Configuração.

Registrando em log
O ASP.NET Core dá suporte a uma API de registro em log que funciona com uma variedade de provedores de
logs. Os provedores internos dão suporte ao envio de logs para um ou mais destinos. As estruturas de registro
em log de terceiros podem ser usadas.
Registro em log

Tratamento de erros
O ASP.NET Core tem recursos internos para manipular erros em aplicativos, incluindo uma página de exceção de
desenvolvedor, páginas de erro personalizadas, páginas de código de status estático e tratamento de exceções de
inicialização.
Para obter mais informações, veja Como tratar erros.

Roteamento
O ASP.NET Core oferece recursos para roteamento de solicitações de aplicativo para manipuladores de rotas.
Para obter mais informações, consulte Roteamento.

Provedores de arquivo
O ASP.NET Core resume o acesso de sistema de arquivos por meio do uso de Provedores de Arquivo, que oferece
uma interface comum para trabalhar com arquivos entre plataformas.
Para obter mais informações, consulte Provedores de Arquivos.

Arquivos estáticos
O middleware de arquivos estáticos fornece arquivos estáticos, como HTML, CSS, imagem e JavaScript.
Para obter mais informações, veja Trabalhar com arquivos estáticos.

Hospedagem
Os aplicativos ASP.NET Core configuram e iniciam um host, que é responsável pelo gerenciamento de
inicialização e de tempo de vida do aplicativo.
Para obter mais informações, consulte Hospedagem.

Estado de sessão e de aplicativo


O estado de sessão é um recurso do ASP.NET Core que você pode usar para salvar e armazenar dados de usuário
enquanto o usuário navega seu aplicativo Web.
Para obter mais informações, consulte Estado de sessão e aplicativo.

Servidores
O modelo de hospedagem do ASP.NET Core não escuta diretamente as solicitações. O modelo de host se baseia
em uma implementação do servidor HTTP para encaminhar a solicitação ao aplicativo. A solicitação encaminhada
é empacotada como um conjunto de objetos de recurso que podem ser acessados por meio de interfaces. O
ASP.NET Core inclui um servidor Web gerenciado e de plataforma cruzada chamado Kestrel. O Kestrel
normalmente é executado atrás de um servidor Web de produção, como IIS ou Nginx. O Kestrel pode ser
executado como um servidor de borda.
Para obter mais informações, consulte Servidores e os seguintes tópicos:
Kestrel
Módulo do ASP.NET Core
HTTP.sys (anteriormente chamado WebListener)

Globalização e localização
Criar um site multilíngue com o ASP.NET Core permite que seu site alcance um público maior. O ASP.NET Core
fornece serviços e middleware para localização em diferentes idiomas e culturas.
Para obter mais informações, consulte Globalização e localização.
Recursos de solicitação
Os detalhes de implementação de servidor Web relacionados a solicitações HTTP e respostas são definidos em
interfaces. Essas interfaces são usadas pelas implementações de servidor e pelo middleware para criar e modificar
o pipeline de hospedagem do aplicativo.
Para obter mais informações, consulte Solicitar Recursos.

Tarefas em segundo plano


As tarefas em segundo plano são implementadas como serviços hospedados. Um serviço hospedado é uma classe
com lógica de tarefa em segundo plano que implementa a interface IHostedService.
Saiba mais em Tarefas em segundo plano com serviços hospedados.

OWIN (Open Web Interface para .NET)


O ASP.NET Core dá suporte para OWIN (Open Web Interface para .NET). O OWIN permite que os aplicativos
Web sejam separados dos servidores Web.
Para obter mais informações, consulte OWIN (Interface da Web Aberta para .NET).

WebSockets
O WebSocket é um protocolo que permite canais de comunicação persistentes bidirecionais em conexões TCP. Ele
é usado em aplicativos como chat, cotações de ações, jogos e em qualquer lugar que desejar funcionalidade em
tempo real em um aplicativo Web. O ASP.NET Core dá suporte a recursos de soquete da Web.
Para obter mais informações, consulte WebSockets.

Metapacote Microsoft.AspNetCore.All
O Metapacote do Microsoft.AspNetCore.All para ASP.NET Core inclui:
Todos os pacotes com suporte da equipe do ASP.NET Core.
Todos os pacotes com suporte pelo Entity Framework Core.
Dependências internas e de terceiros usadas por ASP.NET Core e pelo Entity Framework Core.
Para obter mais informações, consulte Microsoft.AspNetCore.All metapackage.

Tempo de execução do .NET Core vs do .NET Framework


Um aplicativo do ASP.NET Core pode atingir 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.

Escolher entre o ASP.NET Core e o ASP.NET


Para obter mais informações sobre como escolher entre ASP.NET Core e ASP.NET, consulte Escolha entre
ASP.NET Core e ASP.NET.
Inicialização do aplicativo no ASP.NET Core
20/02/2018 • 10 min to read • Edit Online

Por Steve Smith, Tom Dykstra e Luke Latham


A classe Startup configura serviços e o pipeline de solicitação do aplicativo.

A classe Startup
Os aplicativos do ASP.NET Core usam uma classe Startup , que é chamada de Startup por convenção. A
classe Startup :
Também é possível incluir um método ConfigureServices para configurar os serviços do aplicativo.
Deve incluir um método Configure para criar o pipeline de processamento de solicitações do aplicativo.
ConfigureServices e Configure são chamados pelo tempo de execução quando o aplicativo é iniciado:

public class Startup


{
// Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
...
}

// Use this method to configure the HTTP request pipeline.


public void Configure(IApplicationBuilder app)
{
...
}
}

Especifique a classe Startup com o método WebHostBuilderExtensions UseStartup<TStartup>:

public class Program


{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}

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


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

O construtor de classe Startup aceita dependências definidas pelo host. Um uso comum da injeção de
dependência na classe Startup é injetar:
IHostingEnvironment para configurar serviços pelo ambiente.
IConfiguration para configurar o aplicativo durante a inicialização.
public class Startup
{
public Startup(IHostingEnvironment env, IConfiguration config)
{
HostingEnvironment = env;
Configuration = config;
}

public IHostingEnvironment HostingEnvironment { get; }


public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)


{
if (HostingEnvironment.IsDevelopment())
{
// Development configuration
}
else
{
// Staging/Production configuration
}

// Configuration is available during startup. Examples:


// Configuration["key"]
// Configuration["subsection:suboption1"]
}
}

Uma alternativa a injetar IHostingEnvironment é usar uma abordagem baseada em convenções. O aplicativo
pode definir classes Startup separadas para ambientes diferentes (por exemplo, StartupDevelopment ) e a
classe de inicialização adequada é selecionada em tempo de execução. A classe cujo sufixo do nome
corresponde ao ambiente atual é priorizada. Se o aplicativo for executado no ambiente de desenvolvimento
e incluir uma classe Startup e uma classe StartupDevelopment , a classe StartupDevelopment será usada. Para
obter mais informações, consulte Working with multiple environments (Trabalhando com vários ambientes).
Para saber mais sobre WebHostBuilder , consulte tópico sobre Hospedagem. Para obter informações sobre
como tratar erros durante a inicialização, consulte Tratamento de exceção na inicialização.

O método ConfigureServices
O método ConfigureServices é:
Opcional.
Chamado pelo host da Web antes do Configure método para configurar os serviços do aplicativo.
Onde as opções de configuração são definidas por convenção.
Adicionar serviços ao contêiner de serviços os torna disponíveis dentro do aplicativo e no método
Configure . Os serviços são resolvidos via injeção de dependência ou
IApplicationBuilder.ApplicationServices.
O host da Web pode configurar alguns serviços antes que métodos Startup sejam chamados. Detalhes
estão disponíveis no tópico sobre Hospedagem.
Para recursos que exigem uma configuração significativa, há métodos de extensão Add[Service] em
IServiceCollection. Um aplicativo Web típico registra serviços para o Entity Framework, Identity e MVC:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

services.AddMvc();

// Add application services.


services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}

Não disponíveis na Inicialização


O host da Web fornece alguns serviços que estão disponíveis para o construtor de classe Startup . O
aplicativo adiciona serviços adicionais por meio de ConfigureServices . Os Serviços de Aplicativos e o host
ficam, então, disponíveis em Configure e em todo o aplicativo.

O método Configure
O método Configure é usado para especificar como o aplicativo responde as solicitações HTTP. O pipeline de
solicitação é configurado adicionando componentes de middleware a uma instância de IApplicationBuilder.
IApplicationBuilder está disponível para o método Configure , mas não é registrado no contêiner de
serviço. A hospedagem cria um IApplicationBuilder e o passa diretamente para Configure (fonte de
referência).
Os modelos do ASP.NET Core configuram o pipeline com suporte para uma página de exceção de
desenvolvedor, BrowserLink, páginas de erro, arquivos estáticos e o ASP.NET MVC:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)


{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

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

Cada método de extensão Use adiciona um componente de middleware ao pipeline de solicitação. Por
exemplo, o método de extensão UseMvc adiciona o middleware de roteamento ao pipeline de solicitação e
configura MVC como o manipulador padrão.
Serviços adicionais, como IHostingEnvironment e ILoggerFactory , também podem ser especificados na
assinatura do método. Quando especificados, serviços adicionais são injetados quando estão disponíveis.
Para obter mais informações sobre como usar o IApplicationBuilder , consulte Middleware.

Métodos de conveniência
Os métodos de conveniência ConfigureServices e Configure podem ser usados em vez de especificar uma
classe Startup . Diversas chamadas para ConfigureServices são acrescentadas umas às outras. Diversas
chamadas para Configure usam a última chamada de método.

public class Program


{
public static IHostingEnvironment HostingEnvironment { get; set; }
public static IConfiguration Configuration { get; set; }

public static void Main(string[] args)


{
BuildWebHost(args).Run();
}

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


WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
HostingEnvironment = hostingContext.HostingEnvironment;
Configuration = config.Build();
})
.ConfigureServices(services =>
{
services.AddMvc();
})
.Configure(app =>
{
if (HostingEnvironment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}

// Configuration is available during startup. Examples:


// Configuration["key"]
// Configuration["subsection:suboption1"]

app.UseMvcWithDefaultRoute();
app.UseStaticFiles();
})
.Build();
}

Filtros de inicialização
Use IStartupFilter para configurar o middleware no início ou no final do pipeline do middleware Configure
de um aplicativo. IStartupFilter é útil para garantir que um middleware seja executado antes ou depois do
middleware adicionado pelas bibliotecas no início ou no final do pipeline de processamento de solicitação do
aplicativo.
IStartupFilter implementa um único método, Configure, que recebe e retorna um
Action<IApplicationBuilder> . Um IApplicationBuilder define uma classe para configurar o pipeline de
solicitação do aplicativo. Para obter mais informações, consulte Criando um pipeline do middleware com o
IApplicationBuilder.
Cada IStartupFilter implementa uma ou mais middlewares no pipeline de solicitação. Os filtros são
invocados na ordem em que foram adicionados ao contêiner de serviço. Filtros podem adicionar middleware
antes ou depois de passar o controle para o filtro seguinte, de modo que eles são acrescentados ao início ou
no final do pipeline de aplicativo.
O aplicativo de exemplo (como baixar) demonstra como registrar um middleware com IStartupFilter . O
aplicativo de exemplo inclui um middleware que define um valor de opção de um parâmetro de cadeia de
caracteres de consulta:

public class RequestSetOptionsMiddleware


{
private readonly RequestDelegate _next;
private IOptions<AppOptions> _injectedOptions;

public RequestSetOptionsMiddleware(
RequestDelegate next, IOptions<AppOptions> injectedOptions)
{
_next = next;
_injectedOptions = injectedOptions;
}

public async Task Invoke(HttpContext httpContext)


{
Console.WriteLine("RequestSetOptionsMiddleware.Invoke");

var option = httpContext.Request.Query["option"];

if (!string.IsNullOrWhiteSpace(option))
{
_injectedOptions.Value.Option = WebUtility.HtmlEncode(option);
}

await _next(httpContext);
}
}

O RequestSetOptionsMiddleware é configurado na classe RequestSetOptionsStartupFilter :

public class RequestSetOptionsStartupFilter : IStartupFilter


{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return builder =>
{
builder.UseMiddleware<RequestSetOptionsMiddleware>();
next(builder);
};
}
}

O IStartupFilter é registrado no contêiner de serviço em ConfigureServices :

public void ConfigureServices(IServiceCollection services)


{
services.AddTransient<IStartupFilter, RequestSetOptionsStartupFilter>();
services.AddMvc();
}
Quando um parâmetro de cadeia de caracteres de consulta para option é fornecido, o middleware processa
a atribuição de valor antes que o middleware do MVC renderize a resposta:

A ordem de execução do middleware é definida pela ordem dos registros IStartupFilter :


Várias implementações de IStartupFilter podem interagir com os mesmos objetos. Se a ordem for
importante, ordene seus registros de serviço IStartupFilter para corresponder à ordem em que os
middlewares devem ser executados.
Bibliotecas podem adicionar middleware com uma ou mais implementações de IStartupFilter que são
executadas antes ou depois de outro middleware de aplicativo registrado com IStartupFilter . Para
invocar um middleware IStartupFilter antes de um middleware adicionado pelo IStartupFilter de
uma biblioteca, posicione o registro do serviço antes da biblioteca ser adicionada ao contêiner de serviço.
Para invocá-lo posteriormente, posicione o registro do serviço após a biblioteca ser adicionada.

Recursos adicionais
Hospedagem
Trabalhando com vários ambientes
Middleware
Registro em log
Configuração
Classe StartupLoader: método FindStartupType (fonte de referência)
Injeção de dependência no ASP.NET
Core
12/02/2018 • 30 min to read • Edit Online

Por Steve Smith e Scott Addie


O ASP.NET Core foi projetado desde o início para dar suporte e utilizar a injeção
de dependência. Os aplicativos ASP.NET Core podem utilizar os serviços de
estrutura interna mantendo-os injetados em métodos na classe Startup e os
Serviços de Aplicativos também podem ser configurados para injeção. O contêiner
de serviços padrão fornecido pelo ASP.NET Core fornece um conjunto de recursos
mínimos e não se destina a substituir outros contêineres.
Exibir ou baixar código de exemplo (como baixar)

O que é injeção de dependência?


A DI (injeção de dependência) é uma técnica para obter um acoplamento flexível
entre objetos e seus colaboradores, ou dependências. Em vez de criar uma
instância de colaboradores diretamente, ou usar referências estáticas, os objetos
de que uma classe precisa para realizar suas ações são fornecidos para a classe de
alguma forma. Geralmente, as classes declararão suas dependências por meio de
seu construtor, possibilitando que elas sigam o Princípio de Dependências
Explícitas. Essa abordagem é conhecida como "injeção de construtor".
Quando as classes são criadas com a DI em mente, elas têm um acoplamento mais
flexível porque não têm dependências diretas e embutidas em código em seus
colaboradores. Isso segue o Princípio de Inversão de Dependência, que indica que
"módulos de nível alto não devem depender de módulos de nível baixo; ambos
devem depender de abstrações". Em vez de referenciar implementações
específicas, as classes solicitam abstrações (geralmente, interfaces ) que são
fornecidas a elas quando a classe é construída. A extração de dependências em
interfaces e fornecimento de implementações dessas interfaces como parâmetros
também é um exemplo do Padrão de design de estratégia.
Quando um sistema é projetado para usar a DI, com muitas classes solicitando
suas dependências por meio de seu construtor (ou propriedades), é útil ter uma
classe dedicada à criação dessas classes com suas dependências associadas. Essas
classes são chamadas de contêineres ou mais especificamente, contêineres IoC
(Inversão de Controle) ou contêineres DI (Injeção de Dependência). Basicamente,
um contêiner é um alocador responsável por fornecer instâncias dos tipos
solicitados dele. Se determinado tipo declarou que tem dependências e o contêiner
foi configurado para fornecer os tipos de dependência, ele criará as dependências
como parte da criação da instância solicitada. Dessa forma, grafos de dependência
complexos podem ser fornecidos para classes sem a necessidade de uma
construção de objeto embutida em código. Além de criar objetos com suas
dependências, os contêineres normalmente gerenciam tempos de vida de objeto
no aplicativo.
O ASP.NET Core inclui um contêiner interno simples (representado pela interface
IServiceProvider ) que dá suporte à injeção de construtor por padrão e o ASP.NET
disponibiliza alguns serviços por meio da DI. O contêiner do ASP.NET refere-se
aos tipos que ele gerencia como serviços. No restante deste artigo, serviços se
referirão aos tipos que são gerenciados pelo contêiner IoC do ASP.NET Core.
Configure os serviços do contêiner interno no método ConfigureServices na
classe Startup do aplicativo.

OBSERVAÇÃO
Martin Fowler escreveu um artigo abrangente sobre Contêineres de Inversão de
Controle e o padrão de injeção de dependência. O grupo Padrões e Práticas Microsoft
também tem uma ótima descrição sobre a Injeção de Dependência.

OBSERVAÇÃO
Este artigo aborda a Injeção de Dependência aplicável a todos os aplicativos ASP.NET. A
Injeção de Dependência em controladores MVC é abordada em Injeção de Dependência
e controladores.

Comportamento da injeção de construtor


A injeção de construtor exige que o construtor em questão seja público. Caso
contrário, o aplicativo gerará uma InvalidOperationException :

Um construtor adequado para o tipo 'YourType' não pôde ser localizado.


Verifique se o tipo é concreto e se os serviços estão registrados para todos os
parâmetros de um construtor público.

A injeção de construtor exige apenas a existência de um construtor aplicável. Há


suporte para sobrecargas de construtor, mas somente uma sobrecarga pode existir,
cujos argumentos podem ser todos atendidos pela injeção de dependência. Se
houver mais de um, o aplicativo gerará uma InvalidOperationException :

Vários construtores que aceitam todos os tipos de argumento fornecidos


foram encontrados no tipo 'YourType'. Deve haver apenas um construtor
aplicável.

Os construtores podem aceitar argumentos que não são fornecidos pela injeção
de dependência, mas precisam dar suporte a valores padrão. Por exemplo:
// throws InvalidOperationException: Unable to resolve service for type
'System.String'...
public CharactersController(ICharacterRepository characterRepository, string
title)
{
_characterRepository = characterRepository;
_title = title;
}

// runs without error


public CharactersController(ICharacterRepository characterRepository, string
title = "Characters")
{
_characterRepository = characterRepository;
_title = title;
}

Usando serviços fornecidos pela estrutura


O método ConfigureServices na classe Startup é responsável por definir os
serviços que serão usados pelo aplicativo, incluindo recursos de plataforma como
o Entity Framework Core e ASP.NET Core MVC. Inicialmente, a
IServiceCollection fornecida ao ConfigureServices tem os seguintes serviços
definidos (dependendo de como o host foi configurado):

TIPO DE SERVIÇO TEMPO DE VIDA

Microsoft.AspNetCore.Hosting.IHostingEnvir Singleton
onment

Microsoft.Extensions.Logging.ILoggerFactor Singleton
y

Microsoft.Extensions.Logging.ILogger<T> Singleton

Microsoft.AspNetCore.Hosting.Builder.IAppli Transitório
cationBuilderFactory

Microsoft.AspNetCore.Http.IHttpContextFac Transitório
tory

Microsoft.Extensions.Options.IOptions<T> Singleton

System.Diagnostics.DiagnosticSource Singleton

System.Diagnostics.DiagnosticListener Singleton

Microsoft.AspNetCore.Hosting.IStartupFilter Transitório

Microsoft.Extensions.ObjectPool.ObjectPoolP Singleton
rovider

Microsoft.Extensions.Options.IConfigureOpti Transitório
ons<T>
TIPO DE SERVIÇO TEMPO DE VIDA

Microsoft.AspNetCore.Hosting.Server.IServe Singleton
r

Microsoft.AspNetCore.Hosting.IStartup Singleton

Microsoft.AspNetCore.Hosting.IApplicationLi Singleton
fetime

Veja abaixo um exemplo de como adicionar outros serviços ao contêiner usando


vários métodos de extensão como AddDbContext , AddIdentity e AddMvc .

// This method gets called by the runtime. Use this method to add services to
the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>

options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

services.AddMvc();

// Add application services.


services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}

Os recursos e o middleware fornecidos pelo ASP.NET, como o MVC, seguem uma


convenção de uso de um único método de extensão AddServiceName para
registrar todos os serviços exigidos pelo recurso.

DICA
Você pode solicitar alguns serviços fornecidos pela estrutura em métodos Startup por
meio de suas listas de parâmetros – consulte Inicialização do aplicativo para obter mais
detalhes.

Registrando serviços
Você pode registrar seus próprios Serviços de Aplicativos conforme mostrado a
seguir. O primeiro tipo genérico representa o tipo (normalmente uma interface)
que será solicitado do contêiner. O segundo tipo genérico representa o tipo
concreto do qual será criada uma instância pelo contêiner e que será usado para
atender a essas solicitações.

services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
OBSERVAÇÃO
Cada método de extensão services.Add<ServiceName> adiciona (e possivelmente
configura) serviços. Por exemplo, o services.AddMvc() adiciona os serviços exigidos
pelo MVC. É recomendável que você siga essa convenção, colocando os métodos de
extensão no namespace Microsoft.Extensions.DependencyInjection , para
encapsular grupos de registros de serviço.

O método AddTransient é usado para mapear tipos abstratos para serviços


concretos dos quais são criadas instâncias separadamente para cada objeto que
exigir isso. Isso é conhecido como o tempo de vida do serviço e opções de tempo
de vida adicionais são descritas abaixo. É importante escolher um tempo de vida
apropriado para cada um dos serviços registrados. Uma nova instância do serviço
deve ser fornecida para cada classe que a solicitar? Uma instância deve ser usada
durante uma solicitação da Web especificada? Ou uma única instância deve ser
usada para o tempo de vida do aplicativo?
Na amostra deste artigo, há um controlador simples que exibe os nomes de
caracteres, chamado CharactersController . Seu método Index exibe a lista atual
de caracteres que foram armazenados no aplicativo e inicializa a coleção com uma
série de caracteres, se não houver nenhum. Observe que, embora esse aplicativo
use o Entity Framework Core e a classe ApplicationDbContext para sua
persistência, nada disso é aparente no controlador. Em vez disso, o mecanismo de
acesso a dados específico foi abstraído por trás de uma interface
ICharacterRepository , que segue o padrão do repositório. Uma instância de
ICharacterRepository é solicitada por meio do construtor e atribuída a um campo
particular, que é então usado para acessar caracteres, conforme necessário.

public class CharactersController : Controller


{
private readonly ICharacterRepository _characterRepository;

public CharactersController(ICharacterRepository characterRepository)


{
_characterRepository = characterRepository;
}

// GET: /characters/
public IActionResult Index()
{
PopulateCharactersIfNoneExist();
var characters = _characterRepository.ListAll();

return View(characters);
}

private void PopulateCharactersIfNoneExist()


{
if (!_characterRepository.ListAll().Any())
{
_characterRepository.Add(new Character("Darth Maul"));
_characterRepository.Add(new Character("Darth Vader"));
_characterRepository.Add(new Character("Yoda"));
_characterRepository.Add(new Character("Mace Windu"));
}
}
}
O ICharacterRepository define os dois métodos que o controlador precisa para
trabalhar com instâncias Character .

using System.Collections.Generic;
using DependencyInjectionSample.Models;

namespace DependencyInjectionSample.Interfaces
{
public interface ICharacterRepository
{
IEnumerable<Character> ListAll();
void Add(Character character);
}
}

Por sua vez, essa interface é implementada por um tipo concreto,


CharacterRepository , que é usado em tempo de execução.

OBSERVAÇÃO
A maneira como a DI é usada com a classe CharacterRepository é um modelo geral
que você pode seguir para todos os Serviços de Aplicativos, não apenas em
"repositórios" ou classes de acesso a dados.

using System.Collections.Generic;
using System.Linq;
using DependencyInjectionSample.Interfaces;

namespace DependencyInjectionSample.Models
{
public class CharacterRepository : ICharacterRepository
{
private readonly ApplicationDbContext _dbContext;

public CharacterRepository(ApplicationDbContext dbContext)


{
_dbContext = dbContext;
}

public IEnumerable<Character> ListAll()


{
return _dbContext.Characters.AsEnumerable();
}

public void Add(Character character)


{
_dbContext.Characters.Add(character);
_dbContext.SaveChanges();
}
}
}

Observe que CharacterRepository solicita um ApplicationDbContext em seu


construtor. Não é incomum que a injeção de dependência seja usada de maneira
encadeada dessa forma, com cada dependência solicitada solicitando, por sua vez,
suas próprias dependências. O contêiner é responsável por resolver todas as
dependências no grafo e retornar o serviço totalmente resolvido.
OBSERVAÇÃO
A criação do objeto solicitado e todos os objetos exigidos por ele, além de todos os
objetos que esses outros exigem, é, às vezes, chamado de um grafo de objeto. Da
mesma forma, o conjunto de dependências que precisa ser resolvido normalmente é
chamado de uma árvore de dependência ou um grafo de dependência.

Nesse caso, ICharacterRepository e, por sua vez, ApplicationDbContext precisam


ser registrados no contêiner de serviços em ConfigureServices em Startup .
ApplicationDbContext é configurado com a chamada ao método de extensão
AddDbContext<T> . O código a seguir mostra o registro do tipo CharacterRepository
.

public void ConfigureServices(IServiceCollection services)


{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseInMemoryDatabase()
);

// Add framework services.


services.AddMvc();

// Register application services.


services.AddScoped<ICharacterRepository, CharacterRepository>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new
Operation(Guid.Empty));
services.AddTransient<OperationService, OperationService>();
}

Os contextos do Entity Framework devem ser adicionados ao contêiner de


serviços com o tempo de vida Scoped . Isso será resolvido automaticamente se
você usar os métodos auxiliares, conforme mostrado acima. Os repositórios que
farão uso do Entity Framework devem usar o mesmo tempo de vida.

AVISO
O principal perigo com o qual é necessário ter cautela é resolver um serviço Scoped em
um singleton. É provável que, nesse caso, o serviço terá um estado incorreto durante o
processamento das solicitações seguintes.

Serviços que têm dependências devem registrá-las no contêiner. Se o construtor


de um serviço exigir um primitivo, como uma string , isso poderá ser injetado
com o uso da configuração e do padrão de opções.

Tempos de vida do serviço e opções de registro


Os serviços do ASP.NET podem ser configurados com os seguintes tempos de
vida:
Transitório
Os serviços com tempo de vida transitório são criados sempre que são solicitados.
Esse tempo de vida funciona melhor para serviços leves e sem estado.
Com escopo
Os serviços com tempo de vida com escopo são criados uma vez por solicitação.
Singleton
Os serviços com tempo de vida singleton são criados na primeira vez em que são
solicitados (ou quando ConfigureServices for executado se você especificar uma
instância nele) e, em seguida, todas as solicitações seguintes usarão a mesma
instância. Se o aplicativo exigir o comportamento singleton, é recomendado
permitir que o contêiner de serviços gerencie o tempo de vida do serviço, em vez
de implementar o padrão de design singleton e gerenciar o tempo de vida do
objeto na classe por conta própria.
Os serviços podem ser registrados no contêiner de várias maneiras. Já vimos
como registrar uma implementação de serviço com um tipo fornecido,
especificando o tipo concreto a ser usado. Além disso, um alocador pode ser
especificado, que, em seguida, será usado para criar a instância sob demanda. A
terceira abordagem é especificar diretamente a instância do tipo a ser usada, nesse
caso, o contêiner nunca tentará criar uma instância (nem descartará a instância).
Para demonstrar a diferença entre essas opções de tempo de vida e registro,
considere uma interface simples que representa uma ou mais tarefas como uma
operação com um identificador exclusivo, OperationId . Dependendo de como
configurarmos o tempo de vida para esse serviço, o contêiner fornecerá as
mesmas instâncias ou instâncias diferentes do serviço para a classe solicitante.
Para tornar claro qual tempo de vida está sendo solicitado, criaremos um tipo por
opção de tempo de vida:

using System;

namespace DependencyInjectionSample.Interfaces
{
public interface IOperation
{
Guid OperationId { get; }
}

public interface IOperationTransient : IOperation


{
}
public interface IOperationScoped : IOperation
{
}
public interface IOperationSingleton : IOperation
{
}
public interface IOperationSingletonInstance : IOperation
{
}
}

Implementamos essas interfaces usando uma única classe, Operation , que aceita
um Guid em seu construtor, ou usa um novo Guid , se nenhum é fornecido.
Em seguida, em ConfigureServices , cada tipo é adicionado ao contêiner de acordo
com seu tempo de vida nomeado:
services.AddScoped<ICharacterRepository, CharacterRepository>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new
Operation(Guid.Empty));
services.AddTransient<OperationService, OperationService>();
}

Observe que o serviço IOperationSingletonInstance usa uma instância específica


com uma ID conhecida Guid.Empty , para que fique claro quando esse tipo está em
uso (seu Guid serão todos zeros). Também registramos um OperationService que
depende de cada um dos outros tipos Operation , de modo que fique claro em
uma solicitação se esse serviço está obtendo a mesma instância do controlador ou
uma nova, para cada tipo de operação. Tudo o que esse serviço faz é expor suas
dependências como propriedades, para que possam ser mostradas na exibição.

using DependencyInjectionSample.Interfaces;

namespace DependencyInjectionSample.Services
{
public class OperationService
{
public IOperationTransient TransientOperation { get; }
public IOperationScoped ScopedOperation { get; }
public IOperationSingleton SingletonOperation { get; }
public IOperationSingletonInstance SingletonInstanceOperation { get; }

public OperationService(IOperationTransient transientOperation,


IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance instanceOperation)
{
TransientOperation = transientOperation;
ScopedOperation = scopedOperation;
SingletonOperation = singletonOperation;
SingletonInstanceOperation = instanceOperation;
}
}
}

Para demonstrar os tempos de vida de objeto dentro e entre solicitações


individuais separadas para o aplicativo, a amostra inclui um OperationsController
que solicita cada tipo do tipo IOperation , bem como um OperationService . A
ação Index então exibe todos os valores OperationId do controlador e do
serviço.
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
using Microsoft.AspNetCore.Mvc;

namespace DependencyInjectionSample.Controllers
{
public class OperationsController : Controller
{
private readonly OperationService _operationService;
private readonly IOperationTransient _transientOperation;
private readonly IOperationScoped _scopedOperation;
private readonly IOperationSingleton _singletonOperation;
private readonly IOperationSingletonInstance
_singletonInstanceOperation;

public OperationsController(OperationService operationService,


IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance singletonInstanceOperation)
{
_operationService = operationService;
_transientOperation = transientOperation;
_scopedOperation = scopedOperation;
_singletonOperation = singletonOperation;
_singletonInstanceOperation = singletonInstanceOperation;
}

public IActionResult Index()


{
// viewbag contains controller-requested services
ViewBag.Transient = _transientOperation;
ViewBag.Scoped = _scopedOperation;
ViewBag.Singleton = _singletonOperation;
ViewBag.SingletonInstance = _singletonInstanceOperation;

// operation service has its own requested services


ViewBag.Service = _operationService;
return View();
}
}
}

Agora, duas solicitações separadas são feitas para esta ação do controlador:
Observe qual dos valores OperationId varia em uma solicitação e entre as
solicitações.
Objetos transitórios são sempre diferentes; uma nova instância é fornecida
para cada controlador e serviço.
Objetos com escopo são os mesmos em uma solicitação, mas diferentes em
diferentes solicitações
Objetos singleton são os mesmos para cada objeto e solicitação
(independentemente de uma instância ser fornecida em ConfigureServices )

Serviços de solicitação
Os serviços disponíveis em uma solicitação do ASP.NET de HttpContext são
expostos por meio da coleção RequestServices .

Os Serviços de Solicitação representam os serviços configurados e solicitados


como parte do aplicativo. Quando os objetos especificam dependências, elas são
atendidas pelos tipos encontrados em RequestServices , não ApplicationServices .
Em geral, você não deve usar essas propriedades diretamente, preferindo solicitar
os tipos exigidos pelas classes por meio do construtor da classe e permitindo que a
estrutura injete essas dependências. Isso produz classes que são mais fáceis de
serem testadas (consulte Teste) e que têm acoplamento mais flexível.

OBSERVAÇÃO
Prefira solicitar dependências como parâmetros de construtor para acessar a coleção
RequestServices .

Criando serviços para injeção de dependência


Você deve criar seus serviços para que usem a injeção de dependência para obter
seus colaboradores. Isso significa evitar o uso de chamadas de método estático
com estado (que resultam em um "code smell" conhecido como static cling) e a
criação direta de instâncias de classes dependentes em seus serviços. Isso pode
ajudar a lembrar a frase New is Glue, ao escolher se deseja criar uma instância de
um tipo ou solicitá-lo por meio da injeção de dependência. Seguindo os Princípios
SOLID de Design Orientado a Objeto, as classes naturalmente tendem a ser
pequenas, bem fatoradas e facilmente testadas.
E se você descobrir que as classes tendem a ter muitas dependências injetadas?
Isso geralmente é um sinal de que a classe está tentando fazer muito e,
provavelmente, está violando o SRP – o Princípio da Responsabilidade Única. Veja
se você pode refatorar a classe movendo algumas de suas responsabilidades para
uma nova classe. Lembre-se de que as classes Controller devem se concentrar
nos interesses da interface do usuário, de modo que os detalhes de
implementação de acesso a dados e as regras de negócios devem ser mantidos em
classes apropriadas a esses interesses separados.
Especificamente, com relação ao acesso a dados, você pode injetar o DbContext
nos controladores (supondo que você adicionou o EF ao contêiner de serviços em
ConfigureServices ). Alguns desenvolvedores preferem usar uma interface de
repositório para o banco de dados, em vez de injetar o DbContext diretamente. O
uso de uma interface para encapsular a lógica de acesso a dados em um único
local pode minimizar o número de locais que você precisará alterar quando o
banco de dados for alterado.
Descartando serviços
O contêiner chamará Dispose para os tipos IDisposable criados por ele. No
entanto, se você adicionar uma instância ao contêiner por conta própria, ele não
será descartado.
Exemplo:

// Services implement IDisposable:


public class Service1 : IDisposable {}
public class Service2 : IDisposable {}
public class Service3 : IDisposable {}

public interface ISomeService {}


public class SomeServiceImplementation : ISomeService, IDisposable {}

public void ConfigureServices(IServiceCollection services)


{
// container will create the instance(s) of these types and will dispose
them
services.AddScoped<Service1>();
services.AddSingleton<Service2>();
services.AddSingleton<ISomeService>(sp => new SomeServiceImplementation());

// container didn't create instance so it will NOT dispose it


services.AddSingleton<Service3>(new Service3());
services.AddSingleton(new Service3());
}
OBSERVAÇÃO
Na versão 1.0, o contêiner chamado descarta todos os objetos IDisposable , incluindo
aqueles que ele não criou.

Substituindo o contêiner de serviços padrão


O contêiner de serviços interno destina-se a atender às necessidades básicas da
estrutura e a maioria dos aplicativos de consumidor criada nele. No entanto, os
desenvolvedores podem substituir o contêiner interno por seu contêiner
preferencial. O método ConfigureServices normalmente retorna void , mas se
sua assinatura for alterada para retornar IServiceProvider , um contêiner diferente
poderá ser configurado e retornado. Há vários contêineres IOC disponíveis para o
.NET. Neste exemplo, o pacote Autofac é usado.
Primeiro, instale os pacotes de contêiner apropriados:
Autofac
Autofac.Extensions.DependencyInjection

Em seguida, configure o contêiner no ConfigureServices e retorne um


IServiceProvider :

public IServiceProvider ConfigureServices(IServiceCollection services)


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

// Add Autofac
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterModule<DefaultModule>();
containerBuilder.Populate(services);
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);
}

OBSERVAÇÃO
Ao usar um contêiner de DI de terceiros, é necessário alterar ConfigureServices para
que ele retorne IServiceProvider em vez de void .

Finalmente, configure o Autofac normalmente em DefaultModule :

public class DefaultModule : Module


{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<CharacterRepository>().As<ICharacterRepository>();
}
}

Em tempo de execução, o Autofac será usado para resolver tipos e injetar


dependências. Saiba mais sobre como usar o Autofac e o ASP.NET Core.
Acesso thread-safe
Os serviços Singleton precisam ser thread-safe. Se um serviço singleton tiver uma
dependência em um serviço transitório, o serviço transitório também precisará ser
thread-safe, dependendo de como ele é usado pelo singleton.

Recomendações
Ao trabalhar com a injeção de dependência, lembre-se das seguintes
recomendações:
A DI destina-se a objetos que têm dependências complexas. Controladores,
serviços, adaptadores e repositórios são exemplos de objetos que podem
ser adicionados à DI.
Evite armazenar dados e a configuração diretamente na DI. Por exemplo, o
carrinho de compras de um usuário normalmente não deve ser adicionado
ao contêiner de serviços. A configuração deve usar o padrão de opções. Da
mesma forma, evite objetos de "suporte de dados" que existem somente
para permitir o acesso a outro objeto. É melhor solicitar o item real
necessário por meio da DI, se possível.
Evite o acesso estático aos serviços.
Evite o local do serviço no código do aplicativo.
Evite o acesso estático a HttpContext .

OBSERVAÇÃO
Como todos os conjuntos de recomendações, talvez você encontre situações em que é
necessário ignorar um. Descobrimos que exceções são raras – a maioria, casos muito
especiais dentro da própria estrutura.

Lembre-se de que a injeção de dependência é uma alternativa aos padrões de


acesso a objeto estático/global. Você não poderá obter os benefícios da DI se
combiná-lo com o acesso a objeto estático.

Recursos adicionais
Inicialização de aplicativos
Teste
Ativação de middleware de fábrica
Como escrever um código limpo no ASP.NET Core com injeção de
dependência (MSDN )
Design de aplicativo gerenciado por contêiner, prelúdio: a que local o contêiner
pertence?
Princípio de Dependências Explícitas
Contêineres de Inversão de Controle e o padrão de Injeção de Dependência
(Fowler)
Middleware do ASP.NET Core
04/05/2018 • 17 min to read • Edit Online

Por Rick Anderson e Steve Smith


Exibir ou baixar código de exemplo (como baixar)

O que é um middleware?
O middleware é um software montado em um pipeline de aplicativo para manipular solicitações e respostas. Cada
componente:
Escolhe se deseja passar a solicitação para o próximo componente no pipeline.
Pode executar o trabalho antes e depois de o próximo componente no pipeline ser invocado.
Os delegados de solicitação são usados para criar o pipeline de solicitação. Os delegados de solicitação manipulam
cada solicitação HTTP.
Os delegados de solicitação são configurados usando os métodos de extensão Run, Map e Use. Um delegado de
solicitação individual pode ser especificado em linha como um método anônimo (chamado do middleware em
linha) ou pode ser definido em uma classe reutilizável. Essas classes reutilizáveis e os métodos anônimos em linha
são o middleware ou componentes do middleware. Cada componente do middleware no pipeline de solicitação é
responsável por invocar o próximo componente no pipeline ou por ligar a cadeia em curto-circuito, se apropriado.
A seção Migrating HTTP Modules to Middleware (Migrar módulos HTTP para o Middleware) explica a diferença
entre pipelines de solicitação no ASP.NET Core e no ASP.NET 4.x e fornece mais exemplos do middleware.

Criando um pipeline do middleware com o IApplicationBuilder


O pipeline de solicitação do ASP.NET Core consiste em uma sequência de delegados de solicitação, chamados um
após o outro, como mostrado neste diagrama (o thread de execução segue as setas pretas):

Cada delegado pode executar operações antes e depois do próximo delegado. Um delegado também pode optar
por não transmitir uma solicitação ao próximo delegado, o que também é chamado de ligar o pipeline de
solicitação em curto-circuito. O curto-circuito geralmente é desejável porque ele evita trabalho desnecessário. Por
exemplo, o middleware de arquivo estático pode retornar uma solicitação para um arquivo estático e ligar o
restante do pipeline em curto-circuito. Os delegados de tratamento de exceção precisam ser chamados no início do
pipeline, para que possam detectar exceções que ocorrem em etapas posteriores do pipeline.
O aplicativo ASP.NET Core mais simples possível define um delegado de solicitação única que controla todas as
solicitações. Este caso não inclui um pipeline de solicitação real. Em vez disso, uma única função anônima é
chamada em resposta a cada solicitação HTTP.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;

public class Startup


{
public void Configure(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Hello, World!");
});
}
}

O primeiro delegado app.Run encerra o pipeline.


É possível encadear vários delegados de solicitação junto com o app.Use. O parâmetro next representa o próximo
delegado no pipeline. (Lembre-se de que você pode ligar o pipeline em curto-circuito ao não chamar o próximo
parâmetro.) Normalmente, você pode executar ações antes e depois do próximo delegado, conforme este exemplo
demonstra:

public class Startup


{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
// Do work that doesn't write to the Response.
await next.Invoke();
// Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>


{
await context.Response.WriteAsync("Hello from 2nd delegate.");
});
}
}

AVISO
Não chame next.Invoke depois que a resposta tiver sido enviada ao cliente. Altera para HttpResponse depois que a
resposta foi iniciada e lançará uma exceção. Por exemplo, mudanças como a configuração de cabeçalhos, o código de status,
etc., lançarão uma exceção. Gravar no corpo da resposta após a chamada next :
Pode causar uma violação do protocolo. Por exemplo, gravar mais do que o content-length indicado.
Pode corromper o formato do corpo. Por exemplo, gravar um rodapé HTML em um arquivo CSS.
HttpResponse.HasStarted é uma dica útil para indicar se os cabeçalhos foram enviados e/ou o corpo foi gravado.
Ordenando
A ordem em que os componentes do middleware são adicionados ao método Configure define a ordem em que
eles são invocados nas solicitações e a ordem inversa para a resposta. Essa ordem é crítica para a segurança, o
desempenho e a funcionalidade.
O método Configure (mostrado abaixo) adiciona os seguintes componentes de middleware:
1. Exceção/tratamento de erro
2. Servidor de arquivos estático
3. Autenticação
4. MVC
ASP.NET Core 2.x
ASP.NET Core 1.x

public void Configure(IApplicationBuilder app)


{
app.UseExceptionHandler("/Home/Error"); // Call first to catch exceptions
// thrown in the following middleware.

app.UseStaticFiles(); // Return static files and end pipeline.

app.UseAuthentication(); // Authenticate before you access


// secure resources.

app.UseMvcWithDefaultRoute(); // Add MVC to the request pipeline.


}

No código acima, UseExceptionHandler é o primeiro componente de middleware adicionado ao pipeline—portanto,


ele captura qualquer exceção que ocorra em chamadas posteriores.
O middleware de arquivo estático é chamado no início do pipeline para que possa controlar as solicitações e o
curto-circuito sem passar pelos componentes restantes. O middleware de arquivo estático não fornece nenhuma
verificação de autorização. Todos os arquivos atendidos, incluindo aqueles em wwwroot, estão disponíveis
publicamente. Consulte Trabalhar com arquivos estáticos para conhecer uma abordagem para proteger arquivos
estáticos.
ASP.NET Core 2.x
ASP.NET Core 1.x
Se a solicitação não for controlada pelo middleware de arquivo estático, ela será transmitida para o middleware de
identidade ( app.UseAuthentication ), que executa a autenticação. A identidade não liga as solicitações não
autenticadas em curto-circuito. Embora a identidade autentique as solicitações, a autorização (e a rejeição) ocorre
somente depois que o MVC seleciona uma Página Razor específica ou um controlador e uma ação.
O exemplo a seguir demonstra uma solicitação de middleware na qual os pedidos de arquivos estáticos são
tratados pelo middleware de arquivo estático antes do middleware de compactação de resposta. Os arquivos
estáticos não são compactados com esse pedido de middleware. As respostas do MVC de
UseMvcWithDefaultRoute podem ser compactadas.
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles(); // Static files not compressed
// by middleware.
app.UseResponseCompression();
app.UseMvcWithDefaultRoute();
}

Use, Run e Map


Você pode configurar o pipeline de HTTP usando Use , Run e Map . O método Use pode ligar o pipeline em
curto-circuito (ou seja, se ele não chamar um delegado de solicitação next ). Run é uma convenção e alguns
componentes de middleware podem expor os métodos Run[Middleware] que são executados no final do pipeline.
As extensões Map* são usadas como uma convenção de ramificação do pipeline. Map ramifica o pipeline de
solicitação com base na correspondência do caminho da solicitação em questão. Se o caminho da solicitação iniciar
com o caminho especificado, o branch será executado.

public class Startup


{
private static void HandleMapTest1(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 1");
});
}

private static void HandleMapTest2(IApplicationBuilder app)


{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 2");
});
}

public void Configure(IApplicationBuilder app)


{
app.Map("/map1", HandleMapTest1);

app.Map("/map2", HandleMapTest2);

app.Run(async context =>


{
await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});
}
}

A tabela a seguir mostra as solicitações e as respostas de http://localhost:1234 usando o código anterior:

SOLICITAÇÃO RESPOSTA

localhost:1234 Saudação do delegado diferente de Map.

localhost:1234/map1 Teste de Map 1

localhost:1234/map2 Teste de Map 2

localhost:1234/map3 Saudação do delegado diferente de Map.


Quando Map é usado, os segmentos de caminho correspondentes são removidos do HttpRequest.Path e anexados
em HttpRequest.PathBase para cada solicitação.
MapWhen ramifica o pipeline de solicitação com base no resultado do predicado em questão. Qualquer predicado
do tipo Func<HttpContext, bool> pode ser usado para mapear as solicitações para um novo branch do pipeline. No
exemplo a seguir, um predicado é usado para detectar a presença de uma variável de cadeia de caracteres de
consulta branch :

public class Startup


{
private static void HandleBranch(IApplicationBuilder app)
{
app.Run(async context =>
{
var branchVer = context.Request.Query["branch"];
await context.Response.WriteAsync($"Branch used = {branchVer}");
});
}

public void Configure(IApplicationBuilder app)


{
app.MapWhen(context => context.Request.Query.ContainsKey("branch"),
HandleBranch);

app.Run(async context =>


{
await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});
}
}

A tabela a seguir mostra as solicitações e as respostas de http://localhost:1234 usando o código anterior:

SOLICITAÇÃO RESPOSTA

localhost:1234 Saudação do delegado diferente de Map.

localhost:1234/?branch=master Branch usado = mestre

Map é compatível com aninhamento, por exemplo:

app.Map("/level1", level1App => {


level1App.Map("/level2a", level2AApp => {
// "/level1/level2a"
//...
});
level1App.Map("/level2b", level2BApp => {
// "/level1/level2b"
//...
});
});

Map também pode ser correspondido com vários segmentos de uma vez, por exemplo:

app.Map("/level1/level2", HandleMultiSeg);

Middleware interno
O ASP.NET Core é fornecido com os componentes de middleware a seguir, bem como com uma descrição da
ordem em que eles devem ser adicionados:

MIDDLEWARE DESCRIÇÃO PEDIDO

Autenticação Fornece suporte à autenticação. Antes de HttpContext.User ser


necessário. Terminal para retornos de
chamada OAuth.

CORS Configura o Compartilhamento de Antes de componentes que usam o


Recursos entre Origens. CORS.

Diagnóstico Configura o diagnóstico. Antes dos componentes que geram


erros.

ForwardedHeaders/HttpOverrides Encaminha cabeçalhos como proxy para Antes dos componentes que consomem
a solicitação atual. os campos atualizados (exemplos:
Esquema, Host, ClientIP, Método).

Cache de resposta Fornece suporte para as respostas em Antes dos componentes que exigem
cache. armazenamento em cache.

Compactação de resposta Fornece suporte para a compactação de Antes dos componentes que exigem
respostas. compactação.

RequestLocalization Fornece suporte à localização. Antes dos componentes de localização


importantes.

Roteamento Define e restringe as rotas de Terminal de rotas correspondentes.


solicitação.

Sessão Fornece suporte para gerenciar sessões Antes de componentes que exigem a
de usuário. sessão.

Arquivos estáticos Fornece suporte para servir arquivos Terminal, se uma solicitação for
estáticos e pesquisa no diretório. correspondente aos arquivos.

Regravação de URL Fornece suporte para regravar URLs e Antes dos componentes que consomem
redirecionar solicitações. a URL.

WebSockets Habilita o protocolo WebSockets. Antes dos componentes que são


necessários para aceitar solicitações de
WebSocket.

Middleware de gravação
O middleware geralmente é encapsulado em uma classe e exposto com um método de extensão. Considere o
middleware a seguir, que define a cultura para a solicitação atual da cadeia de caracteres de consulta:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use((context, next) =>
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);

CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}

// Call the next delegate/middleware in the pipeline


return next();
});

app.Run(async (context) =>


{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
});

}
}

Observação: o código de exemplo acima é usado para demonstrar a criação de um componente de middleware.
Consulte Globalização e localização para obter suporte de localização interna do ASP.NET Core.
Você pode testar o middleware ao transmitir a cultura, por exemplo http://localhost:7997/?culture=no .
O código a seguir move o delegado de middleware para uma classe:
using Microsoft.AspNetCore.Http;
using System.Globalization;
using System.Threading.Tasks;

namespace Culture
{
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;

public RequestCultureMiddleware(RequestDelegate next)


{
_next = next;
}

public Task InvokeAsync(HttpContext context)


{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);

CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;

// Call the next delegate/middleware in the pipeline


return this._next(context);
}
}
}

OBSERVAÇÃO
No ASP.NET Core 1.x, o nome do método Task de middleware deve ser Invoke . No ASP.NET Core 2.0 ou posterior, o nome
pode ser Invoke ou InvokeAsync .

O seguinte método de extensão expõe o middleware por meio do IApplicationBuilder:

using Microsoft.AspNetCore.Builder;

namespace Culture
{
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
}

O código a seguir chama o middleware de Configure :


public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseRequestCulture();

app.Run(async (context) =>


{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
});

}
}

O middleware deve seguir o princípio de dependências explícitas ao expor suas dependências em seu construtor. O
middleware é construído uma vez por tempo de vida do aplicativo. Consulte a seção Dependências por solicitação
abaixo se você precisar compartilhar serviços com middleware dentro de uma solicitação.
Os componentes de middleware podem resolver suas dependências, utilizando a injeção de dependência por meio
de parâmetros do construtor. UseMiddleware<T> também pode aceitar parâmetros adicionais diretamente.
Dependências de pré -solicitação
Uma vez que o middleware é construído durante a inicialização do aplicativo, e não por solicitação, os serviços de
tempo de vida com escopo usados pelos construtores do middleware não são compartilhados com outros tipos de
dependência inseridos durante cada solicitação. Se você tiver que compartilhar um serviço com escopo entre seu
serviço de middleware e serviços de outros tipos, adicione esses serviços à assinatura do método Invoke . O
método Invoke pode aceitar parâmetros adicionais que são preenchidos pela injeção de dependência. Por
exemplo:

public class MyMiddleware


{
private readonly RequestDelegate _next;

public MyMiddleware(RequestDelegate next)


{
_next = next;
}

public async Task Invoke(HttpContext httpContext, IMyScopedService svc)


{
svc.MyProperty = 1000;
await _next(httpContext);
}
}

Recursos adicionais
Migrar módulos HTTP para Middleware
Inicialização de aplicativos
Recursos de solicitação
Ativação de middleware de fábrica
Ativação de middleware com um contêiner de terceiros
Middleware do ASP.NET Core
04/05/2018 • 17 min to read • Edit Online

Por Rick Anderson e Steve Smith


Exibir ou baixar código de exemplo (como baixar)

O que é um middleware?
O middleware é um software montado em um pipeline de aplicativo para manipular solicitações e
respostas. Cada componente:
Escolhe se deseja passar a solicitação para o próximo componente no pipeline.
Pode executar o trabalho antes e depois de o próximo componente no pipeline ser invocado.
Os delegados de solicitação são usados para criar o pipeline de solicitação. Os delegados de solicitação
manipulam cada solicitação HTTP.
Os delegados de solicitação são configurados usando os métodos de extensão Run, Map e Use. Um
delegado de solicitação individual pode ser especificado em linha como um método anônimo (chamado
do middleware em linha) ou pode ser definido em uma classe reutilizável. Essas classes reutilizáveis e os
métodos anônimos em linha são o middleware ou componentes do middleware. Cada componente do
middleware no pipeline de solicitação é responsável por invocar o próximo componente no pipeline ou
por ligar a cadeia em curto-circuito, se apropriado.
A seção Migrating HTTP Modules to Middleware (Migrar módulos HTTP para o Middleware) explica a
diferença entre pipelines de solicitação no ASP.NET Core e no ASP.NET 4.x e fornece mais exemplos do
middleware.

Criando um pipeline do middleware com o IApplicationBuilder


O pipeline de solicitação do ASP.NET Core consiste em uma sequência de delegados de solicitação,
chamados um após o outro, como mostrado neste diagrama (o thread de execução segue as setas
pretas):
Cada delegado pode executar operações antes e depois do próximo delegado. Um delegado também
pode optar por não transmitir uma solicitação ao próximo delegado, o que também é chamado de ligar o
pipeline de solicitação em curto-circuito. O curto-circuito geralmente é desejável porque ele evita
trabalho desnecessário. Por exemplo, o middleware de arquivo estático pode retornar uma solicitação
para um arquivo estático e ligar o restante do pipeline em curto-circuito. Os delegados de tratamento de
exceção precisam ser chamados no início do pipeline, para que possam detectar exceções que ocorrem
em etapas posteriores do pipeline.
O aplicativo ASP.NET Core mais simples possível define um delegado de solicitação única que controla
todas as solicitações. Este caso não inclui um pipeline de solicitação real. Em vez disso, uma única função
anônima é chamada em resposta a cada solicitação HTTP.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;

public class Startup


{
public void Configure(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Hello, World!");
});
}
}

O primeiro delegado app.Run encerra o pipeline.


É possível encadear vários delegados de solicitação junto com o app.Use. O parâmetro next representa
o próximo delegado no pipeline. (Lembre-se de que você pode ligar o pipeline em curto-circuito ao não
chamar o próximo parâmetro.) Normalmente, você pode executar ações antes e depois do próximo
delegado, conforme este exemplo demonstra:

public class Startup


{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
// Do work that doesn't write to the Response.
await next.Invoke();
// Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>


{
await context.Response.WriteAsync("Hello from 2nd delegate.");
});
}
}
AVISO
Não chame next.Invoke depois que a resposta tiver sido enviada ao cliente. Altera para HttpResponse depois
que a resposta foi iniciada e lançará uma exceção. Por exemplo, mudanças como a configuração de cabeçalhos, o
código de status, etc., lançarão uma exceção. Gravar no corpo da resposta após a chamada next :
Pode causar uma violação do protocolo. Por exemplo, gravar mais do que o content-length indicado.
Pode corromper o formato do corpo. Por exemplo, gravar um rodapé HTML em um arquivo CSS.
HttpResponse.HasStarted é uma dica útil para indicar se os cabeçalhos foram enviados e/ou o corpo foi gravado.

Ordenando
A ordem em que os componentes do middleware são adicionados ao método Configure define a ordem
em que eles são invocados nas solicitações e a ordem inversa para a resposta. Essa ordem é crítica para a
segurança, o desempenho e a funcionalidade.
O método Configure (mostrado abaixo) adiciona os seguintes componentes de middleware:
1. Exceção/tratamento de erro
2. Servidor de arquivos estático
3. Autenticação
4. MVC
ASP.NET Core 2.x
ASP.NET Core 1.x

public void Configure(IApplicationBuilder app)


{
app.UseExceptionHandler("/Home/Error"); // Call first to catch exceptions
// thrown in the following middleware.

app.UseStaticFiles(); // Return static files and end pipeline.

app.UseAuthentication(); // Authenticate before you access


// secure resources.

app.UseMvcWithDefaultRoute(); // Add MVC to the request pipeline.


}

No código acima, UseExceptionHandler é o primeiro componente de middleware adicionado ao pipeline


—portanto, ele captura qualquer exceção que ocorra em chamadas posteriores.
O middleware de arquivo estático é chamado no início do pipeline para que possa controlar as
solicitações e o curto-circuito sem passar pelos componentes restantes. O middleware de arquivo
estático não fornece nenhuma verificação de autorização. Todos os arquivos atendidos, incluindo
aqueles em wwwroot, estão disponíveis publicamente. Consulte Trabalhar com arquivos estáticos para
conhecer uma abordagem para proteger arquivos estáticos.
ASP.NET Core 2.x
ASP.NET Core 1.x
Se a solicitação não for controlada pelo middleware de arquivo estático, ela será transmitida para o
middleware de identidade ( app.UseAuthentication ), que executa a autenticação. A identidade não liga as
solicitações não autenticadas em curto-circuito. Embora a identidade autentique as solicitações, a
autorização (e a rejeição) ocorre somente depois que o MVC seleciona uma Página Razor específica ou
um controlador e uma ação.
O exemplo a seguir demonstra uma solicitação de middleware na qual os pedidos de arquivos estáticos
são tratados pelo middleware de arquivo estático antes do middleware de compactação de resposta. Os
arquivos estáticos não são compactados com esse pedido de middleware. As respostas do MVC de
UseMvcWithDefaultRoute podem ser compactadas.

public void Configure(IApplicationBuilder app)


{
app.UseStaticFiles(); // Static files not compressed
// by middleware.
app.UseResponseCompression();
app.UseMvcWithDefaultRoute();
}

Use, Run e Map


Você pode configurar o pipeline de HTTP usando Use , Run e Map . O método Use pode ligar o
pipeline em curto-circuito (ou seja, se ele não chamar um delegado de solicitação next ). Run é uma
convenção e alguns componentes de middleware podem expor os métodos Run[Middleware] que são
executados no final do pipeline.
As extensões Map* são usadas como uma convenção de ramificação do pipeline. Map ramifica o
pipeline de solicitação com base na correspondência do caminho da solicitação em questão. Se o
caminho da solicitação iniciar com o caminho especificado, o branch será executado.

public class Startup


{
private static void HandleMapTest1(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 1");
});
}

private static void HandleMapTest2(IApplicationBuilder app)


{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 2");
});
}

public void Configure(IApplicationBuilder app)


{
app.Map("/map1", HandleMapTest1);

app.Map("/map2", HandleMapTest2);

app.Run(async context =>


{
await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});
}
}

A tabela a seguir mostra as solicitações e as respostas de http://localhost:1234 usando o código


anterior:
SOLICITAÇÃO RESPOSTA

localhost:1234 Saudação do delegado diferente de Map.

localhost:1234/map1 Teste de Map 1

localhost:1234/map2 Teste de Map 2

localhost:1234/map3 Saudação do delegado diferente de Map.

Quando Map é usado, os segmentos de caminho correspondentes são removidos do HttpRequest.Path


e anexados em HttpRequest.PathBase para cada solicitação.
MapWhen ramifica o pipeline de solicitação com base no resultado do predicado em questão. Qualquer
predicado do tipo Func<HttpContext, bool> pode ser usado para mapear as solicitações para um novo
branch do pipeline. No exemplo a seguir, um predicado é usado para detectar a presença de uma variável
de cadeia de caracteres de consulta branch :

public class Startup


{
private static void HandleBranch(IApplicationBuilder app)
{
app.Run(async context =>
{
var branchVer = context.Request.Query["branch"];
await context.Response.WriteAsync($"Branch used = {branchVer}");
});
}

public void Configure(IApplicationBuilder app)


{
app.MapWhen(context => context.Request.Query.ContainsKey("branch"),
HandleBranch);

app.Run(async context =>


{
await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});
}
}

A tabela a seguir mostra as solicitações e as respostas de http://localhost:1234 usando o código


anterior:

SOLICITAÇÃO RESPOSTA

localhost:1234 Saudação do delegado diferente de Map.

localhost:1234/?branch=master Branch usado = mestre

Map é compatível com aninhamento, por exemplo:


app.Map("/level1", level1App => {
level1App.Map("/level2a", level2AApp => {
// "/level1/level2a"
//...
});
level1App.Map("/level2b", level2BApp => {
// "/level1/level2b"
//...
});
});

Map também pode ser correspondido com vários segmentos de uma vez, por exemplo:

app.Map("/level1/level2", HandleMultiSeg);

Middleware interno
O ASP.NET Core é fornecido com os componentes de middleware a seguir, bem como com uma
descrição da ordem em que eles devem ser adicionados:

MIDDLEWARE DESCRIÇÃO PEDIDO

Autenticação Fornece suporte à autenticação. Antes de HttpContext.User ser


necessário. Terminal para retornos
de chamada OAuth.

CORS Configura o Compartilhamento de Antes de componentes que usam o


Recursos entre Origens. CORS.

Diagnóstico Configura o diagnóstico. Antes dos componentes que geram


erros.

ForwardedHeaders/HttpOverrides Encaminha cabeçalhos como proxy Antes dos componentes que


para a solicitação atual. consomem os campos atualizados
(exemplos: Esquema, Host, ClientIP,
Método).

Cache de resposta Fornece suporte para as respostas Antes dos componentes que exigem
em cache. armazenamento em cache.

Compactação de resposta Fornece suporte para a Antes dos componentes que exigem
compactação de respostas. compactação.

RequestLocalization Fornece suporte à localização. Antes dos componentes de


localização importantes.

Roteamento Define e restringe as rotas de Terminal de rotas correspondentes.


solicitação.

Sessão Fornece suporte para gerenciar Antes de componentes que exigem


sessões de usuário. a sessão.

Arquivos estáticos Fornece suporte para servir Terminal, se uma solicitação for
arquivos estáticos e pesquisa no correspondente aos arquivos.
diretório.
MIDDLEWARE DESCRIÇÃO PEDIDO

Regravação de URL Fornece suporte para regravar URLs Antes dos componentes que
e redirecionar solicitações. consomem a URL.

WebSockets Habilita o protocolo WebSockets. Antes dos componentes que são


necessários para aceitar solicitações
de WebSocket.

Middleware de gravação
O middleware geralmente é encapsulado em uma classe e exposto com um método de extensão.
Considere o middleware a seguir, que define a cultura para a solicitação atual da cadeia de caracteres de
consulta:

public class Startup


{
public void Configure(IApplicationBuilder app)
{
app.Use((context, next) =>
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);

CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}

// Call the next delegate/middleware in the pipeline


return next();
});

app.Run(async (context) =>


{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
});

}
}

Observação: o código de exemplo acima é usado para demonstrar a criação de um componente de


middleware. Consulte Globalização e localização para obter suporte de localização interna do ASP.NET
Core.
Você pode testar o middleware ao transmitir a cultura, por exemplo http://localhost:7997/?culture=no .
O código a seguir move o delegado de middleware para uma classe:
using Microsoft.AspNetCore.Http;
using System.Globalization;
using System.Threading.Tasks;

namespace Culture
{
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;

public RequestCultureMiddleware(RequestDelegate next)


{
_next = next;
}

public Task InvokeAsync(HttpContext context)


{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);

CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;

// Call the next delegate/middleware in the pipeline


return this._next(context);
}
}
}

OBSERVAÇÃO
No ASP.NET Core 1.x, o nome do método Task de middleware deve ser Invoke . No ASP.NET Core 2.0 ou
posterior, o nome pode ser Invoke ou InvokeAsync .

O seguinte método de extensão expõe o middleware por meio do IApplicationBuilder:

using Microsoft.AspNetCore.Builder;

namespace Culture
{
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
}

O código a seguir chama o middleware de Configure :


public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseRequestCulture();

app.Run(async (context) =>


{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
});

}
}

O middleware deve seguir o princípio de dependências explícitas ao expor suas dependências em seu
construtor. O middleware é construído uma vez por tempo de vida do aplicativo. Consulte a seção
Dependências por solicitação abaixo se você precisar compartilhar serviços com middleware dentro de
uma solicitação.
Os componentes de middleware podem resolver suas dependências, utilizando a injeção de dependência
por meio de parâmetros do construtor. UseMiddleware<T> também pode aceitar parâmetros adicionais
diretamente.
Dependências de pré -solicitação
Uma vez que o middleware é construído durante a inicialização do aplicativo, e não por solicitação, os
serviços de tempo de vida com escopo usados pelos construtores do middleware não são
compartilhados com outros tipos de dependência inseridos durante cada solicitação. Se você tiver que
compartilhar um serviço com escopo entre seu serviço de middleware e serviços de outros tipos,
adicione esses serviços à assinatura do método Invoke . O método Invoke pode aceitar parâmetros
adicionais que são preenchidos pela injeção de dependência. Por exemplo:

public class MyMiddleware


{
private readonly RequestDelegate _next;

public MyMiddleware(RequestDelegate next)


{
_next = next;
}

public async Task Invoke(HttpContext httpContext, IMyScopedService svc)


{
svc.MyProperty = 1000;
await _next(httpContext);
}
}

Recursos adicionais
Migrar módulos HTTP para Middleware
Inicialização de aplicativos
Recursos de solicitação
Ativação de middleware de fábrica
Ativação de middleware com um contêiner de terceiros
Ativação de middleware baseada em alocador no
ASP.NET Core
14/02/2018 • 3 min to read • Edit Online

Por Luke Latham


IMiddlewareFactory/IMiddleware é um ponto de extensibilidade para a ativação de middleware.
Os métodos de extensão UseMiddleware verificam se o tipo registrado de um middleware implementa
IMiddleware . Se isso acontecer, a instância IMiddlewareFactory registrada no contêiner será usada para resolver a
implementação IMiddleware em vez de usar a lógica de ativação de middleware baseada em convenção. O
middleware é registrado como um serviço com escopo ou transitório no contêiner de serviço do aplicativo.
Benefícios:
Ativação por solicitação (injeção de serviços com escopo)
Tipagem forte de middleware
IMiddlewareé ativado por solicitação, de modo que os serviços com escopo possam ser injetados no construtor
do middleware.
Exibir ou baixar código de exemplo (como baixar)
O aplicativo de exemplo demonstra o middleware ativado por:
Convenção ( ConventionalMiddleware ). Para obter mais informações sobre a ativação de middleware
convencional, consulte o tópico Middleware.
Uma implementação de IMiddlewareFactory ( IMiddlewareMiddleware ). A classe MiddlewareFactory padrão
ativa o middleware.
As implementações de middleware funcionam de forma idêntica e registram o valor fornecido por um parâmetro
de cadeia de caracteres de consulta ( key ). Os middlewares usam um contexto de banco de dados injetado (um
serviço com escopo) para registrar o valor de cadeia de caracteres de consulta em um banco de dados em
memória.

IMiddleware
IMiddleware define o middleware para o pipeline de solicitação do aplicativo. O método
InvokeAsync(HttpContext, RequestDelegate) manipula as solicitações e retorna uma Task que representa a
execução do middleware.
Middleware ativado por convenção:
public class ConventionalMiddleware
{
private readonly RequestDelegate _next;

public ConventionalMiddleware(RequestDelegate next)


{
_next = next;
}

public async Task InvokeAsync(HttpContext context, AppDbContext db)


{
var keyValue = context.Request.Query["key"];

if (!string.IsNullOrWhiteSpace(keyValue))
{
db.Add(new Request()
{
DT = DateTime.UtcNow,
MiddlewareActivation = "ConventionalMiddleware",
Value = keyValue
});

await db.SaveChangesAsync();
}

await _next(context);
}
}

Middleware ativado por MiddlewareFactory :

public class IMiddlewareMiddleware : IMiddleware


{
private readonly AppDbContext _db;

public IMiddlewareMiddleware(AppDbContext db)


{
_db = db;
}

public async Task InvokeAsync(HttpContext context, RequestDelegate next)


{
var keyValue = context.Request.Query["key"];

if (!string.IsNullOrWhiteSpace(keyValue))
{
_db.Add(new Request()
{
DT = DateTime.UtcNow,
MiddlewareActivation = "IMiddlewareMiddleware",
Value = keyValue
});

await _db.SaveChangesAsync();
}

await next(context);
}
}

As extensões são criadas para os middlewares:


public static class MiddlewareExtensions
{
public static IApplicationBuilder UseConventionalMiddleware(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<ConventionalMiddleware>();
}

public static IApplicationBuilder UseIMiddlewareMiddleware(


this IApplicationBuilder builder)
{
return builder.UseMiddleware<IMiddlewareMiddleware>();
}
}

Não é possível passar objetos para o middleware ativado por alocador com UseMiddleware :

public static IApplicationBuilder UseIMiddlewareMiddleware(


this IApplicationBuilder builder, bool option)
{
// Passing 'option' as an argument throws a NotSupportedException at runtime.
return builder.UseMiddleware<IMiddlewareMiddleware>(option);
}

O middleware ativado por alocador é adicionado ao contêiner interno em Startup.cs:

public void ConfigureServices(IServiceCollection services)


{
services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("InMemoryDb"));

services.AddTransient<IMiddlewareMiddleware>();

services.AddMvc();
}

Ambos os middlewares estão registrados no pipeline de processamento de solicitação em Configure :

public void Configure(IApplicationBuilder app, IHostingEnvironment env)


{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}

app.UseConventionalMiddleware();
app.UseIMiddlewareMiddleware();

app.UseStaticFiles();
app.UseMvc();
}

IMiddlewareFactory
IMiddlewareFactory fornece métodos para a criação do middleware. A implementação de alocador do middleware
é registrada no contêiner como um serviço com escopo.
A implementação IMiddlewareFactory padrão, MiddlewareFactory, foi encontrada no pacote
Microsoft.AspNetCore.Http (fonte de referência).

Recursos adicionais
Middleware
Trabalhar com arquivos estáticos no ASP.NET Core
14/02/2018 • 14 min to read • Edit Online

Por Rick Anderson e Scott Addie


Arquivos estáticos, como HTML, CSS, imagens e JavaScript, são ativos que um aplicativo ASP.NET Core
fornece diretamente para os clientes. Algumas etapas de configuração são necessárias para habilitar o
fornecimento desses arquivos.
Exibir ou baixar código de exemplo (como baixar)

Fornecer arquivos estáticos


Os arquivos estáticos são armazenados no diretório raiz Web do projeto. O diretório padrão é
<content_root>/wwwroot, mas pode ser alterado por meio do método UseWebRoot. Consulte Raiz de
conteúdo e Diretório base para obter mais informações.
O host Web do aplicativo deve ser informado do diretório raiz do conteúdo.
ASP.NET Core 2.x
ASP.NET Core 1.x
O método WebHost.CreateDefaultBuilder define a raiz do conteúdo como o diretório atual:

public class Program


{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}

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


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

Os arquivos estáticos são acessíveis por meio de um caminho relativo ao diretório base. Por exemplo, o
modelo de projeto do Aplicativo Web contém várias pastas dentro da pasta wwwroot:
wwwroot
css
images
js
O formato de URI para acessar um arquivo na subpasta images é
http://<server_address>/images/<image_file_name>. Por exemplo,
http://localhost:9189/images/banner3.svg.
ASP.NET Core 2.x
ASP.NET Core 1.x
Se estiver direcionando o .NET Framework, adicione o pacote Microsoft.AspNetCore.StaticFiles ao projeto.
Se estiver direcionando o .NET Core, o metapacote Microsoft.AspNetCore.All incluirá esse pacote.
Configure o middleware que permite o fornecimento de arquivos estáticos.
Fornecer arquivos dentro do diretório base
Invoque o método UseStaticFiles dentro de Startup.Configure :

public void Configure(IApplicationBuilder app)


{
app.UseStaticFiles();
}

A sobrecarga do método UseStaticFiles sem parâmetro marca os arquivos no diretório base como
fornecíveis. A seguinte marcação referencia wwwroot/images/banner1.svg:

<img src="~/images/banner1.svg" alt="ASP.NET" class="img-responsive" />

Fornecer arquivos fora do diretório base


Considere uma hierarquia de diretórios na qual os arquivos estáticos a serem atendidos residam fora do
diretório base:
wwwroot
css
images
js
MyStaticFiles
images
banner1.svg
Uma solicitação pode acessar o arquivo banner1.svg configurando o middleware de arquivo estático da
seguinte maneira:

public void Configure(IApplicationBuilder app)


{
app.UseStaticFiles(); // For the wwwroot folder

app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "MyStaticFiles")),
RequestPath = "/StaticFiles"
});
}

No código anterior, a hierarquia de diretórios MyStaticFiles é exposta publicamente por meio do segmento do
URI StaticFiles. Uma solicitação para http://<server_address>/StaticFiles/images/banner1.svg atende ao
arquivo banner1.svg.
A seguinte marcação referencia MyStaticFiles/images/banner1.svg:

<img src="~/StaticFiles/images/banner1.svg" alt="ASP.NET" class="img-responsive" />

Definir cabeçalhos de resposta HTTP


Um objeto StaticFileOptions pode ser usado para definir cabeçalhos de resposta HTTP. Além de configurar o
fornecimento de arquivos estáticos do diretório base, o seguinte código define o cabeçalho Cache-Control :
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
// Requires the following import:
// using Microsoft.AspNetCore.Http;
ctx.Context.Response.Headers.Append("Cache-Control", "public,max-age=600");
}
});
}

O método HeaderDictionaryExtensions.Append existe no pacote Microsoft.AspNetCore.Http.


Os arquivos se tornaram armazenáveis em cache publicamente por 10 minutos (600 segundos):

Autorização de arquivo estático


O middleware de arquivo estático não fornece verificações de autorização. Todos os arquivos atendidos,
incluindo aqueles em wwwroot, estão acessíveis publicamente. Para fornecer arquivos com base na
autorização:
Armazene-os fora do wwwroot e de qualquer diretório acessível ao middleware de arquivos estáticos e
Forneça-os por meio de um método de ação ao qual a autorização é aplicada. Retorne um objeto
FileResult:

[Authorize]
public IActionResult BannerImage()
{
var file = Path.Combine(Directory.GetCurrentDirectory(),
"MyStaticFiles", "images", "banner1.svg");

return PhysicalFile(file, "image/svg+xml");


}

Habilitar navegação no diretório


A navegação no diretório permite que os usuários do aplicativo Web vejam uma listagem de diretório e
arquivos em um diretório especificado. A navegação no diretório está desabilitada por padrão por motivos de
segurança (consulte Considerações). Habilite a navegação no diretório invocando o método
UseDirectoryBrowser em Startup.Configure :
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles(); // For the wwwroot folder

app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images")),
RequestPath = "/MyImages"
});

app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images")),
RequestPath = "/MyImages"
});
}

Adicione os serviços necessários invocando o método AddDirectoryBrowser em Startup.ConfigureServices :

public void ConfigureServices(IServiceCollection services)


{
services.AddDirectoryBrowser();
}

O código anterior permite a navegação no diretório da pasta wwwroot/images usando a URL


http://<server_address>/MyImages, com links para cada arquivo e pasta:

Consulte Considerações para saber mais sobre os riscos de segurança ao habilitar a navegação.
Observe as duas chamadas UseStaticFiles no exemplo a seguir. A primeira chamada permite o
fornecimento de arquivos estáticos na pasta wwwroot. A segunda chamada habilita a navegação no diretório
da pasta wwwroot/images usando a URL http://<server_address>/MyImages:
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles(); // For the wwwroot folder

app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images")),
RequestPath = "/MyImages"
});

app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images")),
RequestPath = "/MyImages"
});
}

Fornecer um documento padrão


Definir uma home page padrão fornece ao visitantes um ponto de partida lógico de ao visitar o site. Para
fornecer uma página padrão sem qualificar totalmente o URI do usuário, chame o UseDefaultFiles método de
Startup.Configure :

public void Configure(IApplicationBuilder app)


{
app.UseDefaultFiles();
app.UseStaticFiles();
}

IMPORTANTE
UseDefaultFiles deve ser chamado antes de UseStaticFiles para fornecer o arquivo padrão. UseDefaultFiles
é um rewriter de URL que, na verdade, não fornece o arquivo. Habilite o middleware de arquivo estático por meio de
UseStaticFiles para fornecer o arquivo.

Com UseDefaultFiles , as solicitações para uma pasta pesquisam:


default.htm
default.html
index.htm
index.html
O primeiro arquivo encontrado na lista é fornecido como se a solicitação fosse o URI totalmente qualificado.
A URL do navegador continua refletindo o URI solicitado.
O seguinte código altera o nome de arquivo padrão para mydefault.html:
public void Configure(IApplicationBuilder app)
{
// Serve my app-specific default file, if present.
DefaultFilesOptions options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("mydefault.html");
app.UseDefaultFiles(options);
app.UseStaticFiles();
}

UseFileServer
UseFileServer combina a funcionalidade de UseStaticFiles , UseDefaultFiles e UseDirectoryBrowser .
O código a seguir permite o fornecimento de arquivos estáticos e do arquivo padrão. A navegação no
diretório não está habilitada.

app.UseFileServer();

O seguinte código baseia-se na sobrecarga sem parâmetros, habilitando a navegação no diretório:

app.UseFileServer(enableDirectoryBrowsing: true);

Considere a seguinte hierarquia de diretórios:


wwwroot
css
images
js
MyStaticFiles
images
banner1.svg
default.html
O seguinte código habilita arquivos estáticos, arquivos padrão e a navegação no diretório de MyStaticFiles :

public void Configure(IApplicationBuilder app)


{
app.UseStaticFiles(); // For the wwwroot folder

app.UseFileServer(new FileServerOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "MyStaticFiles")),
RequestPath = "/StaticFiles",
EnableDirectoryBrowsing = true
});
}

AddDirectoryBrowser precisa ser chamado quando o valor da propriedade EnableDirectoryBrowsing é true :


public void ConfigureServices(IServiceCollection services)
{
services.AddDirectoryBrowser();
}

Com o uso da hierarquia de arquivos e do código anterior, as URLs são resolvidas da seguinte maneira:

URI RESPOSTA

http://<server_address>/StaticFiles/images/banner1.svg MyStaticFiles/images/banner1.svg

http://<server_address>/StaticFiles MyStaticFiles/default.html

Se nenhum arquivo nomeado como padrão existir no diretório MyStaticFiles,


http://<server_address>/StaticFiles retornará a listagem de diretórios com links clicáveis:

OBSERVAÇÃO
UseDefaultFiles e UseDirectoryBrowser usam a URL http://<server_address>/StaticFiles sem a barra "" à direita
para disparar um redirecionamento do lado do cliente para http://<server_address>/StaticFiles/. Observe a adição da
barra "" à direita. URLs relativas dentro dos documentos são consideradas inválidas sem uma barra "" à direita.

FileExtensionContentTypeProvider
A classe FileExtensionContentTypeProvider contém uma propriedade Mappings que serve como um
mapeamento de extensões de arquivo para tipos de conteúdo MIME. Na amostra a seguir, várias extensões
de arquivo são registradas para tipos MIME conhecidos. A extensão .rtf é substituída, e .mp4 é removida.
public void Configure(IApplicationBuilder app)
{
// Set up custom content types - associating file extension to MIME type
var provider = new FileExtensionContentTypeProvider();
// Add new mappings
provider.Mappings[".myapp"] = "application/x-msdownload";
provider.Mappings[".htm3"] = "text/html";
provider.Mappings[".image"] = "image/png";
// Replace an existing mapping
provider.Mappings[".rtf"] = "application/x-msdownload";
// Remove MP4 videos.
provider.Mappings.Remove(".mp4");

app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images")),
RequestPath = "/MyImages",
ContentTypeProvider = provider
});

app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images")),
RequestPath = "/MyImages"
});
}

Consulte Tipos de conteúdo MIME.

Tipos de conteúdo não padrão


O middleware de arquivo estático compreende quase 400 tipos de conteúdo de arquivo conhecidos. Se o
usuário solicita um arquivo de um tipo de arquivo desconhecido, o middleware de arquivo estático retorna
uma resposta HTTP 404 (Não Encontrado). Se a navegação no diretório estiver habilitada, um link para o
arquivo será exibido. O URI retorna um erro HTTP 404.
O seguinte código habilita o fornecimento de tipos desconhecidos e renderiza o arquivo desconhecido como
uma imagem:

public void Configure(IApplicationBuilder app)


{
app.UseStaticFiles(new StaticFileOptions
{
ServeUnknownFileTypes = true,
DefaultContentType = "image/png"
});
}

Com o código anterior, uma solicitação para um arquivo com um tipo de conteúdo desconhecido é retornada
como uma imagem.

AVISO
A habilitação de ServeUnknownFileTypes é um risco de segurança. Ela está desabilitada por padrão, e seu uso não é
recomendado. FileExtensionContentTypeProvider fornece uma alternativa mais segura para o fornecimento de arquivos
com extensões não padrão.

Considerações
AVISO
UseDirectoryBrowser e UseStaticFiles podem causar a perda de segredos. A desabilitação da navegação no
diretório em produção é altamente recomendada. Examine com atenção os diretórios que são habilitados por meio de
UseStaticFiles ou UseDirectoryBrowser . Todo o diretório e seus subdiretórios se tornam publicamente acessíveis.
Armazene arquivos adequados para fornecimento ao público em um diretório dedicado, como
<content_root>/wwwroot. Separe esses arquivos das exibições MVC, Páginas do Razor (somente 2.x), arquivos de
configuração, etc.

As URLs para o conteúdo exposto com UseDirectoryBrowser e UseStaticFiles estão sujeitas à


diferenciação de maiúsculas e minúsculas e a restrições de caracteres do sistema de arquivos
subjacente. Por exemplo, o Windows não diferencia maiúsculas de minúsculas, ao contrário do Mac e
do Linux.
Os aplicativos ASP.NET Core hospedados no IIS usam o ANCM (Módulo do ASP.NET Core) para
encaminhar todas as solicitações para o aplicativo, incluindo solicitações de arquivo estático. O
manipulador de arquivos estáticos do IIS não é usado. Não tem nenhuma possibilidade de manipular
solicitações antes que elas sejam manipuladas pelo ANCM.
Conclua as etapas seguinte no Gerenciador do IIS para remover o manipulador de arquivos estáticos
no IIS no nível do servidor ou do site:
1. Navegue para o recurso Módulos.
2. Selecione StaticFileModule na lista.
3. Clique em Remover na barra lateral Ações.

AVISO
Se o manipulador de arquivos estáticos no IIS estiver habilitado e o ANCM estiver configurado incorretamente,
arquivos estáticos serão fornecidos. Isso acontece, por exemplo, se o arquivo web.config não foi implantado.

Coloque arquivos de código (incluindo .cs e .cshtml) fora do diretório base do projeto do aplicativo.
Portanto, uma separação lógica é criada entre o conteúdo do lado do cliente do aplicativo e o código
baseado em servidor. Isso impede a perda de código do lado do servidor.

Recursos adicionais
Middleware
Introdução ao ASP.NET Core
Roteamento no ASP.NET Core
12/02/2018 • 39 min to read • Edit Online

Por Ryan Nowak, Steve Smith e Rick Anderson


A funcionalidade de roteamento é responsável por mapear uma solicitação de entrada para um manipulador
de rotas. As rotas são definidas no aplicativo ASP.NET e configuradas quando o aplicativo é iniciado. Uma
rota pode opcionalmente extrair os valores da URL contida na solicitação e esses valores podem então ser
usados para o processamento da solicitação. Usando as informações de rota do aplicativo ASP.NET, a
funcionalidade de roteamento também pode gerar URLs que são mapeadas para manipuladores de rotas.
Portanto, o roteamento pode encontrar um manipulador de rotas com base em uma URL ou a URL
correspondente a um manipulador de rotas especificado com base nas informações do manipulador de rotas.

IMPORTANTE
Este documento abrange o roteamento de nível inferior do ASP.NET Core. Para o roteamento do ASP.NET Core MVC,
consulte Roteamento para ações do controlador

Exibir ou baixar código de exemplo (como baixar)

Conceitos básicos sobre roteamento


O roteamento usa rotas (implementações de IRouter) para:
mapear solicitações de entrada para manipuladores de rotas
gerar as URLs usadas nas respostas
Em geral, um aplicativo tem uma única coleção de rotas. Quando uma solicitação é recebida, a coleção de
rotas é processada na ordem. A solicitação de entrada procura uma rota que corresponde à URL de
solicitação chamando o método RouteAsync em cada rota disponível na coleção de rotas. Por outro lado, uma
resposta pode usar o roteamento para gerar URLs (por exemplo, para redirecionamento ou links) com base
nas informações de rotas e evitar a necessidade de embutir as URLs em código, o que ajuda a facilidade de
manutenção.
O roteamento está conectado ao pipeline do middleware pela classe RouterMiddleware . O ASP.NET Core
MVC adiciona o roteamento ao pipeline do middleware como parte de sua configuração. Para saber mais
sobre como usar o roteamento como um componente autônomo, confira Usando o middleware de
roteamento.
Correspondência de URL
Correspondência de URL é o processo pelo qual o roteamento expede uma solicitação de entrada para um
manipulador. Esse processo geralmente se baseia nos dados do caminho da URL, mas pode ser estendido
para considerar outros dados na solicitação. A capacidade de expedir solicitações para manipuladores
separados é fundamental no dimensionamento do tamanho e da complexidade de um aplicativo.
As solicitações de entrada entram no RouterMiddleware , que chama o método RouteAsync em cada rota na
sequência. A instância IRouter escolhe se deseja manipular a solicitação definindo o RouteContext.Handler
como RequestDelegate não nulo. Se uma rota definir um manipulador para a solicitação, o processamento de
rotas será interrompido e o manipulador será invocado para processar a solicitação. Se todas as rotas forem
testadas e nenhum manipulador for encontrado para a solicitação, o middleware chamará o próximo e o
próximo middleware no pipeline da solicitação será invocado.
A entrada primária para RouteAsync éo RouteContext.HttpContext associado à solicitação atual. O
RouteContext.Handler e RouteContext.RouteData são saídas que serão definidas após a correspondência de
uma rota.
Uma correspondência durante RouteAsync também definirá as propriedades dos RouteContext.RouteData
com os valores apropriados de acordo com o processamento da solicitação concluído até o momento. Se
uma rota corresponder a uma solicitação, os RouteContext.RouteData conterão informações de estado
importantes sobre o resultado.
RouteData.Values é um dicionário de valores de rota produzidos com base na rota. Esses valores geralmente
são determinados pela criação de tokens da URL e podem ser usados para aceitar a entrada do usuário ou
para tomar outras decisões de expedição dentro do aplicativo.
RouteData.DataTokens é um recipiente de propriedades de dados adicionais relacionados à rota que teve uma
correspondência. DataTokens são fornecidos para dar suporte à associação dos dados de estado a cada rota,
de modo que o aplicativo possa tomar decisões posteriormente com base em qual rota teve uma
correspondência. Esses valores são definidos pelo desenvolvedor e não afetam de forma alguma o
comportamento do roteamento. Além disso, os valores armazenados em stash nos tokens de dados podem
ser de qualquer tipo, ao contrário dos valores de rota, quem devem ser conversíveis com facilidade
bidirecionalmente em cadeias de caracteres.
RouteData.Routers é uma lista das rotas que participaram da correspondência bem-sucedida da solicitação.
As rotas podem ser aninhadas dentro uma da outra e a propriedade Routers reflete o caminho pela árvore
lógica de rotas que resultou em uma correspondência. Geralmente, o primeiro item em Routers é a coleção
de rotas e deve ser usado para a geração de URL. O último item em Routers é o manipulador de rotas que
teve uma correspondência.
Geração de URL
Geração de URL é o processo pelo qual o roteamento pode criar um caminho de URL de acordo com um
conjunto de valores de rota. Isso permite uma separação lógica entre os manipuladores e as URLs que os
acessam.
A geração de URL segue um processo iterativo semelhante, mas começa com a chamada pelo código de
estrutura ou usuário ao método GetVirtualPath da coleção de rotas. Em seguida, cada rota terá seu método
GetVirtualPath chamado na sequência, até que um VirtualPathData não nulo seja retornado.

As entradas primárias para GetVirtualPath são:


VirtualPathContext.HttpContext

VirtualPathContext.Values

VirtualPathContext.AmbientValues

As rotas usam principalmente os valores de rota fornecidos pelo Values e AmbientValues para decidir em
que local é possível gerar uma URL e quais valores serão incluídos. Os AmbientValues são o conjunto de
valores de rota produzidos pela correspondência da solicitação atual com o sistema de roteamento. Por outro
lado, Values são os valores de rota que especificam como gerar a URL desejada para a operação atual. O
HttpContext é fornecido no caso de uma rota precisar obter serviços ou dados adicionais associados ao
contexto atual.
Dica: considere Values como sendo um conjunto de substituições para os AmbientValues . A geração de URL
tenta reutilizar os valores de rota da solicitação atual para facilitar a geração de URLs para links usando a
mesma rota ou valores de rota.
A saída de GetVirtualPath é um VirtualPathData . VirtualPathData é um paralelo do RouteData ; ele contém
o VirtualPath para a URL de saída, bem como algumas propriedades adicionais que devem ser definidas
pela rota.
A propriedade VirtualPathData.VirtualPath contém o caminho virtual produzido pela rota. Dependendo de
suas necessidades, talvez você precise processar ainda mais o caminho. Por exemplo, se você deseja
renderizar a URL gerada em HTML, precisa preceder o caminho base do aplicativo.
O VirtualPathData.Router é uma referência à rota que é gerou a URL com êxito.
As propriedades VirtualPathData.DataTokens são um dicionário de dados adicionais relacionados à rota que
gerou a URL. Isso é o paralelo de RouteData.DataTokens .
Criando rotas
O roteamento fornece a classe Route como a implementação padrão de IRouter . Route usa a sintaxe de
modelo de rota para definir padrões que corresponderão ao caminho de URL quando RouteAsync for
chamado. Route usará o mesmo modelo de rota para gerar uma URL quando GetVirtualPath for chamado.
A maioria dos aplicativos criará rotas chamando MapRoute ou um dos métodos de extensão semelhante
definidos em IRouteBuilder . Todos esses métodos criarão uma instância de Route e a adicionarão à coleção
de rotas.
Observação: MapRoute não usa um parâmetro de manipulador de rotas – ele apenas adiciona rotas que serão
manipuladas pelo DefaultHandler . Como o manipulador padrão é um IRouter , ele pode optar por não
manipular a solicitação. Por exemplo, o ASP.NET MVC normalmente é configurado como um manipulador
padrão que só manipula as solicitações que correspondem a um controlador e uma ação disponíveis. Para
saber mais sobre o roteamento para o MVC, consulte Roteamento para ações do controlador.
Este é um exemplo de uma chamada MapRoute usada por uma definição de rota típica do ASP.NET MVC:

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

Esse modelo corresponderá a um caminho de URL como /Products/Details/17 e extrairá os valores de rota
{ controller = Products, action = Details, id = 17 } . Os valores de rota são determinados pela divisão do
caminho de URL em segmentos e pela correspondência de cada segmento com o nome do parâmetro de
rota do modelo de rota. Os parâmetros de rota são nomeados. Eles são definidos com a colocação do nome
do parâmetro em chaves { } .
O modelo acima também pode corresponder ao caminho de URL / e produzirá valores
{ controller = Home, action = Index } . Isso ocorre porque os parâmetros de rota {controller} e {action}
têm valores padrão e o parâmetro de rota id é opcional. Um sinal de igual = seguido de um valor após o
nome do parâmetro de rota define um valor padrão para o parâmetro. Um ponto de interrogação ? após o
nome do parâmetro de rota define o parâmetro como opcional. Parâmetros de rota com um valor padrão
sempre produzem um valor de rota quando a rota encontra uma correspondência – os parâmetros opcionais
não produzem um valor de rota se não há nenhum segmento de caminho de URL correspondente.
Consulte route-template-reference para obter uma descrição completa dos recursos e da sintaxe de modelo
de rota.
Este exemplo inclui uma restrição de rota:
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id:int}");

Este modelo corresponderá a um caminho de URL como /Products/Details/17 , mas não a


/Products/Details/Apples . A definição do parâmetro de rota {id:int} define uma restrição de rota para o
parâmetro de rota id . As restrições de rota implementam IRouteConstraint e inspecionam valores de rota
para verificá-los. Neste exemplo, o valor de rota id precisa ser conversível em um inteiro. Consulte route-
constraint-reference para obter uma explicação mais detalhada das restrições de rota que são fornecidas pela
estrutura.
Sobrecargas adicionais de MapRoute aceitam valores para constraints , dataTokens e defaults . Esses
parâmetros adicionais de MapRoute são definidos como o tipo object . O uso típico desses parâmetros é
passar um objeto de tipo anônimo, no qual os nomes da propriedade do tipo anônimo correspondem aos
nomes do parâmetro de rota.
Os dois seguintes exemplos criam rotas equivalentes:

routes.MapRoute(
name: "default_route",
template: "{controller}/{action}/{id?}",
defaults: new { controller = "Home", action = "Index" });

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

Dica: a sintaxe embutida para definição de restrições e padrões pode ser mais conveniente para rotas simples.
No entanto, existem recursos, como tokens de dados, que não têm suporte na sintaxe embutida.
Este exemplo demonstra alguns outros recursos:

routes.MapRoute(
name: "blog",
template: "Blog/{*article}",
defaults: new { controller = "Blog", action = "ReadArticle" });

Esse modelo corresponderá a um caminho de URL como /Blog/All-About-Routing/Introduction e extrairá os


valores { controller = Blog, action = ReadArticle, article = All-About-Routing/Introduction } . Os valores
de rota padrão para controller e action são produzidos pela rota, mesmo que não haja nenhum
parâmetro de rota correspondente no modelo. Os valores padrão podem ser especificados no modelo de
rota. O parâmetro de rota article é definido como um catch-all pela aparência de um asterisco * antes do
nome do parâmetro de rota. Os parâmetros de rota catch-all capturam o restante do caminho de URL e
também podem corresponder à cadeia de caracteres vazia.
Este exemplo adiciona restrições de rota e tokens de dados:

routes.MapRoute(
name: "us_english_products",
template: "en-US/Products/{id}",
defaults: new { controller = "Products", action = "Details" },
constraints: new { id = new IntRouteConstraint() },
dataTokens: new { locale = "en-US" });

Esse modelo corresponderá a um caminho de URL como /Products/5 e extrairá os valores


{ controller = Products, action = Details, id = 5 } e os tokens de dados { locale = en-US } .

Geração de URL
A classe Route também pode executar a geração de URL pela combinação de um conjunto de valores de
rota com seu modelo de rota. Logicamente, este é o processo inverso de correspondência do caminho de
URL.
Dica: para entender melhor a geração de URL, imagine qual URL você deseja gerar e, em seguida, considere
como um modelo de rota corresponderá a essa URL. Quais valores serão produzidos? Este é o equivalente
aproximado de como funciona a geração de URL na classe Route .
Este exemplo usa uma rota de estilo básica do ASP.NET MVC:

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

Com os valores de rota { controller = Products, action = List } , essa rota gerará a URL /Products/List .
Os valores de rota são substituídos pelos parâmetros de rota correspondentes para formar o caminho de
URL. Como id é um parâmetro de rota opcional, não há problema algum se ele não tem um valor.
Com os valores de rota { controller = Home, action = Index } , essa rota gerará a URL / . Os valores de
rota que foram fornecidos correspondem aos valores padrão, de modo que os segmentos correspondentes a
esses valores possam ser omitidos com segurança. Observe que as duas URLs geradas farão uma ida e vinda
com essa definição de rota e produzirão os mesmos valores de rota que foram usados para gerar a URL.
Dica: um aplicativo que usa o ASP.NET MVC deve usar UrlHelper para gerar URLs, em vez de chamar o
roteamento diretamente.
Para obter mais detalhes sobre o processo de geração de URL, consulte url-generation-reference.

Usando o middleware de roteamento


Adicione o pacote NuGet "Microsoft.AspNetCore.Routing".
Adicione o roteamento ao contêiner de serviço em Startup.cs:

public void ConfigureServices(IServiceCollection services)


{
services.AddRouting();
}
As rotas precisam ser configuradas no método Configure na classe Startup . A amostra abaixo usa essas
APIs:
RouteBuilder
Build
MapGet Faz a correspondência apenas de solicitações HTTP GET
UseRouter

public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)


{
var trackPackageRouteHandler = new RouteHandler(context =>
{
var routeValues = context.GetRouteData().Values;
return context.Response.WriteAsync(
$"Hello! Route values: {string.Join(", ", routeValues)}");
});

var routeBuilder = new RouteBuilder(app, trackPackageRouteHandler);

routeBuilder.MapRoute(
"Track Package Route",
"package/{operation:regex(^(track|create|detonate)$)}/{id:int}");

routeBuilder.MapGet("hello/{name}", context =>


{
var name = context.GetRouteValue("name");
// This is the route handler when HTTP GET "hello/<anything>" matches
// To match HTTP GET "hello/<anything>/<anything>,
// use routeBuilder.MapGet("hello/{*name}"
return context.Response.WriteAsync($"Hi, {name}!");
});

var routes = routeBuilder.Build();


app.UseRouter(routes);
}

A tabela abaixo mostra as respostas com os URIs fornecidos.

URI RESPOSTA

/package/create/3 Olá! Valores de rota: [operation, create], [id, 3]

/package/track/-3 Olá! Valores de rota: [operation, track], [id, -3]

/package/track/-3/ Olá! Valores de rota: [operation, track], [id, -3]

/package/track/ <São passados, nenhuma correspondência>

GET /hello/Joe Olá, Joe!

POST /hello/Joe <São passados, corresponde apenas a HTTP GET>

GET /hello/Joe/Smith <São passados, nenhuma correspondência>

Se estiver configurando uma única rota, chame app.UseRouter passando uma instância IRouter . Você não
precisará chamar RouteBuilder .
A estrutura fornece um conjunto de métodos de extensão para criar rotas, como:
MapRoute
MapGet
MapPost
MapPut
MapDelete
MapVerb

Alguns desses métodos, como MapGet , exigem que um RequestDelegate seja fornecido. O RequestDelegate
será usado como o manipulador de rotas quando a rota encontrar a correspondência. Outros métodos nesta
família permitem configurar um pipeline do middleware que será usado como o manipulador de rotas. Se o
método Map não aceitar um manipulador, como MapRoute , ele usará o DefaultHandler .
Os métodos Map[Verb] usam restrições para limitar a rota ao Verbo HTTP no nome do método. Por
exemplo, consulte MapGet e MapVerb.

Referência de modelo de rota


Os tokens entre chaves ( { } ) definem os parâmetros de rota que serão associados se for encontrada uma
correspondência para a rota. Você pode definir mais de um parâmetro de rota em um segmento de rota, mas
eles precisam ser separados por um valor literal. Por exemplo, {controller=Home}{action=Index} não será
uma rota válida, porque não há nenhum valor literal entre {controller} e {action} . Esses parâmetros de
rota precisam ter um nome e podem ter atributos adicionais especificados.
Um texto literal diferente dos parâmetros de rota (por exemplo, {id} ) e do separador de caminho / precisa
corresponder ao texto na URL. A correspondência de texto não diferencia maiúsculas de minúsculas e se
baseia na representação decodificada do caminho de URLs. Para encontrar a correspondência do delimitador
de parâmetro de rota literal { ou } , faça seu escape repetindo o caractere ( {{ ou }} ).
Padrões de URL que tentam capturar um nome de arquivo com uma extensão de arquivo opcional
apresentam considerações adicionais. Por exemplo, o uso do modelo files/{filename}.{ext?} – quando
filename e ext existirem, os dois valores serão populados. Se apenas filename existir na URL, a rota
encontrará a correspondência, pois o ponto à direita . é opcional. As seguintes URLs corresponderão a essa
rota:
/files/myFile.txt
/files/myFile.
/files/myFile

Use o caractere * como um prefixo para um parâmetro de rota para associá-lo ao restante do URI – isso é
chamado de um parâmetro catch-all. Por exemplo, blog/{*slug} corresponderá a qualquer URI que começa
com /blog e tem qualquer valor (que será atribuído ao valor de rota slug ). Os parâmetros catch-all
também podem corresponder à cadeia de caracteres vazia.
Os parâmetros de rota podem ter valores padrão, designados por meio da especificação do padrão após o
nome do parâmetro, separado por um = . Por exemplo, {controller=Home} definirá Home como o valor
padrão para controller . O valor padrão é usado se nenhum valor está presente na URL para o parâmetro.
Além dos valores padrão, os parâmetros de rota podem ser opcionais (especificados por meio do acréscimo
de um ? ao final do nome do parâmetro, como em id? ). A diferença entre opcional e "tem o padrão" é que
um parâmetro de rota com um valor padrão sempre produz um valor; um parâmetro opcional tem um valor
somente quando ele é fornecido.
Os parâmetros de rota também podem ter restrições, que precisam corresponder ao valor de rota associado
da URL. A adição de dois-pontos : e do nome da restrição após o nome do parâmetro de rota especifica
uma restrição embutida em um parâmetro de rota. Se a restrição exigir argumentos, eles serão fornecidos
entre parênteses ( ) após o nome da restrição. Várias restrições embutidas podem ser especificadas por
meio do acréscimo de outros dois-pontos : e do nome da restrição. O nome da restrição é passado para o
serviço IInlineConstraintResolver para criar uma instância de IRouteConstraint a ser usada no
processamento de URL. Por exemplo, o modelo de rota blog/{article:minlength(10)} especifica a restrição
minlength com o argumento 10 . Para obter uma descrição mais detalhada das restrições de rota, bem
como uma listagem das restrições fornecidas pela estrutura, consulte route-constraint-reference.
A tabela a seguir demonstra alguns modelos de rota e seu comportamento.

MODELO DE ROTA URL DE CORRESPONDÊNCIA DE EXEMPLO OBSERVAÇÕES

hello /hello Somente corresponde ao caminho


único /hello

{Page=Home} / Faz a correspondência e define Page


como Home

{Page=Home} /Contact Faz a correspondência e define Page


como Contact

{controller}/{action}/{id?} /Products/List É mapeado para o controlador


Products e a ação List

{controller}/{action}/{id?} /Products/Details/123 É mapeado para o controlador


Products e a ação Details . id
definido como 123

{controller=Home}/{action=Index}/{id? / É mapeado para o controlador Home


} e o método Index ; id é ignorado.

Em geral, o uso de um modelo é a abordagem mais simples para o roteamento. Restrições e padrões
também podem ser especificados fora do modelo de rota.
Dica: habilite o Log para ver como as implementações de roteamento internas, como Route , fazem a
correspondência de solicitações.

Referência de restrições de rota


As restrições da rota são executadas quando uma Route correspondeu à sintaxe da URL de entrada e criou
um token do caminho de URL para valores de rota. Em geral, as restrições da rota inspecionam o valor de
rota associado por meio do modelo de rota e tomam uma decisão simples do tipo "sim/não" sobre se o valor
é aceitável ou não. Algumas restrições da rota usam dados fora do valor de rota para considerar se a
solicitação pode ser encaminhada. Por exemplo, a HttpMethodRouteConstraint pode aceitar ou rejeitar uma
solicitação de acordo com o verbo HTTP.

AVISO
Evite usar restrições para validação de entrada, porque fazer isso significa que uma entrada inválida resultará em um
404 (Não Encontrado), em vez de um 400 com uma mensagem de erro apropriada. As restrições da rota devem ser
usadas para desfazer a ambiguidade entre rotas semelhantes, não para validar as entradas de uma rota específica.

A tabela a seguir demonstra algumas restrições de rota e seu comportamento esperado.


CORRESPONDÊNCIAS DE
RESTRIÇÃO EXEMPLO EXEMPLO OBSERVAÇÕES

int {id:int} 123456789 , -123456789 Corresponde a qualquer


inteiro

bool {active:bool} true , FALSE Corresponde a true ou


false (não diferencia
maiúsculas de minúsculas)

datetime {dob:datetime} 2016-12-31 , Corresponde a um valor


2016-12-31 7:32pm DateTime válido (na
cultura invariável – veja o
aviso)

decimal {price:decimal} 49.99 , -1,000.01 Corresponde a um valor


decimal válido (na
cultura invariável – veja o
aviso)

double {weight:double} 1.234 , -1,001.01e8 Corresponde a um valor


double válido (na cultura
invariável – veja o aviso)

float {weight:float} 1.234 , -1,001.01e8 Corresponde a um valor


float válido (na cultura
invariável – veja o aviso)

guid {id:guid} CD2C1638-1638-72D5- Corresponde a um valor


1638-DEADBEEF1638 Guid válido
,
{CD2C1638-1638-72D5-
1638-DEADBEEF1638}

long {ticks:long} 123456789 , -123456789 Corresponde a um valor


long válido

minlength(value) {username:minlength(4)} Rick A cadeia de caracteres deve


ter, no mínimo, 4
caracteres

maxlength(value) {filename:maxlength(8)} Richard A cadeia de caracteres não


pode ser maior que 8
caracteres

length(length) {filename:length(12)} somefile.txt A cadeia de caracteres deve


ter exatamente 12
caracteres

length(min,max) {filename:length(8,16)} somefile.txt A cadeia de caracteres deve


ter, pelo menos, 8 e não
mais de 16 caracteres

min(value) {age:min(18)} 19 O valor inteiro deve ser,


pelo menos, 18
CORRESPONDÊNCIAS DE
RESTRIÇÃO EXEMPLO EXEMPLO OBSERVAÇÕES

max(value) {age:max(120)} 91 O valor inteiro não deve


ser maior que 120

range(min,max) {age:range(18,120)} 91 O valor inteiro deve ser,


pelo menos, 18, mas não
maior que 120

alpha {name:alpha} Rick A cadeia de caracteres deve


consistir em um ou mais
caracteres alfabéticos ( a -
z , não diferencia
maiúsculas de minúsculas)

regex(expression) {ssn:regex(^\\d{{3}}- 123-45-6789 A cadeia de caracteres deve


\\d{{2}}-\\d{{4}}$)} corresponder à expressão
regular (veja as dicas sobre
como definir uma
expressão regular)

required {name:required} Rick Usado para impor que um


valor não parâmetro está
presente durante a geração
de URL

AVISO
Restrições de rota que verificam se a URL pode ser convertida em um tipo CLR (como int ou DateTime ) sempre
usam a cultura invariável – elas supõem que a URL não é localizável. As restrições de rota fornecidas pela estrutura não
modificam os valores armazenados nos valores de rota. Todos os valores de rota analisados com base na URL serão
armazenados como cadeias de caracteres. Por exemplo, a restrição de rota Float tentará converter o valor de rota em
um float, mas o valor convertido é usado somente para verificar se ele pode ser convertido em um float.

Expressões regulares
A estrutura do ASP.NET Core adiciona
RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant ao construtor de expressão
regular. Consulte Enumeração de RegexOptions para obter uma descrição desses membros.
As expressões regulares usam delimitadores e tokens semelhantes aos usados pelo Roteamento e pela
linguagem C#. Os tokens de expressão regular precisam ter escape. Por exemplo, para usar a expressão
regular ^\d{3}-\d{2}-\d{4}$ no Roteamento, ela precisa ter os caracteres \ digitados como \\ no arquivo
de origem C# para fazer o escape do caractere de escape da cadeia de caracteres \ (a menos que literais de
cadeia de caracteres textuais estejam sendo usados). Os caracteres { , } , '[' e ']' precisam ter o escape com
aspas duplas para fazer o escape dos caracteres de delimitador do parâmetro de Roteamento. A tabela abaixo
mostra uma expressão regular e a versão com escape.

EXPRESSÃO OBSERVAÇÃO

^\d{3}-\d{2}-\d{4}$ Expressão regular

^\\d{{3}}-\\d{{2}}-\\d{{4}}$ Com escape


EXPRESSÃO OBSERVAÇÃO

^[a-z]{2}$ Expressão regular

^[[a-z]]{{2}}$ Com escape

As expressões regulares usadas no roteamento geralmente começarão com o caractere ^ (corresponde à


posição inicial da cadeia de caracteres) e terminarão com o caractere $ (corresponde à posição final da
cadeia de caracteres). Os caracteres ^ e $ garantem que a expressão regular corresponde a todo o valor do
parâmetro de rota. Sem os caracteres ^ e $ , a expressão regular corresponderá a qualquer subcadeia de
caracteres na cadeia de caracteres, o que geralmente não é o desejado. A tabela abaixo mostra alguns
exemplos e explica por que eles encontram ou não uma correspondência.

EXPRESSÃO CADEIA DE CARACTERES CORRESPONDER A COMENTÁRIO

[a-z]{2} hello sim correspondências com a


subcadeia de caracteres

[a-z]{2} 123abc456 sim correspondências com a


subcadeia de caracteres

[a-z]{2} mz sim correspondência com a


expressão

[a-z]{2} MZ sim não diferencia maiúsculas


de minúsculas

^[a-z]{2}$ hello no consulte ^ e $ acima

^[a-z]{2}$ 123abc456 no consulte ^ e $ acima

Consulte Expressões regulares do .NET Framework para obter mais informações sobre a sintaxe de
expressão regular.
Para restringir um parâmetro a um conjunto conhecido de valores possíveis, use uma expressão regular. Por
exemplo, {action:regex(^(list|get|create)$)} corresponde o valor de rota action apenas a list , get ou
create . Se for passada para o dicionário de restrições, a cadeia de caracteres "^(list|get|create)$" será
equivalente. As restrições passadas para o dicionário de restrições (não embutidas em um modelo) que não
correspondem a uma das restrições conhecidas também são tratadas como expressões regulares.

Referência de geração de URL


O exemplo abaixo mostra como gerar um link para uma rota com um dicionário de valores de rota e uma
RouteCollection .
app.Run(async (context) =>
{
var dictionary = new RouteValueDictionary
{
{ "operation", "create" },
{ "id", 123}
};

var vpc = new VirtualPathContext(context, null, dictionary, "Track Package Route");


var path = routes.GetVirtualPath(vpc).VirtualPath;

context.Response.ContentType = "text/html";
await context.Response.WriteAsync("Menu<hr/>");
await context.Response.WriteAsync($"<a href='{path}'>Create Package 123</a><br/>");
});

O VirtualPath gerado no final da amostra acima é /package/create/123 .


O segundo parâmetro para o construtor VirtualPathContext é uma coleção de valores de ambiente. Os
valores de ambiente proporcionam conveniência, limitando o número de valores que um desenvolvedor
precisa especificar em determinado contexto de solicitação. Os valores de rota atuais da solicitação atual são
considerados valores de ambiente para a geração de link. Por exemplo, em um aplicativo ASP.NET MVC, se
você estiver na ação About do HomeController , não precisará especificar o valor de rota do controlador a ser
vinculado à ação Index (o valor de ambiente Home será usado).
Os valores de ambiente que não correspondem a um parâmetro são ignorados e os valores de ambiente
também são ignorados quando um valor fornecido de forma explícita o substitui, seguindo da esquerda para
a direita na URL.
Os valores que são fornecidos de forma explícita, mas que não correspondem a nada, são adicionados à
cadeia de caracteres de consulta. A tabela a seguir mostra o resultado do uso do modelo de rota
{controller}/{action}/{id?} .

VALORES DE AMBIENTE VALORES EXPLÍCITOS RESULTADO

controller="Home" action="About" /Home/About

controller="Home" controller="Order",action="About" /Order/About

controller="Home",color="Red" action="About" /Home/About

controller="Home" action="About",color="Red" /Home/About?color=Red

Se uma rota tem um valor padrão que não corresponde a um parâmetro e esse valor é fornecido de forma
explícita, ele precisa corresponder ao valor padrão. Por exemplo:

routes.MapRoute("blog_route", "blog/{*slug}",
defaults: new { controller = "Blog", action = "ReadPost" });

A geração de link gera um link para essa rota quando os valores correspondentes do controlador e da ação
são fornecidos.
Middleware de Reconfiguração de URL no ASP.NET
Core
08/05/2018 • 30 min to read • Edit Online

Por Luke Latham e Mikael Mengistu


Exibir ou baixar código de exemplo (como baixar)
A reconfiguração de URL é o ato de modificar URLs de solicitação com base em uma ou mais regras
predefinidas. A reconfiguração de URL cria uma abstração entre locais de recursos e seus endereços, de forma
que os locais e endereços não estejam totalmente vinculados. Há vários cenários em que a reconfiguração de
URL é útil:
Mover ou substituir recursos de servidor temporária ou permanentemente mantendo localizadores estáveis
para esses recursos
Dividir o processamento de solicitação em diferentes aplicativos ou áreas de um aplicativo
Remover, adicionar ou reorganizar segmentos de URL em solicitações de entrada
Otimizar URLs públicas para SEO (otimização do mecanismo de pesquisa)
Permitir o uso de URLs públicas amigáveis para ajudar as pessoas a prever o conteúdo que encontrarão
seguindo um link
Redirecionar solicitações não seguras para pontos de extremidade seguros
Impedir hotlinking de imagem
Defina regras para alterar a URL de várias maneiras, incluindo regex, regras do módulo mod_rewrite do Apache,
regras do Módulo de Reconfiguração do IIS e uso de uma lógica de regra personalizada. Este documento
apresenta a reconfiguração de URL com instruções sobre como usar o Middleware de Reconfiguração de URL
em aplicativos ASP.NET Core.

OBSERVAÇÃO
A reconfiguração de URL pode reduzir o desempenho de um aplicativo. Sempre que possível, você deve limitar o número e
a complexidade de regras.

Redirecionamento e reconfiguração de URL


A diferença entre os termos redirecionamento de URL e reconfiguração de URL pode parecer sutil no primeiro
momento, mas tem implicações importantes no fornecimento de recursos aos clientes. O Middleware de
Reconfiguração de URL do ASP.NET Core pode atender às necessidades de ambos.
Um redirecionamento de URL é uma operação do lado do cliente, na qual o cliente é instruído a acessar um
recurso em outro endereço. Isso exige a ida e vinda para o servidor. A URL de redirecionamento retornada para o
cliente é exibida na barra de endereços do navegador quando o cliente faz uma nova solicitação para o recurso.
Se /resource é redirecionado para /different-resource , o cliente solicita /resource . O servidor responde que o
cliente deve obter o recurso em /different-resource com um código de status que indica que o
redirecionamento é temporário ou permanente. O cliente executa uma nova solicitação para o recurso na URL de
redirecionamento.
Ao redirecionar solicitações para uma URL diferente, indique se o redirecionamento é permanente ou
temporário. O código de status 301 (Movido Permanentemente) é usado quando o recurso tem uma nova URL
permanente e você deseja instruir o cliente que todas as solicitações futuras para o recurso devem usar a nova
URL. O cliente pode armazenar a resposta em cache quando um código de status 301 é recebido. O código de
status 302 (Encontrado) é usado quando o redirecionamento é temporário ou geralmente sujeito à alteração,
como aquele em que o cliente não deve armazenar e reutilizar a URL de redirecionamento no futuro. Para obter
mais informações, consulte RFC 2616: definições de código de status.
Uma reconfiguração de URL é uma operação do servidor usada para fornecer um recurso de outro endereço de
recurso. A reconfiguração de uma URL não exige a ida e vinda para o servidor. A URL reconfigurada não é
retornada para o cliente e não será exibida na barra de endereços do navegador. Quando /resource é
reconfigurado para /different-resource , o cliente solicita /resource e o servidor busca internamente o recurso
em /different-resource . Embora o cliente possa ter a capacidade de recuperar o recurso na URL reconfigurada,
o cliente não será informado de que o recurso existe na URL reconfigurada quando fizer a solicitação e receber a
resposta.

Aplicativo de exemplo de reconfiguração de URL


Explore os recursos do Middleware de Reconfiguração de URL com o aplicativo de exemplo de reconfiguração
de URL. O aplicativo aplica as regras de reconfiguração e redirecionamento e mostra a URL reconfigurada ou
redirecionada.

Quando usar o Middleware de Reconfiguração de URL


Use o Middleware de Reconfiguração de URL quando não puder usar o módulo de Reconfiguração de URL com
o IIS no Windows Server, o módulo mod_rewrite do Apache no Apache Server, a reconfiguração de URL no
Nginx ou quando o aplicativo estiver hospedado no servidor HTTP.sys (anteriormente chamado WebListener). As
principais razões para o uso das tecnologias de reconfiguração de URL baseadas em servidor no IIS, Apache ou
Nginx são que o middleware não dá suporte aos recursos completos desses módulos e o desempenho do
middleware provavelmente não corresponderá ao desempenho dos módulos. No entanto, há alguns recursos
dos módulos de servidor que não funcionam com projetos do ASP.NET Core, como as restrições IsFile e
IsDirectory do módulo de Reconfiguração de IIS. Nesses cenários, use o middleware.

Pacote
Para incluir o middleware em seu projeto, adicione uma referência ao pacote Microsoft.AspNetCore.Rewrite . Esse
recurso está disponível para aplicativos direcionados ao ASP.NET Core 1.1 ou posterior.
Extensão e opções
Estabeleça as regras de reconfiguração e redirecionamento de URL criando uma instância da classe
RewriteOptions com métodos de extensão para cada uma das regras. Encadeie várias regras na ordem em que
deseja que sejam processadas. As RewriteOptions são passadas para o Middleware de Reconfiguração de URL,
conforme ele é adicionado ao pipeline de solicitação com app.UseRewriter(options); .
ASP.NET Core 2.x
ASP.NET Core 1.x

public void Configure(IApplicationBuilder app)


{
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(MethodRules.RedirectXMLRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));

app.UseRewriter(options);
}

app.Run(context => context.Response.WriteAsync(


$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}

Redirecionamento de URL
Use AddRedirect para redirecionar as solicitações. O primeiro parâmetro contém o regex para correspondência
no caminho da URL de entrada. O segundo parâmetro é a cadeia de caracteres de substituição. O terceiro
parâmetro, se presente, especifica o código de status. Se você não especificar o código de status, ele usará como
padrão 302 (Encontrado), que indica que o recurso foi movido temporariamente ou substituído.
ASP.NET Core 2.x
ASP.NET Core 1.x
public void Configure(IApplicationBuilder app)
{
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(MethodRules.RedirectXMLRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));

app.UseRewriter(options);
}

app.Run(context => context.Response.WriteAsync(


$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}

Em um navegador com as ferramentas para desenvolvedores habilitadas, faça uma solicitação para o aplicativo
de exemplo com o caminho /redirect-rule/1234/5678 . O regex corresponde ao caminho de solicitação em
redirect-rule/(.*) e o caminho é substituído por /redirected/1234/5678 . A URL de redirecionamento é enviada
novamente ao cliente com um código de status 302 (Encontrado). O navegador faz uma nova solicitação na URL
de redirecionamento, que é exibida na barra de endereços do navegador. Como nenhuma regra no aplicativo de
exemplo corresponde à URL de redirecionamento, a segunda solicitação recebe uma resposta 200 (OK) do
aplicativo e o corpo da resposta mostra a URL de redirecionamento. Uma ida e vinda é feita para o servidor
quando uma URL é redirecionada.

AVISO
Tenha cuidado ao estabelecer as regras de redirecionamento. As regras de redirecionamento são avaliadas em cada
solicitação para o aplicativo, incluindo após um redirecionamento. É fácil criar acidentalmente um loop de
redirecionamentos infinitos.

Solicitação original: /redirect-rule/1234/5678

A parte da expressão contida nos parênteses é chamada um grupo de captura. O ponto ( . ) da expressão
significa corresponder a qualquer caractere. O asterisco ( * ) indica corresponder ao caractere zero precedente
ou mais vezes. Portanto, os dois últimos segmentos de caminho da URL, 1234/5678 , são capturados pelo grupo
de captura (.*) . Qualquer valor que você fornecer na URL de solicitação após redirect-rule/ é capturado por
esse único grupo de captura.
Na cadeia de caracteres de substituição, os grupos capturados são injetados na cadeia de caracteres com o cifrão
( $ ) seguido do número de sequência da captura. O primeiro valor de grupo de captura é obtido com $1 , o
segundo com $2 e eles continuam em sequência para os grupos de captura no regex. Há apenas um grupo
capturado no regex da regra de redirecionamento no aplicativo de exemplo, para que haja apenas um grupo
injetado na cadeia de caracteres de substituição, que é $1 . Quando a regra é aplicada, a URL se torna
/redirected/1234/5678 .

Redirecionamento de URL para um ponto de extremidade seguro


Use AddRedirectToHttps para redirecionar solicitações HTTP para o mesmo host e caminho usando HTTPS (
https:// ). Se o código de status não for fornecido, o middleware usará como padrão 302 ( Encontrado). Se a
porta não for fornecida, o middleware usará null como padrão, o que significa que o protocolo é alterado para
https:// e o cliente acessa o recurso na porta 443. O exemplo mostra como definir o código de status como
301 (Movido Permanentemente) e alterar a porta para 5001.

public void Configure(IApplicationBuilder app)


{
var options = new RewriteOptions()
.AddRedirectToHttps(301, 5001);

app.UseRewriter(options);
}

Use AddRedirectToHttpsPermanent para redirecionar solicitações não seguras para o mesmo host e caminho com
o protocolo HTTPS seguro ( https:// na porta 443). O middleware define o código de status como 301 (Movido
Permanentemente).

public void Configure(IApplicationBuilder app)


{
var options = new RewriteOptions()
.AddRedirectToHttpsPermanent();

app.UseRewriter(options);
}

O aplicativo de exemplo pode demonstrar como usar AddRedirectToHttps ou AddRedirectToHttpsPermanent .


Adicione o método de extensão às RewriteOptions . Faça uma solicitação não segura para o aplicativo em
qualquer URL. Ignore o aviso de segurança do navegador que informa que o certificado autoassinado não é
confiável.
Solicitação original usando AddRedirectToHttps(301, 5001) : /secure
Solicitação original usando AddRedirectToHttpsPermanent : /secure

Reconfiguração de URL
Use AddRewrite para criar uma regra para a reconfiguração de URLs. O primeiro parâmetro contém o regex
para correspondência no caminho da URL de entrada. O segundo parâmetro é a cadeia de caracteres de
substituição. O terceiro parâmetro, skipRemainingRules: {true|false} , indica para o middleware se ele deve ou
não ignorar regras de reconfiguração adicionais se a regra atual é aplicada.
ASP.NET Core 2.x
ASP.NET Core 1.x

public void Configure(IApplicationBuilder app)


{
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(MethodRules.RedirectXMLRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));

app.UseRewriter(options);
}

app.Run(context => context.Response.WriteAsync(


$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}

Solicitação original: /rewrite-rule/1234/5678


A primeira coisa que é observada no regex é o acento circunflexo ( ^ ) no início da expressão. Isso significa que a
correspondência começa no início do caminho da URL.
No exemplo anterior com a regra de redirecionamento, redirect-rule/(.*) , não há nenhum acento circunflexo
no início do regex; portanto, qualquer caractere pode preceder redirect-rule/ no caminho para uma
correspondência bem-sucedida.

CAMINHO CORRESPONDER A

/redirect-rule/1234/5678 Sim

/my-cool-redirect-rule/1234/5678 Sim

/anotherredirect-rule/1234/5678 Sim

A regra de reconfiguração, ^rewrite-rule/(\d+)/(\d+) , corresponde apenas a caminhos se eles são iniciados com
rewrite-rule/ . Observe a diferença na correspondência entre a regra de reconfiguração abaixo e a regra de
redirecionamento acima.

CAMINHO CORRESPONDER A

/rewrite-rule/1234/5678 Sim

/my-cool-rewrite-rule/1234/5678 Não

/anotherrewrite-rule/1234/5678 Não

Após a parte ^rewrite-rule/ da expressão, há dois grupos de captura, (\d+)/(\d+) . O \d significa


corresponder a um dígito (número ). O sinal de adição ( + ) significa corresponder a um ou mais caracteres
anteriores. Portanto, a URL precisa conter um número seguido de uma barra "/" seguida de outro número. Esses
grupos de captura são injetados na URL reconfigurada como $1 e $2 . A cadeia de caracteres de substituição da
regra de reconfiguração coloca os grupos capturados na cadeia de consulta. O caminho solicitado de
/rewrite-rule/1234/5678 foi reconfigurado para obter o recurso em /rewritten?var1=1234&var2=5678 . Se uma
cadeia de consulta estiver presente na solicitação original, ela será preservada quando a URL for reconfigurada.
Não há nenhuma ida e vinda para o servidor para obtenção do recurso. Se o recurso existir, ele será buscado e
retornado ao cliente com um código de status 200 (OK). Como o cliente não é redirecionado, a URL na barra de
endereços do navegador não é alterada. Quanto ao cliente, a operação de reconfiguração de URL nunca ocorreu.
OBSERVAÇÃO
Use skipRemainingRules: true sempre que possível, porque as regras de correspondência são um processo caro e
reduzem o tempo de resposta do aplicativo. Para a resposta mais rápida do aplicativo:
Ordene as regras de reconfiguração da regra com correspondência mais frequente para a regra com correspondência
menos frequente.
Ignore o processamento das regras restantes quando ocorrer uma correspondência e nenhum processamento de regra
adicional for necessário.

mod_rewrite do Apache
Aplique as regras do mod_rewrite do Apache com AddApacheModRewrite . Verifique se o arquivo de regras foi
implantado com o aplicativo. Para obter mais informações e exemplos de regras de mod_rewrite, consulte
mod_rewrite do Apache.
ASP.NET Core 2.x
ASP.NET Core 1.x
Um StreamReader é usado para ler as regras do arquivo de regras ApacheModRewrite.txt.

public void Configure(IApplicationBuilder app)


{
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(MethodRules.RedirectXMLRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));

app.UseRewriter(options);
}

app.Run(context => context.Response.WriteAsync(


$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}

O aplicativo de exemplo redireciona solicitações de /apache-mod-rules-redirect/(.\*) para /redirected?id=$1 .O


código de status da resposta é 302 (Encontrado).

# Rewrite path with additional sub directory


RewriteRule ^/apache-mod-rules-redirect/(.*) /redirected?id=$1 [L,R=302]

Solicitação original: /apache-mod-rules-redirect/1234


Va r i á v e i s d e se r v i d o r c o m p a t í v e i s

O middleware dá suporte às seguintes variáveis de servidor do mod_rewrite do Apache:


CONN_REMOTE_ADDR
HTTP_ACCEPT
HTTP_CONNECTION
HTTP_COOKIE
HTTP_FORWARDED
HTTP_HOST
HTTP_REFERER
HTTP_USER_AGENT
HTTPS
IPV6
QUERY_STRING
REMOTE_ADDR
REMOTE_PORT
REQUEST_FILENAME
REQUEST_METHOD
REQUEST_SCHEME
REQUEST_URI
SCRIPT_FILENAME
SERVER_ADDR
SERVER_PORT
SERVER_PROTOCOL
TIME
TIME_DAY
TIME_HOUR
TIME_MIN
TIME_MON
TIME_SEC
TIME_WDAY
TIME_YEAR
Regras do Módulo de Reconfiguração de URL do IIS
Para usar regras que se aplicam ao Módulo de Reconfiguração de URL do IIS, use AddIISUrlRewrite . Verifique se
o arquivo de regras foi implantado com o aplicativo. Não instrua o middleware a usar o arquivo web.config
quando ele estiver em execução no IIS do Windows Server. Com o IIS, essas regras devem ser armazenadas fora
do web.config para evitar conflitos com o módulo de Reconfiguração do IIS. Para obter mais informações e
exemplos de regras do Módulo de Reconfiguração de URL do IIS, consulte Usando o Módulo de Reconfiguração
de URL 2.0 e Referência de configuração do Módulo de Reconfiguração de URL.
ASP.NET Core 2.x
ASP.NET Core 1.x
Um StreamReader é usado para ler as regras do arquivo de regras IISUrlRewrite.xml.

public void Configure(IApplicationBuilder app)


{
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(MethodRules.RedirectXMLRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));

app.UseRewriter(options);
}

app.Run(context => context.Response.WriteAsync(


$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}

O aplicativo de exemplo reconfigura as solicitações de /iis-rules-rewrite/(.*) para /rewritten?id=$1 .A


resposta é enviada ao cliente com um código de status 200 (OK).

<rewrite>
<rules>
<rule name="Rewrite segment to id querystring" stopProcessing="true">
<match url="^iis-rules-rewrite/(.*)$" />
<action type="Rewrite" url="rewritten?id={R:1}" appendQueryString="false"/>
</rule>
</rules>
</rewrite>

Solicitação original: /iis-rules-rewrite/1234


Caso você tenha um Módulo de Reconfiguração do IIS ativo com regras no nível do servidor configuradas que
poderiam afetar o aplicativo de maneiras indesejadas, desabilite o Módulo de Reconfiguração do IIS em um
aplicativo. Para obter mais informações, consulte Desabilitando módulos do IIS.
Recursos sem suporte
ASP.NET Core 2.x
ASP.NET Core 1.x
O middleware liberado com o ASP.NET Core 2.x não dá suporte aos seguintes recursos do Módulo de
Reconfiguração de URL do IIS:
Regras de saída
Variáveis de servidor personalizadas
Curingas
LogRewrittenUrl
Variáveis de servidor compatíveis
O middleware dá suporte às seguintes variáveis de servidor do Módulo de Reconfiguração de URL do IIS:
CONTENT_LENGTH
CONTENT_TYPE
HTTP_ACCEPT
HTTP_CONNECTION
HTTP_COOKIE
HTTP_HOST
HTTP_REFERER
HTTP_URL
HTTP_USER_AGENT
HTTPS
LOCAL_ADDR
QUERY_STRING
REMOTE_ADDR
REMOTE_PORT
REQUEST_FILENAME
REQUEST_URI

OBSERVAÇÃO
Também obtenha um IFileProvider por meio de um PhysicalFileProvider . Essa abordagem pode fornecer maior
flexibilidade para o local dos arquivos de regras de reconfiguração. Verifique se os arquivos de regras de reconfiguração são
implantados no servidor no caminho fornecido.

PhysicalFileProvider fileProvider = new PhysicalFileProvider(Directory.GetCurrentDirectory());

Regra baseada em método


Use Add(Action<RewriteContext> applyRule) para implementar sua própria lógica de regra em um método. O
RewriteContext expõe o HttpContext para uso no método. O context.Result determina como o processamento
de pipeline adicional é manipulado.
CONTEX T.RESULT AÇÃO

RuleResult.ContinueRules (padrão) Continuar aplicando regras

RuleResult.EndResponse Parar de aplicar regras e enviar a resposta

RuleResult.SkipRemainingRules Parar de aplicar regras e enviar o contexto para o próximo


middleware

ASP.NET Core 2.x


ASP.NET Core 1.x

public void Configure(IApplicationBuilder app)


{
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(MethodRules.RedirectXMLRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));

app.UseRewriter(options);
}

app.Run(context => context.Response.WriteAsync(


$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}

O aplicativo de exemplo demonstra um método que redireciona as solicitações para caminhos que terminam
com .xml. Se você faz uma solicitação para /file.xml , ela é redirecionada para /xmlfiles/file.xml . O código de
status é definido como 301 (Movido Permanentemente). Para um redirecionamento, é necessário definir
explicitamente o código de status da resposta; caso contrário, será retornado um código de status 200 (OK) e o
redirecionamento não ocorrerá no cliente.
public static void RedirectXMLRequests(RewriteContext context)
{
var request = context.HttpContext.Request;

// Because we're redirecting back to the same app, stop


// processing if the request has already been redirected
if (request.Path.StartsWithSegments(new PathString("/xmlfiles")))
{
return;
}

if (request.Path.Value.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
{
var response = context.HttpContext.Response;
response.StatusCode = StatusCodes.Status301MovedPermanently;
context.Result = RuleResult.EndResponse;
response.Headers[HeaderNames.Location] =
"/xmlfiles" + request.Path + request.QueryString;
}
}

Solicitação original: /file.xml

Regra baseada em IRule


Use Add(IRule) para implementar sua própria lógica de regra em uma classe que é derivada de IRule . O uso
de uma IRule fornece maior flexibilidade em relação ao uso da abordagem de regra baseada em método. A
classe derivada pode incluir um construtor, no qual você pode passar parâmetros para o método ApplyRule .
ASP.NET Core 2.x
ASP.NET Core 1.x
public void Configure(IApplicationBuilder app)
{
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(MethodRules.RedirectXMLRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));

app.UseRewriter(options);
}

app.Run(context => context.Response.WriteAsync(


$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}

Os valores dos parâmetros no aplicativo de exemplo para a extension e o newPath são verificados para atender
a várias condições. A extension precisa conter um valor que precisa ser .png, .jpg ou .gif. Se o newPath não é
válido, uma ArgumentException é gerada. Se você faz uma solicitação para image.png, ela é redirecionada para
/png-images/image.png . Se você faz uma solicitação para image.jpg, ela é redirecionada para
/jpg-images/image.jpg . O código de status é definido como 301 ( Movido Permanentemente) e o context.Result
é definida para parar o processamento de regras e enviar a resposta.
public class RedirectImageRequests : IRule
{
private readonly string _extension;
private readonly PathString _newPath;

public RedirectImageRequests(string extension, string newPath)


{
if (string.IsNullOrEmpty(extension))
{
throw new ArgumentException(nameof(extension));
}

if (!Regex.IsMatch(extension, @"^\.(png|jpg|gif)$"))
{
throw new ArgumentException("Invalid extension", nameof(extension));
}

if (!Regex.IsMatch(newPath, @"(/[A-Za-z0-9]+)+?"))
{
throw new ArgumentException("Invalid path", nameof(newPath));
}

_extension = extension;
_newPath = new PathString(newPath);
}

public void ApplyRule(RewriteContext context)


{
var request = context.HttpContext.Request;

// Because we're redirecting back to the same app, stop


// processing if the request has already been redirected
if (request.Path.StartsWithSegments(new PathString(_newPath)))
{
return;
}

if (request.Path.Value.EndsWith(_extension, StringComparison.OrdinalIgnoreCase))
{
var response = context.HttpContext.Response;
response.StatusCode = StatusCodes.Status301MovedPermanently;
context.Result = RuleResult.EndResponse;
response.Headers[HeaderNames.Location] =
_newPath + request.Path + request.QueryString;
}
}
}

Solicitação original: /image.png

Solicitação original: /image.jpg


Exemplos do regex
CADEIA DE CARACTERES DE SUBSTITUIÇÃO
CADEIA DE CARACTERES DO REGEX & &
GOAL EXEMPLO DE CORRESPONDÊNCIA EXEMPLO DE SAÍDA

Reconfigurar o caminho na cadeia de ^path/(.*)/(.*) path?var1=$1&var2=$2


consulta /path/abc/123 /path?var1=abc&var2=123

Barra "/" à direita da faixa (.*)/$ $1


/path/ /path

Impor barra "/" à direita (.*[^/])$ $1/


/path /path/

Evitar a reconfiguração de solicitações ^(.*)(?<!\.axd)$ ou rewritten/$1


específicas ^(?!.*\.axd$)(.*)$ /rewritten/resource.htm
Sim: /resource.htm /resource.axd
Não: /resource.axd

Reorganizar segmentos de URL path/(.*)/(.*)/(.*) path/$3/$2/$1


path/1/2/3 path/3/2/1

Substituir um segmento de URL ^(.*)/segment2/(.*) $1/replaced/$2


/segment1/segment2/segment3 /segment1/replaced/segment3

Recursos adicionais
Inicialização de aplicativos
Middleware
Expressões regulares no .NET
Linguagem de expressão regular – referência rápida
mod_rewrite do Apache
Usando o Módulo de Reconfiguração de URL 2.0 (para IIS )
Referência de configuração do Módulo de Reconfiguração de URL
Fórum do Módulo de Reconfiguração de URL do IIS
Manter uma estrutura de URL simples
10 dicas e truques de reconfiguração de URL
Inserir ou não inserir uma barra "/"
Trabalhando com vários ambientes
08/02/2018 • 9 min to read • Edit Online

Por Rick Anderson


O ASP.NET Core fornece suporte para a configuração do comportamento do aplicativo em tempo de execução
com variáveis de ambiente.
Exibir ou baixar código de exemplo (como baixar)

Ambientes
O ASP.NET Core lê a variável de ambiente ASPNETCORE_ENVIRONMENT na inicialização do aplicativo e armazena
esse valor em IHostingEnvironment.EnvironmentName. ASPNETCORE_ENVIRONMENT pode ser definido com
qualquer valor, mas há suporte para três valores na estrutura: Desenvolvimento, Preparo e Produção. Se
ASPNETCORE_ENVIRONMENT não estiver definido, ele usará Production como padrão.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)


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

if (env.IsProduction() || env.IsStaging() || env.IsEnvironment("Staging_2"))


{
app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}

O código anterior:
Chama UseDeveloperExceptionPage e UseBrowserLink quando ASPNETCORE_ENVIRONMENT é definido
como Development .
Chama UseExceptionHandler quando o valor de ASPNETCORE_ENVIRONMENT é definido com um dos
seguintes:
Staging
Production
Staging_2

O Auxiliar de Marca de Ambiente usa o valor de IHostingEnvironment.EnvironmentName para incluir ou excluir a


marcação no elemento:
@page
@inject Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnv
@model AboutModel
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"]</h2>
<h3>@Model.Message</h3>

<p> ASPNETCORE_ENVIRONMENT = @hostingEnv.EnvironmentName</p>

<environment include="Development">
<div>&lt;environment include="Development"&gt;</div>
</environment>
<environment exclude="Development">
<div>&lt;environment exclude="Development"&gt;</div>
</environment>
<environment include="Staging,Development,Staging_2">
<div>
&lt;environment include="Staging,Development,Staging_2"&gt;
</div>
</environment>

Observação: no Windows e macOS, valores e variáveis de ambiente não diferenciam maiúsculas de


minúsculas. Valores e variáveis de ambiente do Linux diferenciam maiúsculas de minúsculas por padrão.
Desenvolvimento
O ambiente de desenvolvimento pode habilitar recursos que não devem ser expostos em produção. Por
exemplo, os modelos do ASP.NET Core habilitam a página de exceção do desenvolvedor no ambiente de
desenvolvimento.
O ambiente de desenvolvimento do computador local pode ser definido no arquivo
Properties\launchSettings.json do projeto. Os valores de ambiente definidos em launchSettings.json
substituem os valores definidos no ambiente do sistema.
O seguinte JSON mostra três perfis de um arquivo launchSettings.json:
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:54339/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"WebApp1": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Staging"
},
"applicationUrl": "http://localhost:54340/"
},
"Kestrel Staging": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_My_Environment": "1",
"ASPNETCORE_DETAILEDERRORS": "1",
"ASPNETCORE_ENVIRONMENT": "Staging"
},
"applicationUrl": "http://localhost:51997/"
}
}
}

Quando o aplicativo for iniciado com dotnet run , o primeiro perfil com "commandName": "Project" será usado.
O valor de commandName especifica o servidor Web a ser iniciado. commandName pode ser um destes:
IIS Express
IIS
Projeto (que inicia o Kestrel)
Quando um aplicativo é iniciado com dotnet run :
launchSettings.json é lido, se está disponível. As configurações de environmentVariables em
launchSettings.json substituem as variáveis de ambiente.
O ambiente de hospedagem é exibido.
O seguinte resultado mostra um aplicativo iniciado com dotnet run :

PS C:\Webs\WebApp1> dotnet run


Using launch settings from C:\Webs\WebApp1\Properties\launchSettings.json...
Hosting environment: Staging
Content root path: C:\Webs\WebApp1
Now listening on: http://localhost:54340
Application started. Press Ctrl+C to shut down.

A guia Depurar do Visual Studio fornece uma GUI para editar o arquivo launchSettings.json:
As alterações feitas nos perfis do projeto poderão não ter efeito até que o servidor Web seja reiniciado. O
Kestrel precisa ser reiniciado antes de detectar as alterações feitas em seu ambiente.

AVISO
launchSettings.json não deve armazenar segredos. A ferramenta Secret Manager pode ser usado para armazenar
segredos de desenvolvimento local.

Produção
O ambiente de produção deve ser configurado para maximizar a segurança, o desempenho e a robustez do
aplicativo. Algumas configurações comuns que um ambiente de produção pode ter que são diferentes do
desenvolvimento incluem:
Cache.
Recursos do lado do cliente são agrupados, minimizados e potencialmente atendidos por meio de uma
CDN.
Páginas de erro de diagnóstico desabilitadas.
Páginas de erro amigáveis habilitadas.
Log de produção e monitoramento habilitados. Por exemplo, Application Insights.

Configurando o ambiente
Geralmente, é útil definir um ambiente específico para teste. Se o ambiente não for definido, ele usará
Production como padrão, o que desabilita a maioria dos recursos de depuração.

O método para configurar o ambiente depende do sistema operacional.


Azure
Para o Serviço de Aplicativo do Azure:
Selecione a folha Configurações de Aplicativo.
Adicione a chave e o valor em Configurações de aplicativo.
Windows
Para definir o ASPNETCORE_ENVIRONMENT para a sessão atual, se o aplicativo é iniciado com dotnet run , os
seguintes comandos são usados
Linha de comando

set ASPNETCORE_ENVIRONMENT=Development

PowerShell

$Env:ASPNETCORE_ENVIRONMENT = "Development"

Esses comandos têm efeito somente para a janela atual. Quando a janela é fechada, a configuração
ASPNETCORE_ENVIRONMENT é revertida para a configuração padrão ou o valor do computador. Para
definir o valor globalmente no Windows, abra o Painel de Controle > Sistema > Configurações
avançadas do sistema e adicione ou edite o valor ASPNETCORE_ENVIRONMENT .

web.config
Consulte a seção Configurando variáveis de ambiente do tópico Referência de configuração do Módulo do
ASP.NET Core.
Por pool de aplicativos do IIS
Para definir variáveis de ambiente para aplicativos individuais executados em Pools de Aplicativos isolados
(compatíveis com o IIS 10.0+), consulte a seção Comando AppCmd.exe do tópico Variáveis de ambiente
<environmentVariables>.
macOS
A configuração do ambiente atual para o macOS pode ser feita em linha durante a execução do aplicativo

ASPNETCORE_ENVIRONMENT=Development dotnet run

ou usando export para configurá-lo antes da execução do aplicativo.

export ASPNETCORE_ENVIRONMENT=Development

As variáveis de ambiente no nível do computador são definidas no arquivo .bashrc ou .bash_profile. Edite o
arquivo usando qualquer editor de texto e adicione a instrução a seguir.

export ASPNETCORE_ENVIRONMENT=Development

Linux
Para distribuições Linux, use o comando export na linha de comando para as configurações de variável
baseadas na sessão e o arquivo bash_profile para as configurações de ambiente no nível do computador.
Configuração por ambiente
Consulte Configuração por ambiente para obter mais informações.

Classe Startup e métodos baseados no ambiente


Quando um aplicativo ASP.NET Core é iniciado, a classe Startup inicia o aplicativo. Se houver uma classe
Startup{EnvironmentName} , essa classe será chamada para esse EnvironmentName :
public class StartupDevelopment
{
public StartupDevelopment(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)


{
services.AddMvc();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)


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

if (env.IsProduction() || env.IsStaging())
{
throw new Exception("Not development.");
}

app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
}

Observação: a chamada a WebHostBuilder.UseStartup substitui as seções de configuração.


Configure e ConfigureServices são compatíveis com versões específicas ao ambiente do formato
Configure{EnvironmentName} e Configure{EnvironmentName}Services :
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)


{
services.AddMvc();
}

public void ConfigureStagingServices(IServiceCollection services)


{
services.AddMvc();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)


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

if (env.IsProduction() || env.IsStaging() || env.IsEnvironment("Staging_2"))


{
app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}

public void ConfigureStaging(IApplicationBuilder app, IHostingEnvironment env)


{
if (!env.IsStaging())
{
throw new Exception("Not staging.");
}

app.UseExceptionHandler("/Error");
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
}

Recursos adicionais
Inicialização de aplicativos
Configuração
IHostingEnvironment.EnvironmentName
Configuração no ASP.NET Core
04/05/2018 • 26 min to read • Edit Online

Por Rick Anderson, Mark Michaelis, Steve Smith, Daniel Roth e Luke Latham
A API de configuração fornece uma maneira de configurar um aplicativo Web do ASP.NET Core com base em uma
lista de pares nome-valor. A configuração é lida em tempo de execução por meio de várias fontes. Os pares de
nome-valor podem ser agrupados em uma hierarquia de vários níveis.
Há provedores de configuração para:
Formatos de arquivo (INI, JSON e XML ).
Argumentos de linha de comando.
Variáveis de ambiente.
Objetos do .NET na memória.
O armazenamento do Secret Manager não criptografado.
Um repositório de usuário criptografado, como o Azure Key Vault.
Provedores personalizados (instalados ou criados).
Cada valor de configuração é mapeado para uma chave de cadeia de caracteres. Há suporte interno de associação
para desserializar configurações em um objeto POCO personalizado (uma classe simples de .NET com
propriedades).
O padrão de opções usa classes de opções para representar grupos de configurações relacionadas. Para obter mais
informações sobre como usar o padrão de opções, consulte o tópico Opções.
Exibir ou baixar código de exemplo (como baixar)

Configuração de JSON
O aplicativo de console a seguir usa o provedor de configuração JSON:
using System;
using System.IO;
// Requires NuGet package
// Microsoft.Extensions.Configuration.Json
using Microsoft.Extensions.Configuration;

public class Program


{
public static IConfiguration Configuration { get; set; }

public static void Main(string[] args = null)


{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");

Configuration = builder.Build();

Console.WriteLine($"option1 = {Configuration["Option1"]}");
Console.WriteLine($"option2 = {Configuration["option2"]}");
Console.WriteLine(
$"suboption1 = {Configuration["subsection:suboption1"]}");
Console.WriteLine();

Console.WriteLine("Wizards:");
Console.Write($"{Configuration["wizards:0:Name"]}, ");
Console.WriteLine($"age {Configuration["wizards:0:Age"]}");
Console.Write($"{Configuration["wizards:1:Name"]}, ");
Console.WriteLine($"age {Configuration["wizards:1:Age"]}");
Console.WriteLine();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}

O aplicativo lê e exibe as seguintes definições de configuração:

{
"option1": "value1_from_json",
"option2": 2,

"subsection": {
"suboption1": "subvalue1_from_json"
},
"wizards": [
{
"Name": "Gandalf",
"Age": "1000"
},
{
"Name": "Harry",
"Age": "17"
}
]
}

A configuração consiste em uma lista hierárquica de pares nome-valor na qual os nós são separados por dois
pontos ( : ). Para recuperar um valor, acesse o indexador Configuration com a chave do item correspondente:

Console.WriteLine(
$"suboption1 = {Configuration["subsection:suboption1"]}");
Para trabalhar com matrizes em fontes de configuração formatadas em JSON, use um índice de matriz como parte
da cadeia de caracteres separada por dois pontos. O exemplo a seguir obtém o nome do primeiro item na matriz
wizards anterior:

Console.Write($"{Configuration["wizards:0:Name"]}");
// Output: Gandalf

Os pares nome-valor gravados nos provedores de Configuração internos não são mantidos. No entanto, um
provedor personalizado que salva valores pode ser criado. Consulte provedor de configuração personalizado.
O exemplo anterior usa o indexador de configuração para ler valores. Para acessar a configuração fora de Startup ,
use o padrão de opções. Para obter mais informações, consulte o tópico Opções.

Configuração XML
Para trabalhar com matrizes em fontes de configuração formatadas em XML, forneça um índice name para cada
elemento. Use o índice para acessar os valores:

<wizards>
<wizard name="Gandalf">
<age>1000</age>
</wizard>
<wizard name="Harry">
<age>17</age>
</wizard>
</wizards>

Console.Write($"{Configuration["wizard:Harry:age"]}");
// Output: 17

Configuração por ambiente


É comum ter configurações diferentes para ambientes diferentes, por exemplo, desenvolvimento, teste e produção.
O método de extensão CreateDefaultBuilder em um aplicativo do ASP.NET Core 2.x (ou usando AddJsonFile e
AddEnvironmentVariables diretamente em um aplicativo do ASP.NET Core 1.x) adiciona provedores de configuração
para ler arquivos JSON e fontes de configuração do sistema:
appsettings.json
appsettings.<EnvironmentName>.json
Variáveis de ambiente
Aplicativos do ASP.NET Core 1.x precisam chamar AddJsonFile e AddEnvironmentVariables.
Consulte AddJsonFile para uma explicação sobre os parâmetros. reloadOnChange só é compatível no ASP.NET Core
1.1 ou posterior.
As fontes de configuração são lidas na ordem em que são especificadas. No código anterior, as variáveis de
ambiente são lidas por último. Os valores de configuração definidos por meio do ambiente substituem aqueles
definidos nos dois provedores anteriores.
Considere o arquivo appsettings.Staging.json a seguir:
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"System": "Information",
"Microsoft": "Information"
}
},
"MyConfig": "My Config Value for staging."
}

Quando o ambiente está configurado como Staging , o seguinte método Configure lê o valor de MyConfig :

public void Configure(IApplicationBuilder app, IHostingEnvironment env)


{
var myConfig = Configuration["MyConfig"];
// use myConfig
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}

if (env.IsProduction() || env.IsStaging())
{
app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}

O ambiente normalmente é definido como Development , Staging ou Production . Para obter mais informações,
veja Trabalhar com vários ambientes.
Considerações de configuração:
IOptionsSnapshot pode recarregar dados de configuração quando é alterado.
Chaves de configuração não diferenciam maiúsculas de minúsculas.
Nunca armazene senhas ou outros dados confidenciais no código do provedor de configuração ou nos
arquivos de configuração de texto sem formatação. Não use segredos de produção em ambientes de teste ou de
desenvolvimento. Especifique segredos fora do projeto para que eles não sejam acidentalmente comprometidos
com um repositório de código-fonte. Saiba mais sobre como trabalhar com vários ambientes e gerenciar
armazenamento seguro de segredos de aplicativo no desenvolvimento.
Para saber valores de configuração hierárquica especificados nas variáveis de ambiente, dois-pontos ( : ) pode
não funcionar em todas as plataformas. Sublinhado duplo ( __ ) é compatível com todas as plataformas.
Ao interagir com a configuração de API, dois-pontos ( : ) funciona em todas as plataformas.

Provedor na memória e associação a uma classe POCO


O exemplo a seguir mostra como usar o provedor na memória e associar a uma classe:
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;

public class Program


{
public static IConfiguration Configuration { get; set; }

public static void Main(string[] args = null)


{
var dict = new Dictionary<string, string>
{
{"Profile:MachineName", "Rick"},
{"App:MainWindow:Height", "11"},
{"App:MainWindow:Width", "11"},
{"App:MainWindow:Top", "11"},
{"App:MainWindow:Left", "11"}
};

var builder = new ConfigurationBuilder();


builder.AddInMemoryCollection(dict);

Configuration = builder.Build();

Console.WriteLine($"Hello {Configuration["Profile:MachineName"]}");

var window = new MyWindow();


// Bind requrires NuGet package
// Microsoft.Extensions.Configuration.Binder
Configuration.GetSection("App:MainWindow").Bind(window);
Console.WriteLine($"Left {window.Left}");
Console.WriteLine();

Console.WriteLine("Press any key...");


Console.ReadKey();
}
}

public class MyWindow


{
public int Height { get; set; }
public int Width { get; set; }
public int Top { get; set; }
public int Left { get; set; }
}

Os valores de configuração são retornados como cadeias de caracteres, mas a associação permite a construção de
objetos. A associação permite recuperar objetos POCO ou até mesmo gráficos de objetos inteiros.
GetValue
O exemplo a seguir demonstra o método de extensão GetValue<T>:
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;

public class Program


{
public static IConfiguration Configuration { get; set; }

public static void Main(string[] args = null)


{
var dict = new Dictionary<string, string>
{
{"Profile:MachineName", "Rick"},
{"App:MainWindow:Height", "11"},
{"App:MainWindow:Width", "11"},
{"App:MainWindow:Top", "11"},
{"App:MainWindow:Left", "11"}
};

var builder = new ConfigurationBuilder();


builder.AddInMemoryCollection(dict);

Configuration = builder.Build();

Console.WriteLine($"Hello {Configuration["Profile:MachineName"]}");

// Show GetValue overload and set the default value to 80


// Requires NuGet package "Microsoft.Extensions.Configuration.Binder"
var left = Configuration.GetValue<int>("App:MainWindow:Left", 80);
Console.WriteLine($"Left {left}");

var window = new MyWindow();


Configuration.GetSection("App:MainWindow").Bind(window);
Console.WriteLine($"Left {window.Left}");
Console.WriteLine();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}

public class MyWindow


{
public int Height { get; set; }
public int Width { get; set; }
public int Top { get; set; }
public int Left { get; set; }
}

O método GetValue<T> do ConfigurationBinder permite especificar um valor padrão (80 no exemplo).


GetValue<T> é para cenários simples e não se associa a seções inteiras. GetValue<T> obtém valores escalares de
GetSection(key).Value convertidos em um tipo específico.

Associar a um gráfico de objeto


Cada objeto em uma classe pode ser associado recursivamente. Considere a classe AppSettings a seguir:
public class AppSettings
{
public Window Window { get; set; }
public Connection Connection { get; set; }
public Profile Profile { get; set; }
}

public class Window


{
public int Height { get; set; }
public int Width { get; set; }
}

public class Connection


{
public string Value { get; set; }
}

public class Profile


{
public string Machine { get; set; }
}

O exemplo a seguir associa à classe AppSettings :

using System;
using System.IO;
using Microsoft.Extensions.Configuration;

public class Program


{
public static void Main(string[] args = null)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");

var config = builder.Build();

var appConfig = new AppSettings();


config.GetSection("App").Bind(appConfig);

Console.WriteLine($"Height {appConfig.Window.Height}");
Console.WriteLine();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}

O ASP.NET Core 1.1 e superior pode usar Get<T> , que trabalha com seções inteiras. O Get<T> pode ser mais
conveniente do que usar Bind . O código a seguir mostra como usar o Get<T> com o exemplo acima:

var appConfig = config.GetSection("App").Get<AppSettings>();

Usando o seguinte arquivo appsettings.json:


{
"App": {
"Profile": {
"Machine": "Rick"
},
"Connection": {
"Value": "connectionstring"
},
"Window": {
"Height": "11",
"Width": "11"
}
}
}

O programa exibe Height 11 .


O código a seguir pode ser usado para o teste de unidade da configuração:

[Fact]
public void CanBindObjectTree()
{
var dict = new Dictionary<string, string>
{
{"App:Profile:Machine", "Rick"},
{"App:Connection:Value", "connectionstring"},
{"App:Window:Height", "11"},
{"App:Window:Width", "11"}
};
var builder = new ConfigurationBuilder();
builder.AddInMemoryCollection(dict);
var config = builder.Build();

var settings = new AppSettings();


config.GetSection("App").Bind(settings);

Assert.Equal("Rick", settings.Profile.Machine);
Assert.Equal(11, settings.Window.Height);
Assert.Equal(11, settings.Window.Width);
Assert.Equal("connectionstring", settings.Connection.Value);
}

Criar um provedor personalizado do Entity Framework


Nesta seção, é criado um provedor de configuração básico, que lê os pares nome-valor de um banco de dados,
usando o EF.
Defina uma entidade ConfigurationValue para armazenar valores de configuração no banco de dados:

public class ConfigurationValue


{
public string Id { get; set; }
public string Value { get; set; }
}

Adicione um ConfigurationContext para armazenar e acessar os valores configurados:


public class ConfigurationContext : DbContext
{
public ConfigurationContext(DbContextOptions options) : base(options)
{
}

public DbSet<ConfigurationValue> Values { get; set; }


}

Crie uma classe que implementa IConfigurationSource:

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace CustomConfigurationProvider
{
public class EFConfigSource : IConfigurationSource
{
private readonly Action<DbContextOptionsBuilder> _optionsAction;

public EFConfigSource(Action<DbContextOptionsBuilder> optionsAction)


{
_optionsAction = optionsAction;
}

public IConfigurationProvider Build(IConfigurationBuilder builder)


{
return new EFConfigProvider(_optionsAction);
}
}
}

Crie o provedor de configuração personalizado através da herança do ConfigurationProvider. O provedor de


configuração inicializa o banco de dados quando ele está vazio:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace CustomConfigurationProvider
{
public class EFConfigProvider : ConfigurationProvider
{
public EFConfigProvider(Action<DbContextOptionsBuilder> optionsAction)
{
OptionsAction = optionsAction;
}

Action<DbContextOptionsBuilder> OptionsAction { get; }

// Load config data from EF DB.


public override void Load()
{
var builder = new DbContextOptionsBuilder<ConfigurationContext>();
OptionsAction(builder);

using (var dbContext = new ConfigurationContext(builder.Options))


{
dbContext.Database.EnsureCreated();
Data = !dbContext.Values.Any()
? CreateAndSaveDefaultValues(dbContext)
: dbContext.Values.ToDictionary(c => c.Id, c => c.Value);
}
}

private static IDictionary<string, string> CreateAndSaveDefaultValues(


ConfigurationContext dbContext)
{
var configValues = new Dictionary<string, string>
{
{ "key1", "value_from_ef_1" },
{ "key2", "value_from_ef_2" }
};
dbContext.Values.AddRange(configValues
.Select(kvp => new ConfigurationValue { Id = kvp.Key, Value = kvp.Value })
.ToArray());
dbContext.SaveChanges();
return configValues;
}
}
}

Os valores realçados do banco de dados ("value_from_ef_1" e "value_from_ef_2") são exibidos quando o exemplo é
executado.
Um método de extensão EFConfigSource para adicionar a fonte de configuração pode ser usado:
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace CustomConfigurationProvider
{
public static class EntityFrameworkExtensions
{
public static IConfigurationBuilder AddEntityFrameworkConfig(
this IConfigurationBuilder builder, Action<DbContextOptionsBuilder> setup)
{
return builder.Add(new EFConfigSource(setup));
}
}
}

O código a seguir mostra como usar o EFConfigProvider personalizado:

using System;
using System.IO;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using CustomConfigurationProvider;

public static class Program


{
public static void Main()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");

var connectionStringConfig = builder.Build();

var config = new ConfigurationBuilder()


.SetBasePath(Directory.GetCurrentDirectory())
// Add "appsettings.json" to bootstrap EF config.
.AddJsonFile("appsettings.json")
// Add the EF configuration provider, which will override any
// config made with the JSON provider.
.AddEntityFrameworkConfig(options =>
options.UseSqlServer(connectionStringConfig.GetConnectionString(
"DefaultConnection"))
)
.Build();

Console.WriteLine("key1={0}", config["key1"]);
Console.WriteLine("key2={0}", config["key2"]);
Console.WriteLine("key3={0}", config["key3"]);
Console.WriteLine();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}

Observe que o exemplo adiciona o EFConfigProvider personalizado depois do provedor JSON, de maneira que
todas as configurações do banco de dados substituam as configurações do arquivo appsettings.json.
Usando o seguinte arquivo appsettings.json:
{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=CustomConfigurationProvider;Trusted_Connection=True;MultipleActiveResultSets=t
rue"
},
"key1": "value_from_json_1",
"key2": "value_from_json_2",
"key3": "value_from_json_3"
}

É exibida a saída a seguir:

key1=value_from_ef_1
key2=value_from_ef_2
key3=value_from_json_3

Provedor de configuração CommandLine


O Provedor de configuração CommandLine recebe pares chave-valor de argumento de linha de comando para a
configuração em tempo de execução.
Exibir ou baixar o exemplo de configuração do CommandLine
Configurar e usar o provedor de configuração CommandLine
Configuração básica
ASP.NET Core 2.x
ASP.NET Core 1.x
Para ativar a configuração de linha de comando, chame o método de extensão AddCommandLine em uma instância
do ConfigurationBuilder:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;

public class Program


{
public static IConfiguration Configuration { get; set; }

public static void Main(string[] args = null)


{
var dict = new Dictionary<string, string>
{
{"Profile:MachineName", "MairaPC"},
{"App:MainWindow:Left", "1980"}
};

var builder = new ConfigurationBuilder();

builder.AddInMemoryCollection(dict)
.AddCommandLine(args);

Configuration = builder.Build();

Console.WriteLine($"MachineName: {Configuration["Profile:MachineName"]}");
Console.WriteLine($"Left: {Configuration["App:MainWindow:Left"]}");
Console.WriteLine();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}

Ao executar o código, a seguinte saída será exibida:

MachineName: MairaPC
Left: 1980

Passar pares chave-valor de argumento na linha de comando altera os valores de Profile:MachineName e


App:MainWindow:Left :

dotnet run Profile:MachineName=BartPC App:MainWindow:Left=1979

A janela do console exibe:

MachineName: BartPC
Left: 1979

Para substituir a configuração fornecida por outros provedores de configuração pela configuração de linha de
comando, chame AddCommandLine por último em ConfigurationBuilder :

var config = new ConfigurationBuilder()


.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.AddCommandLine(args)
.Build();
Arguments
Os argumentos passados na linha de comando devem estar em conformidade com um dos dois formatos
mostrados na tabela a seguir:

FORMATO DE ARGUMENTO EXEMPLO

Argumento único: um par chave-valor separado por um sinal key1=value


de igual ( = )

Sequência de dois argumentos: um par chave-valor separado /key1 value1


por um espaço

Argumento único
O valor deve seguir um sinal de igual ( = ). O valor pode ser nulo (por exemplo, mykey= ).
A chave pode ter um prefixo.

PREFIXO DA CHAVE EXEMPLO

Nenhum prefixo key1=value1

Traço único ( - )† -key2=value2

Dois traços ( -- ) --key3=value3

Barra ( / ) /key4=value4

& #8224;Uma chave com um prefixo de traço único ( - ) deve ser fornecida em mapeamentos de comutador, como
descrito abaixo.
Comando de exemplo:

dotnet run key1=value1 -key2=value2 --key3=value3 /key4=value4

Observação: se -key2 não estiver presente nos mapeamentos de comutador fornecidos para o provedor de
configuração, uma FormatException será gerada.
Sequência de dois argumentos
O valor não pode ser nulo e deve seguir a chave separada por um espaço.
A chave deve ter um prefixo.

PREFIXO DA CHAVE EXEMPLO

Traço único ( - )† -key1 value1

Dois traços ( -- ) --key2 value2

Barra ( / ) /key3 value3

& #8224;Uma chave com um prefixo de traço único ( - ) deve ser fornecida em mapeamentos de comutador, como
descrito abaixo.
Comando de exemplo:

dotnet run -key1 value1 --key2 value2 /key3 value3

Observação: se -key1 não estiver presente nos mapeamentos de comutador fornecidos para o provedor de
configuração, uma FormatException será gerada.
Chaves duplicadas
Se chaves duplicadas forem fornecidas, o último par chave-valor será usado.
Mapeamentos de comutador
Ao criar manualmente a configuração com ConfigurationBuilder , um dicionário de mapeamentos de comutador
pode ser adicionado ao método AddCommandLine . Os mapeamentos de comutador permitem fornecer a lógica de
substituição do nome da chave.
Ao ser usado, o dicionário de mapeamentos de comutador é verificado para oferecer uma chave que corresponda à
chave fornecida por um argumento de linha de comando. Se a chave de linha de comando for encontrada no
dicionário, o valor do dicionário (a substituição da chave) será passado de volta para definir a configuração. Um
mapeamento de comutador é necessário para qualquer chave de linha de comando prefixada com um traço único (
- ).

Regras de chave do dicionário de mapeamentos de comutador:


Os comutadores devem começar com um traço ( - ) ou traço duplo ( -- ).
O dicionário de mapeamentos de comutador chave não deve conter chaves duplicadas.
No exemplo a seguir, o método GetSwitchMappings permite que os argumentos de linha de comando usem um
prefixo de chave de traço único ( - ) e evitem prefixos de subchaves à esquerda.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;

public class Program


{
public static IConfiguration Configuration { get; set; }

public static Dictionary<string, string> GetSwitchMappings(


IReadOnlyDictionary<string, string> configurationStrings)
{
return configurationStrings.Select(item =>
new KeyValuePair<string, string>(
"-" + item.Key.Substring(item.Key.LastIndexOf(':') + 1),
item.Key))
.ToDictionary(
item => item.Key, item => item.Value);
}

public static void Main(string[] args = null)


{
var dict = new Dictionary<string, string>
{
{"Profile:MachineName", "RickPC"},
{"App:MainWindow:Left", "1980"}
};

var builder = new ConfigurationBuilder();

builder.AddInMemoryCollection(dict)
.AddCommandLine(args, GetSwitchMappings(dict));

Configuration = builder.Build();

Console.WriteLine($"MachineName: {Configuration["Profile:MachineName"]}");
Console.WriteLine($"Left: {Configuration["App:MainWindow:Left"]}");
Console.WriteLine();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}

Sem fornecer argumentos de linha de comando, o dicionário fornecido para AddInMemoryCollection definirá os
valores de configuração. Execute o aplicativo com o seguinte comando:

dotnet run

A janela do console exibe:

MachineName: RickPC
Left: 1980

Use o seguinte para passar parâmetros de configuração:

dotnet run /Profile:MachineName=DahliaPC /App:MainWindow:Left=1984

A janela do console exibe:


MachineName: DahliaPC
Left: 1984

Depois que o dicionário de mapeamentos de comutador for criado, ele conterá os dados mostrados na tabela a
seguir:

CHAVE VALOR

-MachineName Profile:MachineName

-Left App:MainWindow:Left

Para demonstrar a troca de chaves usando o dicionário, execute o seguinte comando:

dotnet run -MachineName=ChadPC -Left=1988

As chaves da linha de comando são trocadas. A janela de console exibe os valores de configuração de
Profile:MachineName e App:MainWindow:Left :

MachineName: ChadPC
Left: 1988

Arquivo web.config
Um arquivo web.config é necessário quando você hospeda o aplicativo em IIS ou IIS Express. As configurações no
web.config habilitam o Módulo ASP.NET Core para iniciar o aplicativo e definir outras configurações e módulos do
IIS. Se o arquivo web.config não estiver presente e o arquivo de projeto inclui
<Project Sdk="Microsoft.NET.Sdk.Web"> , a publicação do projeto criará um arquivo web.config na saída publicada (a
pasta publish). Para obter mais informações, consulte Hospedar o ASP.NET Core no Windows com o IIS.

Acessar a configuração durante a inicialização


Para acessar a configuração em ConfigureServices ou Configure durante a inicialização, consulte os exemplos no
tópico Inicialização do aplicativo.

Acessar a configuração em uma página do Razor ou exibição do MVC


Para acessar definições de configuração em uma página do Razor ou uma exibição do MVC, adicione usando
diretiva (referência de C#: usando diretiva) para o namespace Microsoft.Extensions.Configuration e injete
IConfiguration na página ou na exibição.
Em uma página do Razor:
@page
@model IndexModel

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

<!DOCTYPE html>
<html lang="en">
<head>
<title>Index Page</title>
</head>
<body>
<h1>Access configuration in a Razor Pages page</h1>
<p>Configuration[&quot;key&quot;]: @Configuration["key"]</p>
</body>
</html>

Em uma exibição do MVC:

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

<!DOCTYPE html>
<html lang="en">
<head>
<title>Index View</title>
</head>
<body>
<h1>Access configuration in an MVC view</h1>
<p>Configuration[&quot;key&quot;]: @Configuration["key"]</p>
</body>
</html>

Observações adicionais
A DI (injeção de dependência) não é configurada até que ConfigureServices seja invocado.
O sistema de configuração não tem reconhecimento de DI.
O IConfiguration tem duas especializações:
IConfigurationRoot Usado para o nó raiz. Pode disparar um recarregamento.
IConfigurationSection Representa uma seção de valores de configuração. O métodos GetSection e
GetChildren retornam um IConfigurationSection .
Use IConfigurationRoot ao recarregar a configuração ou para ter acesso a cada provedor. Nenhuma
dessas situações são comuns.

Recursos adicionais
Opções
Trabalhar com vários ambientes
Armazenamento seguro dos segredos do aplicativo no desenvolvimento
Hospedagem no ASP.NET Core
Injeção de dependência
Provedor de configuração do Azure Key Vault
Configuração no ASP.NET Core
04/05/2018 • 26 min to read • Edit Online

Por Rick Anderson, Mark Michaelis, Steve Smith, Daniel Roth e Luke Latham
A API de configuração fornece uma maneira de configurar um aplicativo Web do ASP.NET Core com
base em uma lista de pares nome-valor. A configuração é lida em tempo de execução por meio de
várias fontes. Os pares de nome-valor podem ser agrupados em uma hierarquia de vários níveis.
Há provedores de configuração para:
Formatos de arquivo (INI, JSON e XML ).
Argumentos de linha de comando.
Variáveis de ambiente.
Objetos do .NET na memória.
O armazenamento do Secret Manager não criptografado.
Um repositório de usuário criptografado, como o Azure Key Vault.
Provedores personalizados (instalados ou criados).
Cada valor de configuração é mapeado para uma chave de cadeia de caracteres. Há suporte interno de
associação para desserializar configurações em um objeto POCO personalizado (uma classe simples de
.NET com propriedades).
O padrão de opções usa classes de opções para representar grupos de configurações relacionadas.
Para obter mais informações sobre como usar o padrão de opções, consulte o tópico Opções.
Exibir ou baixar código de exemplo (como baixar)

Configuração de JSON
O aplicativo de console a seguir usa o provedor de configuração JSON:
using System;
using System.IO;
// Requires NuGet package
// Microsoft.Extensions.Configuration.Json
using Microsoft.Extensions.Configuration;

public class Program


{
public static IConfiguration Configuration { get; set; }

public static void Main(string[] args = null)


{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");

Configuration = builder.Build();

Console.WriteLine($"option1 = {Configuration["Option1"]}");
Console.WriteLine($"option2 = {Configuration["option2"]}");
Console.WriteLine(
$"suboption1 = {Configuration["subsection:suboption1"]}");
Console.WriteLine();

Console.WriteLine("Wizards:");
Console.Write($"{Configuration["wizards:0:Name"]}, ");
Console.WriteLine($"age {Configuration["wizards:0:Age"]}");
Console.Write($"{Configuration["wizards:1:Name"]}, ");
Console.WriteLine($"age {Configuration["wizards:1:Age"]}");
Console.WriteLine();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}

O aplicativo lê e exibe as seguintes definições de configuração:

{
"option1": "value1_from_json",
"option2": 2,

"subsection": {
"suboption1": "subvalue1_from_json"
},
"wizards": [
{
"Name": "Gandalf",
"Age": "1000"
},
{
"Name": "Harry",
"Age": "17"
}
]
}

A configuração consiste em uma lista hierárquica de pares nome-valor na qual os nós são separados
por dois pontos ( : ). Para recuperar um valor, acesse o indexador Configuration com a chave do item
correspondente:
Console.WriteLine(
$"suboption1 = {Configuration["subsection:suboption1"]}");

Para trabalhar com matrizes em fontes de configuração formatadas em JSON, use um índice de matriz
como parte da cadeia de caracteres separada por dois pontos. O exemplo a seguir obtém o nome do
primeiro item na matriz wizards anterior:

Console.Write($"{Configuration["wizards:0:Name"]}");
// Output: Gandalf

Os pares nome-valor gravados nos provedores de Configuração internos não são mantidos. No
entanto, um provedor personalizado que salva valores pode ser criado. Consulte provedor de
configuração personalizado.
O exemplo anterior usa o indexador de configuração para ler valores. Para acessar a configuração fora
de Startup , use o padrão de opções. Para obter mais informações, consulte o tópico Opções.

Configuração XML
Para trabalhar com matrizes em fontes de configuração formatadas em XML, forneça um índice name
para cada elemento. Use o índice para acessar os valores:

<wizards>
<wizard name="Gandalf">
<age>1000</age>
</wizard>
<wizard name="Harry">
<age>17</age>
</wizard>
</wizards>

Console.Write($"{Configuration["wizard:Harry:age"]}");
// Output: 17

Configuração por ambiente


É comum ter configurações diferentes para ambientes diferentes, por exemplo, desenvolvimento, teste
e produção. O método de extensão CreateDefaultBuilder em um aplicativo do ASP.NET Core 2.x (ou
usando AddJsonFile e AddEnvironmentVariables diretamente em um aplicativo do ASP.NET Core 1.x)
adiciona provedores de configuração para ler arquivos JSON e fontes de configuração do sistema:
appsettings.json
appsettings.<EnvironmentName>.json
Variáveis de ambiente
Aplicativos do ASP.NET Core 1.x precisam chamar AddJsonFile e AddEnvironmentVariables.
Consulte AddJsonFile para uma explicação sobre os parâmetros. reloadOnChange só é compatível no
ASP.NET Core 1.1 ou posterior.
As fontes de configuração são lidas na ordem em que são especificadas. No código anterior, as
variáveis de ambiente são lidas por último. Os valores de configuração definidos por meio do ambiente
substituem aqueles definidos nos dois provedores anteriores.
Considere o arquivo appsettings.Staging.json a seguir:

{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"System": "Information",
"Microsoft": "Information"
}
},
"MyConfig": "My Config Value for staging."
}

Quando o ambiente está configurado como Staging , o seguinte método Configure lê o valor de
MyConfig :

public void Configure(IApplicationBuilder app, IHostingEnvironment env)


{
var myConfig = Configuration["MyConfig"];
// use myConfig
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}

if (env.IsProduction() || env.IsStaging())
{
app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}

O ambiente normalmente é definido como Development , Staging ou Production . Para obter mais
informações, veja Trabalhar com vários ambientes.
Considerações de configuração:
IOptionsSnapshot pode recarregar dados de configuração quando é alterado.
Chaves de configuração não diferenciam maiúsculas de minúsculas.
Nunca armazene senhas ou outros dados confidenciais no código do provedor de configuração ou
nos arquivos de configuração de texto sem formatação. Não use segredos de produção em
ambientes de teste ou de desenvolvimento. Especifique segredos fora do projeto para que eles não
sejam acidentalmente comprometidos com um repositório de código-fonte. Saiba mais sobre como
trabalhar com vários ambientes e gerenciar armazenamento seguro de segredos de aplicativo no
desenvolvimento.
Para saber valores de configuração hierárquica especificados nas variáveis de ambiente, dois-pontos
( : ) pode não funcionar em todas as plataformas. Sublinhado duplo ( __ ) é compatível com todas
as plataformas.
Ao interagir com a configuração de API, dois-pontos ( : ) funciona em todas as plataformas.

Provedor na memória e associação a uma classe POCO


O exemplo a seguir mostra como usar o provedor na memória e associar a uma classe:
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;

public class Program


{
public static IConfiguration Configuration { get; set; }

public static void Main(string[] args = null)


{
var dict = new Dictionary<string, string>
{
{"Profile:MachineName", "Rick"},
{"App:MainWindow:Height", "11"},
{"App:MainWindow:Width", "11"},
{"App:MainWindow:Top", "11"},
{"App:MainWindow:Left", "11"}
};

var builder = new ConfigurationBuilder();


builder.AddInMemoryCollection(dict);

Configuration = builder.Build();

Console.WriteLine($"Hello {Configuration["Profile:MachineName"]}");

var window = new MyWindow();


// Bind requrires NuGet package
// Microsoft.Extensions.Configuration.Binder
Configuration.GetSection("App:MainWindow").Bind(window);
Console.WriteLine($"Left {window.Left}");
Console.WriteLine();

Console.WriteLine("Press any key...");


Console.ReadKey();
}
}

public class MyWindow


{
public int Height { get; set; }
public int Width { get; set; }
public int Top { get; set; }
public int Left { get; set; }
}

Os valores de configuração são retornados como cadeias de caracteres, mas a associação permite a
construção de objetos. A associação permite recuperar objetos POCO ou até mesmo gráficos de
objetos inteiros.
GetValue
O exemplo a seguir demonstra o método de extensão GetValue<T>:
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;

public class Program


{
public static IConfiguration Configuration { get; set; }

public static void Main(string[] args = null)


{
var dict = new Dictionary<string, string>
{
{"Profile:MachineName", "Rick"},
{"App:MainWindow:Height", "11"},
{"App:MainWindow:Width", "11"},
{"App:MainWindow:Top", "11"},
{"App:MainWindow:Left", "11"}
};

var builder = new ConfigurationBuilder();


builder.AddInMemoryCollection(dict);

Configuration = builder.Build();

Console.WriteLine($"Hello {Configuration["Profile:MachineName"]}");

// Show GetValue overload and set the default value to 80


// Requires NuGet package "Microsoft.Extensions.Configuration.Binder"
var left = Configuration.GetValue<int>("App:MainWindow:Left", 80);
Console.WriteLine($"Left {left}");

var window = new MyWindow();


Configuration.GetSection("App:MainWindow").Bind(window);
Console.WriteLine($"Left {window.Left}");
Console.WriteLine();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}

public class MyWindow


{
public int Height { get; set; }
public int Width { get; set; }
public int Top { get; set; }
public int Left { get; set; }
}

O método GetValue<T> do ConfigurationBinder permite especificar um valor padrão (80 no exemplo).


GetValue<T> é para cenários simples e não se associa a seções inteiras. GetValue<T> obtém valores
escalares de GetSection(key).Value convertidos em um tipo específico.

Associar a um gráfico de objeto


Cada objeto em uma classe pode ser associado recursivamente. Considere a classe AppSettings a
seguir:
public class AppSettings
{
public Window Window { get; set; }
public Connection Connection { get; set; }
public Profile Profile { get; set; }
}

public class Window


{
public int Height { get; set; }
public int Width { get; set; }
}

public class Connection


{
public string Value { get; set; }
}

public class Profile


{
public string Machine { get; set; }
}

O exemplo a seguir associa à classe AppSettings :

using System;
using System.IO;
using Microsoft.Extensions.Configuration;

public class Program


{
public static void Main(string[] args = null)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");

var config = builder.Build();

var appConfig = new AppSettings();


config.GetSection("App").Bind(appConfig);

Console.WriteLine($"Height {appConfig.Window.Height}");
Console.WriteLine();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}

O ASP.NET Core 1.1 e superior pode usar Get<T> , que trabalha com seções inteiras. O Get<T> pode
ser mais conveniente do que usar Bind . O código a seguir mostra como usar o Get<T> com o
exemplo acima:

var appConfig = config.GetSection("App").Get<AppSettings>();

Usando o seguinte arquivo appsettings.json:


{
"App": {
"Profile": {
"Machine": "Rick"
},
"Connection": {
"Value": "connectionstring"
},
"Window": {
"Height": "11",
"Width": "11"
}
}
}

O programa exibe Height 11 .


O código a seguir pode ser usado para o teste de unidade da configuração:

[Fact]
public void CanBindObjectTree()
{
var dict = new Dictionary<string, string>
{
{"App:Profile:Machine", "Rick"},
{"App:Connection:Value", "connectionstring"},
{"App:Window:Height", "11"},
{"App:Window:Width", "11"}
};
var builder = new ConfigurationBuilder();
builder.AddInMemoryCollection(dict);
var config = builder.Build();

var settings = new AppSettings();


config.GetSection("App").Bind(settings);

Assert.Equal("Rick", settings.Profile.Machine);
Assert.Equal(11, settings.Window.Height);
Assert.Equal(11, settings.Window.Width);
Assert.Equal("connectionstring", settings.Connection.Value);
}

Criar um provedor personalizado do Entity Framework


Nesta seção, é criado um provedor de configuração básico, que lê os pares nome-valor de um banco de
dados, usando o EF.
Defina uma entidade ConfigurationValue para armazenar valores de configuração no banco de dados:

public class ConfigurationValue


{
public string Id { get; set; }
public string Value { get; set; }
}

Adicione um ConfigurationContext para armazenar e acessar os valores configurados:


public class ConfigurationContext : DbContext
{
public ConfigurationContext(DbContextOptions options) : base(options)
{
}

public DbSet<ConfigurationValue> Values { get; set; }


}

Crie uma classe que implementa IConfigurationSource:

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace CustomConfigurationProvider
{
public class EFConfigSource : IConfigurationSource
{
private readonly Action<DbContextOptionsBuilder> _optionsAction;

public EFConfigSource(Action<DbContextOptionsBuilder> optionsAction)


{
_optionsAction = optionsAction;
}

public IConfigurationProvider Build(IConfigurationBuilder builder)


{
return new EFConfigProvider(_optionsAction);
}
}
}

Crie o provedor de configuração personalizado através da herança do ConfigurationProvider. O


provedor de configuração inicializa o banco de dados quando ele está vazio:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace CustomConfigurationProvider
{
public class EFConfigProvider : ConfigurationProvider
{
public EFConfigProvider(Action<DbContextOptionsBuilder> optionsAction)
{
OptionsAction = optionsAction;
}

Action<DbContextOptionsBuilder> OptionsAction { get; }

// Load config data from EF DB.


public override void Load()
{
var builder = new DbContextOptionsBuilder<ConfigurationContext>();
OptionsAction(builder);

using (var dbContext = new ConfigurationContext(builder.Options))


{
dbContext.Database.EnsureCreated();
Data = !dbContext.Values.Any()
? CreateAndSaveDefaultValues(dbContext)
: dbContext.Values.ToDictionary(c => c.Id, c => c.Value);
}
}

private static IDictionary<string, string> CreateAndSaveDefaultValues(


ConfigurationContext dbContext)
{
var configValues = new Dictionary<string, string>
{
{ "key1", "value_from_ef_1" },
{ "key2", "value_from_ef_2" }
};
dbContext.Values.AddRange(configValues
.Select(kvp => new ConfigurationValue { Id = kvp.Key, Value = kvp.Value })
.ToArray());
dbContext.SaveChanges();
return configValues;
}
}
}

Os valores realçados do banco de dados ("value_from_ef_1" e "value_from_ef_2") são exibidos quando


o exemplo é executado.
Um método de extensão EFConfigSource para adicionar a fonte de configuração pode ser usado:
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace CustomConfigurationProvider
{
public static class EntityFrameworkExtensions
{
public static IConfigurationBuilder AddEntityFrameworkConfig(
this IConfigurationBuilder builder, Action<DbContextOptionsBuilder> setup)
{
return builder.Add(new EFConfigSource(setup));
}
}
}

O código a seguir mostra como usar o EFConfigProvider personalizado:

using System;
using System.IO;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using CustomConfigurationProvider;

public static class Program


{
public static void Main()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");

var connectionStringConfig = builder.Build();

var config = new ConfigurationBuilder()


.SetBasePath(Directory.GetCurrentDirectory())
// Add "appsettings.json" to bootstrap EF config.
.AddJsonFile("appsettings.json")
// Add the EF configuration provider, which will override any
// config made with the JSON provider.
.AddEntityFrameworkConfig(options =>
options.UseSqlServer(connectionStringConfig.GetConnectionString(
"DefaultConnection"))
)
.Build();

Console.WriteLine("key1={0}", config["key1"]);
Console.WriteLine("key2={0}", config["key2"]);
Console.WriteLine("key3={0}", config["key3"]);
Console.WriteLine();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}

Observe que o exemplo adiciona o EFConfigProvider personalizado depois do provedor JSON, de


maneira que todas as configurações do banco de dados substituam as configurações do arquivo
appsettings.json.
Usando o seguinte arquivo appsettings.json:
{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=CustomConfigurationProvider;Trusted_Connection=True;MultipleActive
ResultSets=true"
},
"key1": "value_from_json_1",
"key2": "value_from_json_2",
"key3": "value_from_json_3"
}

É exibida a saída a seguir:

key1=value_from_ef_1
key2=value_from_ef_2
key3=value_from_json_3

Provedor de configuração CommandLine


O Provedor de configuração CommandLine recebe pares chave-valor de argumento de linha de
comando para a configuração em tempo de execução.
Exibir ou baixar o exemplo de configuração do CommandLine
Configurar e usar o provedor de configuração CommandLine
Configuração básica
ASP.NET Core 2.x
ASP.NET Core 1.x
Para ativar a configuração de linha de comando, chame o método de extensão AddCommandLine em uma
instância do ConfigurationBuilder:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;

public class Program


{
public static IConfiguration Configuration { get; set; }

public static void Main(string[] args = null)


{
var dict = new Dictionary<string, string>
{
{"Profile:MachineName", "MairaPC"},
{"App:MainWindow:Left", "1980"}
};

var builder = new ConfigurationBuilder();

builder.AddInMemoryCollection(dict)
.AddCommandLine(args);

Configuration = builder.Build();

Console.WriteLine($"MachineName: {Configuration["Profile:MachineName"]}");
Console.WriteLine($"Left: {Configuration["App:MainWindow:Left"]}");
Console.WriteLine();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}

Ao executar o código, a seguinte saída será exibida:

MachineName: MairaPC
Left: 1980

Passar pares chave-valor de argumento na linha de comando altera os valores de Profile:MachineName


e App:MainWindow:Left :

dotnet run Profile:MachineName=BartPC App:MainWindow:Left=1979

A janela do console exibe:

MachineName: BartPC
Left: 1979

Para substituir a configuração fornecida por outros provedores de configuração pela configuração de
linha de comando, chame AddCommandLine por último em ConfigurationBuilder :

var config = new ConfigurationBuilder()


.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.AddCommandLine(args)
.Build();
Arguments
Os argumentos passados na linha de comando devem estar em conformidade com um dos dois
formatos mostrados na tabela a seguir:

FORMATO DE ARGUMENTO EXEMPLO

Argumento único: um par chave-valor separado por um key1=value


sinal de igual ( = )

Sequência de dois argumentos: um par chave-valor /key1 value1


separado por um espaço

Argumento único
O valor deve seguir um sinal de igual ( = ). O valor pode ser nulo (por exemplo, mykey= ).
A chave pode ter um prefixo.

PREFIXO DA CHAVE EXEMPLO

Nenhum prefixo key1=value1

Traço único ( - )† -key2=value2

Dois traços ( -- ) --key3=value3

Barra ( / ) /key4=value4

& #8224;Uma chave com um prefixo de traço único ( - ) deve ser fornecida em mapeamentos de
comutador, como descrito abaixo.
Comando de exemplo:

dotnet run key1=value1 -key2=value2 --key3=value3 /key4=value4

Observação: se -key2 não estiver presente nos mapeamentos de comutador fornecidos para o
provedor de configuração, uma FormatException será gerada.
Sequência de dois argumentos
O valor não pode ser nulo e deve seguir a chave separada por um espaço.
A chave deve ter um prefixo.

PREFIXO DA CHAVE EXEMPLO

Traço único ( - )† -key1 value1

Dois traços ( -- ) --key2 value2

Barra ( / ) /key3 value3

& #8224;Uma chave com um prefixo de traço único ( - ) deve ser fornecida em mapeamentos de
comutador, como descrito abaixo.
Comando de exemplo:

dotnet run -key1 value1 --key2 value2 /key3 value3

Observação: se -key1 não estiver presente nos mapeamentos de comutador fornecidos para o
provedor de configuração, uma FormatException será gerada.
Chaves duplicadas
Se chaves duplicadas forem fornecidas, o último par chave-valor será usado.
Mapeamentos de comutador
Ao criar manualmente a configuração com ConfigurationBuilder , um dicionário de mapeamentos de
comutador pode ser adicionado ao método AddCommandLine . Os mapeamentos de comutador permitem
fornecer a lógica de substituição do nome da chave.
Ao ser usado, o dicionário de mapeamentos de comutador é verificado para oferecer uma chave que
corresponda à chave fornecida por um argumento de linha de comando. Se a chave de linha de
comando for encontrada no dicionário, o valor do dicionário (a substituição da chave) será passado de
volta para definir a configuração. Um mapeamento de comutador é necessário para qualquer chave de
linha de comando prefixada com um traço único ( - ).
Regras de chave do dicionário de mapeamentos de comutador:
Os comutadores devem começar com um traço ( - ) ou traço duplo ( -- ).
O dicionário de mapeamentos de comutador chave não deve conter chaves duplicadas.
No exemplo a seguir, o método GetSwitchMappings permite que os argumentos de linha de comando
usem um prefixo de chave de traço único ( - ) e evitem prefixos de subchaves à esquerda.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;

public class Program


{
public static IConfiguration Configuration { get; set; }

public static Dictionary<string, string> GetSwitchMappings(


IReadOnlyDictionary<string, string> configurationStrings)
{
return configurationStrings.Select(item =>
new KeyValuePair<string, string>(
"-" + item.Key.Substring(item.Key.LastIndexOf(':') + 1),
item.Key))
.ToDictionary(
item => item.Key, item => item.Value);
}

public static void Main(string[] args = null)


{
var dict = new Dictionary<string, string>
{
{"Profile:MachineName", "RickPC"},
{"App:MainWindow:Left", "1980"}
};

var builder = new ConfigurationBuilder();

builder.AddInMemoryCollection(dict)
.AddCommandLine(args, GetSwitchMappings(dict));

Configuration = builder.Build();

Console.WriteLine($"MachineName: {Configuration["Profile:MachineName"]}");
Console.WriteLine($"Left: {Configuration["App:MainWindow:Left"]}");
Console.WriteLine();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}

Sem fornecer argumentos de linha de comando, o dicionário fornecido para AddInMemoryCollection


definirá os valores de configuração. Execute o aplicativo com o seguinte comando:

dotnet run

A janela do console exibe:

MachineName: RickPC
Left: 1980

Use o seguinte para passar parâmetros de configuração:

dotnet run /Profile:MachineName=DahliaPC /App:MainWindow:Left=1984

A janela do console exibe:


MachineName: DahliaPC
Left: 1984

Depois que o dicionário de mapeamentos de comutador for criado, ele conterá os dados mostrados na
tabela a seguir:

CHAVE VALOR

-MachineName Profile:MachineName

-Left App:MainWindow:Left

Para demonstrar a troca de chaves usando o dicionário, execute o seguinte comando:

dotnet run -MachineName=ChadPC -Left=1988

As chaves da linha de comando são trocadas. A janela de console exibe os valores de configuração de
Profile:MachineName e App:MainWindow:Left :

MachineName: ChadPC
Left: 1988

Arquivo web.config
Um arquivo web.config é necessário quando você hospeda o aplicativo em IIS ou IIS Express. As
configurações no web.config habilitam o Módulo ASP.NET Core para iniciar o aplicativo e definir outras
configurações e módulos do IIS. Se o arquivo web.config não estiver presente e o arquivo de projeto
inclui <Project Sdk="Microsoft.NET.Sdk.Web"> , a publicação do projeto criará um arquivo web.config na
saída publicada (a pasta publish). Para obter mais informações, consulte Hospedar o ASP.NET Core no
Windows com o IIS.

Acessar a configuração durante a inicialização


Para acessar a configuração em ConfigureServices ou Configure durante a inicialização, consulte os
exemplos no tópico Inicialização do aplicativo.

Acessar a configuração em uma página do Razor ou exibição do


MVC
Para acessar definições de configuração em uma página do Razor ou uma exibição do MVC, adicione
usando diretiva (referência de C#: usando diretiva) para o namespace
Microsoft.Extensions.Configuration e injete IConfiguration na página ou na exibição.
Em uma página do Razor:
@page
@model IndexModel

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

<!DOCTYPE html>
<html lang="en">
<head>
<title>Index Page</title>
</head>
<body>
<h1>Access configuration in a Razor Pages page</h1>
<p>Configuration[&quot;key&quot;]: @Configuration["key"]</p>
</body>
</html>

Em uma exibição do MVC:

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

<!DOCTYPE html>
<html lang="en">
<head>
<title>Index View</title>
</head>
<body>
<h1>Access configuration in an MVC view</h1>
<p>Configuration[&quot;key&quot;]: @Configuration["key"]</p>
</body>
</html>

Observações adicionais
A DI (injeção de dependência) não é configurada até que ConfigureServices seja invocado.
O sistema de configuração não tem reconhecimento de DI.
O IConfiguration tem duas especializações:
IConfigurationRoot Usado para o nó raiz. Pode disparar um recarregamento.
IConfigurationSection Representa uma seção de valores de configuração. O métodos
GetSection e GetChildren retornam um IConfigurationSection .
Use IConfigurationRoot ao recarregar a configuração ou para ter acesso a cada provedor.
Nenhuma dessas situações são comuns.

Recursos adicionais
Opções
Trabalhar com vários ambientes
Armazenamento seguro dos segredos do aplicativo no desenvolvimento
Hospedagem no ASP.NET Core
Injeção de dependência
Provedor de configuração do Azure Key Vault
Padrão de opções no ASP.NET Core
08/02/2018 • 16 min to read • Edit Online

Por Luke Latham


O padrão de opções usa classes de opções para representar grupos de configurações relacionadas. Quando as
definições de configuração são isoladas por recurso em classes de opções separadas, o aplicativo segue dois
princípios importantes de engenharia de software:
O ISP (Princípio de Segregação da Interface): os recursos (classes) que dependem de definições de
configuração dependem apenas das definições de configuração usadas por eles.
Separação de Interesses: as configurações para diferentes partes do aplicativo não são dependentes nem
acopladas entre si.
Exiba ou baixe o código de exemplo (como baixar) Este artigo é mais fácil de ser acompanhado com o
aplicativo de exemplo.

Configuração de opções básicas


A configuração de opções básicas é demonstrada como o Exemplo #1 no aplicativo de exemplo.
Uma classe de opções deve ser não abstrata com um construtor público sem parâmetros. A classe a seguir,
MyOptions , tem duas propriedades, Option1 e Option2 . A configuração de valores padrão é opcional, mas o
construtor de classe no exemplo a seguir define o valor padrão de Option1 . Option2 tem um valor padrão
definido com a inicialização da propriedade diretamente (Models/MyOptions.cs):

public class MyOptions


{
public MyOptions()
{
// Set default value.
Option1 = "value1_from_ctor";
}

public string Option1 { get; set; }


public int Option2 { get; set; } = 5;
}

A classe MyOptions é adicionada ao contêiner de serviço com IConfigureOptions<TOptions> e associada à


configuração:

// Example #1: Basic options


// Register the ConfigurationBuilder instance which MyOptions binds against.
services.Configure<MyOptions>(Configuration);

O seguinte modelo de página usa a injeção de dependência de construtor com IOptions<TOptions> para
acessar as configurações (Pages/Index.cshtml.cs):

private readonly MyOptions _options;


public IndexModel(
IOptions<MyOptions> optionsAccessor,
IOptions<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptions<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.Value;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.Value;
_subOptions = subOptionsAccessor.Value;
_snapshotOptions = snapshotOptionsAccessor.Value;
_named_options_1 = namedOptionsAccessor.Get("named_options_1");
_named_options_2 = namedOptionsAccessor.Get("named_options_2");
}

// Example #1: Simple options


var option1 = _options.Option1;
var option2 = _options.Option2;
SimpleOptions = $"option1 = {option1}, option2 = {option2}";

O arquivo appsettings.json de exemplo especifica valores para option1 e option2 :

{
"option1": "value1_from_json",
"option2": -1,
"subsection": {
"suboption1": "subvalue1_from_json",
"suboption2": 200
}
}

Quando o aplicativo é executado, o método OnGet do modelo de página retorna uma cadeia de caracteres que
mostra os valores da classe de opção:

option1 = value1_from_json, option2 = -1

Configurar opções simples com um delegado


A configuração de opções simples com um delegado é demonstrada como o Exemplo #2 no aplicativo de
exemplo.
Use um delegado para definir valores de opções. O aplicativo de exemplo usa a classe
MyOptionsWithDelegateConfig ( Models/MyOptionsWithDelegateConfig.cs):

public class MyOptionsWithDelegateConfig


{
public MyOptionsWithDelegateConfig()
{
// Set default value.
Option1 = "value1_from_ctor";
}

public string Option1 { get; set; }


public int Option2 { get; set; } = 5;
}

No código a seguir, um segundo serviço IConfigureOptions<TOptions> é adicionado ao contêiner de serviço.


Ele usa um delegado para configurar a associação com MyOptionsWithDelegateConfig :

// Example #2: Options bound and configured by a delegate


services.Configure<MyOptionsWithDelegateConfig>(myOptions =>
{
myOptions.Option1 = "value1_configured_by_delegate";
myOptions.Option2 = 500;
});

Index.cshtml.cs:

private readonly MyOptionsWithDelegateConfig _optionsWithDelegateConfig;

public IndexModel(
IOptions<MyOptions> optionsAccessor,
IOptions<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptions<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.Value;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.Value;
_subOptions = subOptionsAccessor.Value;
_snapshotOptions = snapshotOptionsAccessor.Value;
_named_options_1 = namedOptionsAccessor.Get("named_options_1");
_named_options_2 = namedOptionsAccessor.Get("named_options_2");
}

// Example #2: Options configured by delegate


var delegate_config_option1 = _optionsWithDelegateConfig.Option1;
var delegate_config_option2 = _optionsWithDelegateConfig.Option2;
SimpleOptionsWithDelegateConfig =
$"delegate_option1 = {delegate_config_option1}, " +
$"delegate_option2 = {delegate_config_option2}";

Adicione vários provedores de configuração. Provedores de configuração estão disponíveis em pacotes


NuGet. Eles são aplicados para que sejam registrados.
Cada chamada a Configure<TOptions> adiciona um serviço IConfigureOptions<TOptions> ao contêiner de
serviço. No exemplo anterior, os valores Option1 e Option2 são especificados em appsettings.json, mas os
valores Option1 e Option2 são substituídos pelo delegado configurado.
Quando mais de um serviço de configuração é habilitado, a última fonte de configuração especificada vence e
define o valor de configuração. Quando o aplicativo é executado, o método OnGet do modelo de página
retorna uma cadeia de caracteres que mostra os valores da classe de opção:

delegate_option1 = value1_configured_by_delgate, delegate_option2 = 500

Configuração de subopções
A configuração de subopções básicas é demonstrada como o Exemplo #3 no aplicativo de exemplo.
Os aplicativos devem criar classes de opções que pertencem a grupos de recursos específicos (classes) no
aplicativo. Partes do aplicativo que exigem valores de configuração devem ter acesso apenas aos valores de
configuração usados por elas.
Ao associar opções à configuração, cada propriedade no tipo de opções é associada a uma chave de
configuração do formato property[:sub-property:] . Por exemplo, a propriedade MyOptions.Option1 é
associada à chave Option1 , que é lida da propriedade option1 em appsettings.json.
No código a seguir, um terceiro serviço IConfigureOptions<TOptions> é adicionado ao contêiner de serviço. Ele
associa MySubOptions à seção subsection do arquivo appsettings.json:

// Example #3: Sub-options


// Bind options using a sub-section of the appsettings.json file.
services.Configure<MySubOptions>(Configuration.GetSection("subsection"));

O método de extensão GetSection exige o pacote NuGet


Microsoft.Extensions.Options.ConfigurationExtensions. Se o aplicativo usar o metapacote
Microsoft.AspNetCore.All, o pacote será incluído automaticamente.
O arquivo appsettings.json de exemplo define um membro subsection com chaves para suboption1 e
suboption2 :

{
"option1": "value1_from_json",
"option2": -1,
"subsection": {
"suboption1": "subvalue1_from_json",
"suboption2": 200
}
}

A classe MySubOptions define propriedades, SubOption1 e SubOption2 , para armazenar os valores de


subopção (Models/MySubOptions.cs):

public class MySubOptions


{
public MySubOptions()
{
// Set default values.
SubOption1 = "value1_from_ctor";
SubOption2 = 5;
}

public string SubOption1 { get; set; }


public int SubOption2 { get; set; }
}

O método OnGet do modelo de página retorna uma cadeia de caracteres com os valores de subopção
(Pages/Index.cshtml.cs):

private readonly MySubOptions _subOptions;


public IndexModel(
IOptions<MyOptions> optionsAccessor,
IOptions<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptions<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.Value;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.Value;
_subOptions = subOptionsAccessor.Value;
_snapshotOptions = snapshotOptionsAccessor.Value;
_named_options_1 = namedOptionsAccessor.Get("named_options_1");
_named_options_2 = namedOptionsAccessor.Get("named_options_2");
}

// Example #3: Sub-options


var subOption1 = _subOptions.SubOption1;
var subOption2 = _subOptions.SubOption2;
SubOptions = $"subOption1 = {subOption1}, subOption2 = {subOption2}";

Quando o aplicativo é executado, o método OnGet retorna uma cadeia de caracteres que mostra os valores da
classe de subopção:

subOption1 = subvalue1_from_json, subOption2 = 200

Opções fornecidas por um modelo de exibição ou com a injeção de


exibição direta
As opções fornecidas por um modelo de exibição ou com a injeção de exibição direta são demonstradas como
o Exemplo #4 no aplicativo de exemplo.
As opções podem ser fornecidas em um modelo de exibição ou pela injeção de IOptions<TOptions>
diretamente em uma exibição (Pages/Index.cshtml.cs):

private readonly MyOptions _options;

public IndexModel(
IOptions<MyOptions> optionsAccessor,
IOptions<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptions<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.Value;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.Value;
_subOptions = subOptionsAccessor.Value;
_snapshotOptions = snapshotOptionsAccessor.Value;
_named_options_1 = namedOptionsAccessor.Get("named_options_1");
_named_options_2 = namedOptionsAccessor.Get("named_options_2");
}

// Example #4: Bind options directly to the page


MyOptions = _options;

Para a injeção direta, injete IOptions<MyOptions> com uma diretiva @inject :


@page
@model IndexModel
@using Microsoft.Extensions.Options
@using UsingOptionsSample.Models
@inject IOptions<MyOptions> OptionsAccessor
@{
ViewData["Title"] = "Using Options Sample";
}

<h1>@ViewData["Title"]</h1>

Quando o aplicativo é executado, os valores de opções são mostrados na página renderizada:

Recarregar dados de configuração com IOptionsSnapshot


O recarregamento de dados de configuração com IOptionsSnapshot é demonstrado no Exemplo #5 no
aplicativo de exemplo.
Exige o ASP.NET Core 1.1 ou posterior.
IOptionsSnapshot dá suporte a opções de recarregamento com sobrecarga mínima de processamento. No
ASP.NET Core 1.1, IOptionsSnapshot é um instantâneo de IOptionsMonitor<TOptions> e é atualizado
automaticamente sempre que o monitor dispara alterações com base na alteração da fonte de dados. No
ASP.NET Core 2.0 e posterior, as opções são calculadas uma vez por solicitação, quando acessadas e
armazenadas em cache durante o tempo de vida da solicitação.
O exemplo a seguir demonstra como um novo IOptionsSnapshot é criado após a alteração de appsettings.json
(Pages/Index.cshtml.cs). Várias solicitações ao servidor retornam valores de constante fornecidos pelo arquivo
appsettings.json, até que o arquivo seja alterado e a configuração seja recarregada.

private readonly MyOptions _snapshotOptions;


public IndexModel(
IOptions<MyOptions> optionsAccessor,
IOptions<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptions<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.Value;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.Value;
_subOptions = subOptionsAccessor.Value;
_snapshotOptions = snapshotOptionsAccessor.Value;
_named_options_1 = namedOptionsAccessor.Get("named_options_1");
_named_options_2 = namedOptionsAccessor.Get("named_options_2");
}

// Example #5: Snapshot options


var snapshotOption1 = _snapshotOptions.Option1;
var snapshotOption2 = _snapshotOptions.Option2;
SnapshotOptions =
$"snapshot option1 = {snapshotOption1}, " +
$"snapshot option2 = {snapshotOption2}";

A seguinte imagem mostra is valores option1 e option2 iniciais carregados do arquivo appsettings.json:

snapshot option1 = value1_from_json, snapshot option2 = -1

Altere os valores no arquivo appsettings.json para value1_from_json UPDATED e 200 . Salve o arquivo
appsettings.json. Atualize o navegador para ver se os valores de opções foram atualizados:

snapshot option1 = value1_from_json UPDATED, snapshot option2 = 200

Suporte de opções nomeadas com IConfigureNamedOptions


O suporte de opções nomeadas com IConfigureNamedOptions é demonstrado como o Exemplo #6 no
aplicativo de exemplo.
Exige o ASP.NET Core 2.0 ou posterior.
O suporte de opções nomeadas permite que o aplicativo faça a distinção entre as configurações de opções
nomeadas. No aplicativo de exemplo, as opções nomeadas são declaradas com o método
ConfigureNamedOptions<TOptions>.Configure:

// Example #6: Named options (named_options_1)


// Register the ConfigurationBuilder instance which MyOptions binds against.
// Specify that the options loaded from configuration are named
// "named_options_1".
services.Configure<MyOptions>("named_options_1", Configuration);

// Example #6: Named options (named_options_2)


// Specify that the options loaded from the MyOptions class are named
// "named_options_2".
// Use a delegate to configure option values.
services.Configure<MyOptions>("named_options_2", myOptions =>
{
myOptions.Option1 = "named_options_2_value1_from_action";
});
O aplicativo de exemplo acessa as opções nomeadas com IOptionsSnapshot<TOptions>.Get
(Pages/Index.cshtml.cs):

private readonly MyOptions _named_options_1;


private readonly MyOptions _named_options_2;

public IndexModel(
IOptions<MyOptions> optionsAccessor,
IOptions<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptions<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.Value;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.Value;
_subOptions = subOptionsAccessor.Value;
_snapshotOptions = snapshotOptionsAccessor.Value;
_named_options_1 = namedOptionsAccessor.Get("named_options_1");
_named_options_2 = namedOptionsAccessor.Get("named_options_2");
}

// Example #6: Named options


var named_options_1 =
$"named_options_1: option1 = {_named_options_1.Option1}, " +
$"option2 = {_named_options_1.Option2}";
var named_options_2 =
$"named_options_2: option1 = {_named_options_2.Option1}, " +
$"option2 = {_named_options_2.Option2}";
NamedOptions = $"{named_options_1} {named_options_2}";

Executando o aplicativo de exemplo, as opções nomeadas são retornadas:

named_options_1: option1 = value1_from_json, option2 = -1


named_options_2: option1 = named_options_2_value1_from_action, option2 = 5

Os valores named_options_1 são fornecidos pela configuração, que são carregados do arquivo appsettings.json.
Os valores named_options_2 são fornecidos pelo:
Delegado named_options_2 em ConfigureServices para Option1 .
Valor padrão para Option2 fornecido pela classe MyOptions .

Configure todas as instâncias de opções nomeadas com o método


OptionsServiceCollectionExtensions.ConfigureAll. O código a seguir configura Option1 para todas as
instâncias de configuração nomeadas com um valor comum. Adicione o seguinte código manualmente ao
método Configure :

services.ConfigureAll<MyOptions>(myOptions =>
{
myOptions.Option1 = "ConfigureAll replacement value";
});

A execução do aplicativo de exemplo após a adição do código produz o seguinte resultado:

named_options_1: option1 = ConfigureAll replacement value, option2 = -1


named_options_2: option1 = ConfigureAll replacement value, option2 = 5
OBSERVAÇÃO
No ASP.NET Core 2.0 e posterior, todas as opções são instâncias nomeadas. As instâncias IConfigureOption existentes
são tratadas como sendo direcionadas à instância Options.DefaultName , que é string.Empty .
IConfigureNamedOptions também implementa IConfigureOptions . A implementação padrão de
IOptionsFactory<TOptions> (fonte de referência) tem uma lógica para usar cada um de forma adequada. A opção
nomeada null é usada para direcionar todas as instâncias nomeadas, em vez de uma instância nomeada específica
(ConfigureAll e PostConfigureAll usa essa convenção).

IPostConfigureOptions
Exige o ASP.NET Core 2.0 ou posterior.
Defina a pós-configuração com IPostConfigureOptions<TOptions>. A pós-configuração é executada depois
que ocorre toda a configuração de IConfigureOptions<TOptions>:

services.PostConfigure<MyOptions>(myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});

PostConfigure<TOptions> está disponível para pós-configurar opções nomeadas:

services.PostConfigure<MyOptions>("named_options_1", myOptions =>


{
myOptions.Option1 = "post_configured_option1_value";
});

Use PostConfigureAll<TOptions> para pós-configurar todas as instâncias de configuração nomeadas:

services.PostConfigureAll<MyOptions>("named_options_1", myOptions =>


{
myOptions.Option1 = "post_configured_option1_value";
});

Alocador de opções, monitoramento e cache


IOptionsMonitor é usado para notificações quando instâncias TOptions são alteradas. IOptionsMonitor dá
suporte a opções recarregáveis, notificações de alteração e IPostConfigureOptions .
IOptionsFactory<TOptions> (ASP.NET Core 2.0 ou posterior) é responsável por criar novas instâncias de
opções. Ele tem um único método Create. A implementação padrão usa todos os IConfigureOptions e
IPostConfigureOptions registrados e executa toda a configuração primeiro, seguido da pós-configuração. Ela
faz distinção entre IConfigureNamedOptions e IConfigureOptions e chama apenas a interface apropriada.
IOptionsMonitorCache<TOptions> (ASP.NET Core 2.0 ou posterior) é usado por IOptionsMonitor para
armazenar instâncias TOptions em cache. O IOptionsMonitorCache invalida as instâncias de opções no
monitor, de modo que o valor seja recalculado (TryRemove). Os valores também podem ser manualmente
inseridos com TryAdd. O método Clear é usado quando todas as instâncias nomeadas devem ser recriadas
sob demanda.

Acessando opções durante a inicialização


IOptions pode ser usado em Configure , pois os serviços são criados antes da execução do método
Configure . Se um provedor de serviços for criado em ConfigureServices para acessar as opções, ele não
conterá nenhuma configuração de opções fornecida após sua criação. Portanto, pode haver um estado
inconsistente de opções devido à ordenação dos registros de serviço.
Como as opções geralmente são carregadas da configuração, a configuração pode ser usada na inicialização
em Configure e ConfigureServices . Para obter exemplos de como usar a configuração durante a inicialização,
consulte o tópico Inicialização do aplicativo.

Consulte também
Configuração
Registro em log no ASP.NET Core
04/05/2018 • 42 min to read • Edit Online

Por Steve Smith e Tom Dykstra


O ASP.NET Core dá suporte a uma API de registro em log que funciona com uma variedade de provedores
de logs. Os provedores internos permitem que você envie logs para um ou mais destinos e você pode se
conectar a uma estrutura de registros de terceiros. Este artigo mostra como usar a API de registro em log e
os provedores internos em seu código.
ASP.NET Core 2.x
ASP.NET Core 1.x
Exibir ou baixar código de exemplo (como baixar)

Como criar logs


Para criar logs, obtenha um objeto ILogger do contêiner de injeção de dependência:

public class TodoController : Controller


{
private readonly ITodoRepository _todoRepository;
private readonly ILogger _logger;

public TodoController(ITodoRepository todoRepository,


ILogger<TodoController> logger)
{
_todoRepository = todoRepository;
_logger = logger;
}

Em seguida, chame métodos de registro em log nesse objeto de agente:

public IActionResult GetById(string id)


{
_logger.LogInformation(LoggingEvents.GetItem, "Getting item {ID}", id);
var item = _todoRepository.Find(id);
if (item == null)
{
_logger.LogWarning(LoggingEvents.GetItemNotFound, "GetById({ID}) NOT FOUND", id);
return NotFound();
}
return new ObjectResult(item);
}

Este exemplo cria logs com a classe TodoController como a categoria. As categorias serão explicadas
posteriormente neste artigo.
O ASP.NET Core não fornece métodos de agente assíncronos porque o registro em log deve ser tão rápido
que não valeria a pena usar assíncronos. Se você estiver em uma situação em que isso não é verdadeiro,
considere alterar a maneira como você faz logs. Se seu armazenamento de dados é lento, grave as
mensagens de log em um repositório rápido primeiro e, depois, mova-as para um repositório lento. Por
exemplo, registre em uma fila de mensagens que seja lida e persistida em um armazenamento lento por
outro processo.
Como adicionar provedores
ASP.NET Core 2.x
ASP.NET Core 1.x
Um provedor de logs obtém as mensagens que você cria com um objeto ILogger e as exibe ou armazena.
Por exemplo, o provedor Console exibe as mensagens no console e o provedor do Serviço de Aplicativo do
Azure pode armazená-las no armazenamento de blobs do Azure.
Para usar um provedor, chame o método de extensão Add<ProviderName> do provedor no Program.cs:

public static void Main(string[] args)


{
var webHost = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange:
true);
config.AddEnvironmentVariables();
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
})
.UseStartup<Startup>()
.Build();

webHost.Run();
}

O modelo de projeto padrão permite o registro em log com o método CreateDefaultBuilder:

public static void Main(string[] args)


{
BuildWebHost(args).Run();
}

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


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

Você encontrará informações sobre cada provedor de log interno e links para provedores de log de terceiros
mais adiante no artigo.

Exemplo de saída de registro em log


Com o código de exemplo mostrado na seção anterior, você verá logs no console ao executar através da linha
de comando. Aqui está um exemplo da saída do console:
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:5000/api/todo/0
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Executing action method TodoApi.Controllers.TodoController.GetById (TodoApi) with arguments (0) -
ModelState is Valid
info: TodoApi.Controllers.TodoController[1002]
Getting item 0
warn: TodoApi.Controllers.TodoController[4000]
GetById(0) NOT FOUND
info: Microsoft.AspNetCore.Mvc.StatusCodeResult[1]
Executing HttpStatusCodeResult, setting HTTP status code 404
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action TodoApi.Controllers.TodoController.GetById (TodoApi) in 42.9286ms
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 148.889ms 404

Esses logs foram criados acessando http://localhost:5000/api/todo/0 , que dispara a execução das duas
chamadas ILogger mostradas na seção anterior.
Aqui está um exemplo de como os mesmos logs aparecem na janela Depuração quando você executa o
aplicativo de exemplo no Visual Studio:

Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET


http://localhost:53104/api/todo/0
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executing action method
TodoApi.Controllers.TodoController.GetById (TodoApi) with arguments (0) - ModelState is Valid
TodoApi.Controllers.TodoController:Information: Getting item 0
TodoApi.Controllers.TodoController:Warning: GetById(0) NOT FOUND
Microsoft.AspNetCore.Mvc.StatusCodeResult:Information: Executing HttpStatusCodeResult, setting HTTP
status code 404
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action
TodoApi.Controllers.TodoController.GetById (TodoApi) in 152.5657ms
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 316.3195ms 404

Os logs criados pelas chamadas ILogger mostradas na seção anterior começam com
"TodoApi.Controllers.TodoController". Os logs que começam com categorias "Microsoft" são do ASP.NET
Core. O próprio ASP.NET Core e o código do aplicativo estão usando a mesma API de registro em log e os
mesmos provedores de log.
O restante deste artigo explica alguns detalhes e opções para registro em log.

Pacotes NuGet
As interfaces ILogger e ILoggerFactory estão em Microsoft.Extensions.Logging.Abstractions e as
implementações padrão para elas estão em Microsoft.Extensions.Logging.

Categoria de log
Um categoria é incluída com cada log que você cria. Você especifica a categoria ao criar um objeto ILogger .
A categoria pode ser qualquer cadeia de caracteres, mas uma convenção é usar o nome totalmente
qualificado da classe da qual os logs são gravados. Por exemplo: "TodoApi.Controllers.TodoController".
Você pode especificar a categoria como uma cadeia de caracteres ou usar um método de extensão que deriva
a categoria do tipo. Para especificar a categoria como uma cadeia de caracteres, chame CreateLogger em uma
instância de ILoggerFactory , conforme mostrado abaixo.
public class TodoController : Controller
{
private readonly ITodoRepository _todoRepository;
private readonly ILogger _logger;

public TodoController(ITodoRepository todoRepository,


ILoggerFactory logger)
{
_todoRepository = todoRepository;
_logger = logger.CreateLogger("TodoApi.Controllers.TodoController");
}

Na maioria das vezes será mais fácil usar ILogger<T> , conforme mostrado no exemplo a seguir.

public class TodoController : Controller


{
private readonly ITodoRepository _todoRepository;
private readonly ILogger _logger;

public TodoController(ITodoRepository todoRepository,


ILogger<TodoController> logger)
{
_todoRepository = todoRepository;
_logger = logger;
}

Isso é equivalente a chamar CreateLogger com o nome de tipo totalmente qualificado de T .

Nível de log
Sempre que você grava um log, você especifica o LogLevel. O nível de log indica o grau de gravidade ou
importância. Por exemplo, você pode gravar um log Information quando um método é finalizado
normalmente, um log Warning quando um método retorna um código de retorno 404 e um log Error ao
capturar uma exceção inesperada.
No exemplo de código a seguir, os nomes dos métodos (por exemplo, LogWarning ) especificam o nível de log.
O primeiro parâmetro é a ID de evento de log. O segundo parâmetro é um modelo de mensagem com
espaços reservados para valores de argumento fornecidos pelos parâmetros de método restantes. Os
parâmetros de método serão explicados com mais detalhes posteriormente neste artigo.

public IActionResult GetById(string id)


{
_logger.LogInformation(LoggingEvents.GetItem, "Getting item {ID}", id);
var item = _todoRepository.Find(id);
if (item == null)
{
_logger.LogWarning(LoggingEvents.GetItemNotFound, "GetById({ID}) NOT FOUND", id);
return NotFound();
}
return new ObjectResult(item);
}

Os métodos de log que incluem o nível no nome do método são métodos de extensão para ILogger. Nos
bastidores, esses métodos chamam um método Log que recebe um parâmetro LogLevel . Você pode chamar
o método Log diretamente em vez de um desses métodos de extensão, mas a sintaxe é relativamente
complicada. Para obter mais informações, consulte a interface ILogger e o código-fonte de extensões de
agente.
O ASP.NET Core define os seguintes níveis de log, ordenados aqui da menor para a maior gravidade.
Trace = 0
Para obter informações valiosas somente para um desenvolvedor que esteja depurando um problema.
Essas mensagens podem conter dados confidenciais de aplicativos e, portanto, não devem ser
habilitadas em um ambiente de produção. Desabilitado por padrão. Exemplo:
Credentials: {"User":"someuser", "Password":"P@ssword"}

Debug = 1
Para informações que tenham utilidade de curto prazo durante o desenvolvimento e a depuração.
Exemplo: Entering method Configure with flag set to true. Você normalmente não habilitaria logs de
nível Debug em produção, a menos que estivesse solucionando problemas, devido ao alto volume de
logs.
Information = 2
Para rastrear o fluxo geral do aplicativo. Esses logs normalmente têm algum valor a longo prazo.
Exemplo: Request received for path /api/todo
Warning = 3
Para eventos anormais ou inesperados no fluxo de aplicativo. Eles podem incluir erros ou outras
condições que não fazem com que o aplicativo pare, mas que talvez precisem ser investigados.
Exceções manipuladas são um local comum para usar o nível de log Warning . Exemplo:
FileNotFoundException for file quotes.txt.

Error = 4
Para erros e exceções que não podem ser manipulados. Essas mensagens indicam uma falha na
atividade ou na operação atual (como a solicitação HTTP atual) e não uma falha em todo o aplicativo.
Mensagem de log de exemplo: Cannot insert record due to duplicate key violation.
Critical = 5
Para falhas que exigem atenção imediata. Exemplos: cenários de perda de dados, espaço em disco
insuficiente.
Você pode usar o nível de log para controlar a quantidade de saída de log que é gravada em uma mídia de
armazenamento específica ou em uma janela de exibição. Por exemplo, em produção, você pode desejar que
todos os logs de nível Information e inferior vão para um armazenamento de dados de volume e que todos
os logs de nível Warning e superior vão para um armazenamento de dados de valor. Durante o
desenvolvimento, você normalmente poderia enviar os logs de gravidade Warning ou mais alta para o
console. Então, quando precisar solucionar problemas, você poderá adicionar o nível Debug . A seção
Filtragem de log mais adiante neste artigo explicará como controlar os níveis de log que um provedor
manipula.
A estrutura do ASP.NET Core grava logs de nível Debug para eventos de estrutura. Os exemplos de log
anteriores neste artigo excluíram logs abaixo do nível Information , portanto, os logs de nível Debug não
foram mostrados. Aqui está um exemplo de logs do console, caso você execute o aplicativo de exemplo
configurado para mostrar logs de nível Debug e superiores para o provedor de console.
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:62555/api/todo/0
dbug: Microsoft.AspNetCore.Routing.Tree.TreeRouter[1]
Request successfully matched the route with name 'GetTodo' and template 'api/Todo/{id}'.
dbug: Microsoft.AspNetCore.Mvc.Internal.ActionSelector[2]
Action 'TodoApi.Controllers.TodoController.Update (TodoApi)' with id '089d59b6-92ec-472d-b552-
cc613dfd625d' did not match the constraint 'Microsoft.AspNetCore.Mvc.Internal.HttpMethodActionConstraint'
dbug: Microsoft.AspNetCore.Mvc.Internal.ActionSelector[2]
Action 'TodoApi.Controllers.TodoController.Delete (TodoApi)' with id 'f3476abe-4bd9-4ad3-9261-
3ead09607366' did not match the constraint 'Microsoft.AspNetCore.Mvc.Internal.HttpMethodActionConstraint'
dbug: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Executing action TodoApi.Controllers.TodoController.GetById (TodoApi)
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Executing action method TodoApi.Controllers.TodoController.GetById (TodoApi) with arguments (0) -
ModelState is Valid
info: TodoApi.Controllers.TodoController[1002]
Getting item 0
warn: TodoApi.Controllers.TodoController[4000]
GetById(0) NOT FOUND
dbug: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action method TodoApi.Controllers.TodoController.GetById (TodoApi), returned result
Microsoft.AspNetCore.Mvc.NotFoundResult.
info: Microsoft.AspNetCore.Mvc.StatusCodeResult[1]
Executing HttpStatusCodeResult, setting HTTP status code 404
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action TodoApi.Controllers.TodoController.GetById (TodoApi) in 0.8788ms
dbug: Microsoft.AspNetCore.Server.Kestrel[9]
Connection id "0HL6L7NEFF2QD" completed keep alive response.
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 2.7286ms 404

ID de evento de log
Sempre que você grava um log, você pode especificar uma ID de evento. O aplicativo de exemplo faz isso
usando uma classe LoggingEvents definida localmente:

public IActionResult GetById(string id)


{
_logger.LogInformation(LoggingEvents.GetItem, "Getting item {ID}", id);
var item = _todoRepository.Find(id);
if (item == null)
{
_logger.LogWarning(LoggingEvents.GetItemNotFound, "GetById({ID}) NOT FOUND", id);
return NotFound();
}
return new ObjectResult(item);
}

public class LoggingEvents


{
public const int GenerateItems = 1000;
public const int ListItems = 1001;
public const int GetItem = 1002;
public const int InsertItem = 1003;
public const int UpdateItem = 1004;
public const int DeleteItem = 1005;

public const int GetItemNotFound = 4000;


public const int UpdateItemNotFound = 4001;
}
Uma ID de evento é um valor inteiro que pode ser usado para associar um conjunto de eventos registrados.
Por exemplo, um log para adicionar um item a um carrinho de compras pode ser ter a ID de evento 1000 e
um log para concluir uma compra pode ter a ID de evento 1001.
Na saída do registro em log, a ID do evento pode ser armazenada em um campo ou incluída na mensagem
de texto, dependendo do provedor. O provedor Depuração não mostra as IDs de evento, mas o provedor do
console sim, entre colchetes depois da categoria:

info: TodoApi.Controllers.TodoController[1002]
Getting item invalidid
warn: TodoApi.Controllers.TodoController[4000]
GetById(invalidid) NOT FOUND

Modelo de mensagem de log


Sempre que você grava uma mensagem de log, você fornece um modelo de mensagem. O modelo de
mensagem pode ser uma cadeia de caracteres ou pode conter espaços reservados nomeados, nos quais
valores de argumento serão colocados. O modelo não é uma cadeia de caracteres de formatação e os espaços
reservados devem ser nomeados, não numerados.

public IActionResult GetById(string id)


{
_logger.LogInformation(LoggingEvents.GetItem, "Getting item {ID}", id);
var item = _todoRepository.Find(id);
if (item == null)
{
_logger.LogWarning(LoggingEvents.GetItemNotFound, "GetById({ID}) NOT FOUND", id);
return NotFound();
}
return new ObjectResult(item);
}

A ordem dos espaços reservados e não de seus nomes, determina quais parâmetros serão usados para
fornecer seus valores. Se você tiver o seguinte código:

string p1 = "parm1";
string p2 = "parm2";
_logger.LogInformation("Parameter values: {p2}, {p1}", p1, p2);

A mensagem de log resultante terá esta aparência:

Parameter values: parm1, parm2

A estrutura de registros realiza a formatação de mensagens dessa maneira para possibilitar que os
provedores de log implementem o registro em log semântico, também conhecido como registro em log
estruturado. Como os próprios argumentos são passados para o sistema de registro em log, não apenas o
modelo de mensagem formatada, mas os provedores de log, também poderão armazenar os valores de
parâmetro como campos, além do modelo de mensagem. Se você estiver direcionando a saída de log para o
Armazenamento de Tabelas do Azure e sua chamada de método do agente tiver esta aparência:

_logger.LogInformation("Getting item {ID} at {RequestTime}", id, DateTime.Now);

Cada entidade da Tabela do Azure poderá ter propriedades ID e RequestTime , o que simplificará as
consultas nos dados de log. Você poderá encontrar todos os logs em um determinado intervalo de
RequestTime sem a necessidade de analisar o tempo limite da mensagem de texto.

Exceções de registro em log


Os métodos de agente têm sobrecargas que permitem que você passe uma exceção, como no exemplo a
seguir:

catch (Exception ex)


{
_logger.LogWarning(LoggingEvents.GetItemNotFound, ex, "GetById({ID}) NOT FOUND", id);
return NotFound();
}
return new ObjectResult(item);

Provedores diferentes manipulam as informações de exceção de maneiras diferentes. Aqui está um exemplo
da saída do provedor Depuração do código mostrado acima.

TodoApi.Controllers.TodoController:Warning: GetById(036dd898-fb01-47e8-9a65-f92eb73cf924) NOT FOUND

System.Exception: Item not found exception.


at TodoApi.Controllers.TodoController.GetById(String id) in
C:\logging\sample\src\TodoApi\Controllers\TodoController.cs:line 226

Filtragem de log
ASP.NET Core 2.x
ASP.NET Core 1.x
Você pode especificar um nível de log mínimo para um provedor e uma categoria específicos ou para todos
os provedores ou todas as categorias. Os logs abaixo do nível mínimo não serão passados para esse
provedor, para que não sejam exibidos ou armazenados.
Se quiser suprimir todos os logs, você poderá especificar LogLevel.None como o nível de log mínimo. O valor
inteiro de LogLevel.None é 6, que é maior do que LogLevel.Critical (5).
Criar regras de filtro na configuração
Os modelos de projeto criam código que chama CreateDefaultBuilder para configurar o registro em log para
os provedores Console e Depuração. O método CreateDefaultBuilder também configura o registro em log
para procurar a configuração em uma seção Logging , usando código semelhante ao seguinte:
public static void Main(string[] args)
{
var webHost = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange:
true);
config.AddEnvironmentVariables();
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
})
.UseStartup<Startup>()
.Build();

webHost.Run();
}

Os dados de configuração especificam níveis de log mínimo por provedor e por categoria, como no exemplo
a seguir:

{
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Information"
}
},
"Console": {
"LogLevel": {
"Microsoft.AspNetCore.Mvc.Razor.Internal": "Warning",
"Microsoft.AspNetCore.Mvc.Razor.Razor": "Debug",
"Microsoft.AspNetCore.Mvc.Razor": "Error",
"Default": "Information"
}
},
"LogLevel": {
"Default": "Debug"
}
}
}

Este JSON cria seis regras de filtro, uma para o provedor Depuração, quatro para o provedor Console e uma
que se aplica a todos os provedores. Você verá posteriormente como apenas uma dessas regras é escolhida
para cada provedor quando um objeto ILogger é criado.
Regras de filtro no código
Você pode registrar regras de filtro no código, conforme mostrado no exemplo a seguir:
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureLogging(logging =>
logging.AddFilter("System", LogLevel.Debug)
.AddFilter<DebugLoggerProvider>("Microsoft", LogLevel.Trace))
.Build();

O segundo AddFilter especifica o provedor Depuração usando seu nome de tipo. O primeiro AddFilter se
aplica a todos os provedores porque ele não especifica um tipo de provedor.
Como as regras de filtragem são aplicadas
Os dados de configuração e o código AddFilter , mostrados nos exemplos anteriores, criam as regras
mostradas na tabela a seguir. As primeiras seis vêm do exemplo de configuração e as últimas duas vêm do
exemplo de código.

CATEGORIAS QUE COMEÇAM


NÚMERO PROVIDER COM... NÍVEL DE LOG MÍNIMO

1 Depurar Todas as categorias Informações

2 Console Microsoft.AspNetCore.Mvc. Aviso


Razor.Internal

3 Console Microsoft.AspNetCore.Mvc. Depurar


Razor.Razor

4 Console Microsoft.AspNetCore.Mvc. Erro


Razor

5 Console Todas as categorias Informações

6 Todos os provedores Todas as categorias Depurar

7 Todos os provedores Sistema Depurar

8 Depurar Microsoft Rastrear

Quando você cria um objeto ILogger para usar para gravar logs, o objeto ILoggerFactory seleciona uma
única regra por provedor para aplicar a esse agente. Todas as mensagens gravadas pelo objeto ILogger são
filtradas com base nas regras selecionadas. A regra mais específica possível para cada par de categoria e
provedor é selecionada dentre as regras disponíveis.
O algoritmo a seguir é usado para cada provedor quando um ILogger é criado para uma determinada
categoria:
Selecione todas as regras que correspondem ao provedor ou seu alias. Se nenhuma for encontrada,
selecione todas as regras com um provedor vazio.
Do resultado da etapa anterior, selecione as regras com o prefixo de categoria de maior correspondência.
Se nenhuma for encontrada, selecione todas as regras que não especificam uma categoria.
Se várias regras forem selecionadas use a última.
Se nenhuma regra for selecionada, use MinimumLevel .

Por exemplo, suponha que você tem a lista anterior de regras e cria um objeto ILogger para a categoria
"Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine":
Para o provedor Depuração as regras 1, 6 e 8 se aplicam. A regra 8 é mais específica, portanto é a que será
selecionada.
Para o provedor Console as regras 3, 4, 5 e 6 se aplicam. A regra 3 é a mais específica.
Quando você cria logs com um ILogger para a categoria
"Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine", os logs de nível Trace e superiores vão para o
provedor Depuração e os logs de nível Debug e superiores vão para o provedor Console.
Aliases de provedor
Você pode usar o nome do tipo para especificar um provedor na configuração, mas cada provedor define um
alias menor que é mais fácil de usar. Para os provedores internos, use os seguintes aliases:
Console
Depurar
EventLog
AzureAppServices
TraceSource
EventSource
Nível mínimo padrão
Há uma configuração de nível mínimo que entra em vigor somente se nenhuma regra de código ou de
configuração se aplicar a um provedor e uma categoria determinados. O exemplo a seguir mostra como
definir o nível mínimo:

WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Warning))
.Build();

Se você não definir explicitamente o nível mínimo, o valor padrão será Information , o que significa que logs
Trace e Debug serão ignorados.

Funções de filtro
Você pode escrever código em uma função de filtro para aplicar regras de filtragem. Uma função de filtro é
invocada para todos os provedores e categorias que não têm regras atribuídas a eles por configuração ou
código. O código na função tem acesso ao tipo de provedor, à categoria e ao nível de log para decidir se uma
mensagem deve ser registrada. Por exemplo:

WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureLogging(logBuilder =>
{
logBuilder.AddFilter((provider, category, logLevel) =>
{
if (provider == "Microsoft.Extensions.Logging.Console.ConsoleLoggerProvider" &&
category == "TodoApi.Controllers.TodoController")
{
return false;
}
return true;
});
})
.Build();
Escopos de log
Você pode agrupar um conjunto de operações lógicas dentro de um escopo para anexar os mesmos dados a
cada log que é criado como parte desse conjunto. Por exemplo, é conveniente que cada log criado como parte
do processamento de uma transação inclua a ID da transação.
Um escopo é um tipo IDisposable retornado pelo método ILogger.BeginScope<TState> e que dura até que
seja descartado. Um escopo é usado pelo encapsulamento das chama de agente em um bloco using ,
conforme mostrado aqui:

public IActionResult GetById(string id)


{
TodoItem item;
using (_logger.BeginScope("Message attached to logs created in the using block"))
{
_logger.LogInformation(LoggingEvents.GetItem, "Getting item {ID}", id);
item = _todoRepository.Find(id);
if (item == null)
{
_logger.LogWarning(LoggingEvents.GetItemNotFound, "GetById({ID}) NOT FOUND", id);
return NotFound();
}
}
return new ObjectResult(item);
}

O código a seguir habilita os escopos para o provedor de console:


ASP.NET Core 2.x
ASP.NET Core 1.x
Em Program.cs:

.ConfigureLogging((hostingContext, logging) =>


{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole(options => options.IncludeScopes = true);
logging.AddDebug();
})

OBSERVAÇÃO
A configuração da opção de agente de console IncludeScopes é necessária para habilitar o registro em log baseado
em escopo. A configuração de IncludeScopes usando arquivos de configuração appsettings estará disponível com o
lançamento do ASP.NET Core 2.1.

Cada mensagem de log inclui as informações com escopo definido:

info: TodoApi.Controllers.TodoController[1002]
=> RequestId:0HKV9C49II9CK RequestPath:/api/todo/0 => TodoApi.Controllers.TodoController.GetById
(TodoApi) => Message attached to logs created in the using block
Getting item 0
warn: TodoApi.Controllers.TodoController[4000]
=> RequestId:0HKV9C49II9CK RequestPath:/api/todo/0 => TodoApi.Controllers.TodoController.GetById
(TodoApi) => Message attached to logs created in the using block
GetById(0) NOT FOUND
Provedores de log internos
O ASP.NET Core vem com os seguintes provedores:
Console
Depurar
EventSource
EventLog
TraceSource
Serviço de Aplicativo do Azure
O provedor de console
O pacote de provedor Microsoft.Extensions.Logging.Console envia a saída de log para o console.
ASP.NET Core 2.x
ASP.NET Core 1.x

logging.AddConsole()

O provedor Depuração
O pacote de provedor Microsoft.Extensions.Logging.Debug grava a saída de log usando a classe
System.Diagnostics.Debug (chamadas de método Debug.WriteLine ).
No Linux, esse provedor grava logs em /var/log/message.
ASP.NET Core 2.x
ASP.NET Core 1.x

logging.AddDebug()

O provedor EventSource
Para aplicativos que se destinam ao ASP.NET Core 1.1.0 ou superior, o pacote de provedor
Microsoft.Extensions.Logging.EventSource pode implementar o rastreamento de eventos. No Windows, ele
usa ETW. O provedor é multiplataforma, mas ainda não há ferramentas de coleta e exibição de eventos para
Linux ou macOS.
ASP.NET Core 2.x
ASP.NET Core 1.x

logging.AddEventSourceLogger()

Uma boa maneira de coletar e exibir logs é usar o utilitário PerfView. Há outras ferramentas para exibir os
logs do ETW, mas o PerfView proporciona a melhor experiência para trabalhar com os eventos de ETW
emitidos pelo ASP.NET.
Para configurar o PerfView para coletar eventos registrados por esse provedor, adicione a cadeia de
caracteres *Microsoft-Extensions-Logging à lista Provedores Adicionais. (Não se esqueça do asterisco no
início da cadeia de caracteres).
O provedor EventLog do Windows
O pacote de provedor Microsoft.Extensions.Logging.EventLog envia a saída de log para o Log de Eventos do
Windows.
ASP.NET Core 2.x
ASP.NET Core 1.x

logging.AddEventLog()

O provedor TraceSource
O pacote de provedor Microsoft.Extensions.Logging.TraceSource usa as bibliotecas e provedores de
System.Diagnostics.TraceSource.
ASP.NET Core 2.x
ASP.NET Core 1.x

logging.AddTraceSource(sourceSwitchName);

As sobrecargas de AddTraceSource permitem que você passe um comutador de fonte e um ouvinte de


rastreamento.
Para usar esse provedor, o aplicativo deve ser executado no .NET Framework (em vez do .NET Core). O
provedor permite rotear mensagens a uma variedade de ouvintes, como o TextWriterTraceListener usado no
aplicativo de exemplo.
O exemplo a seguir configura um provedor TraceSource que registra mensagens Warning e superiores na
janela de console.
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
loggerFactory
.AddDebug();

// add Trace Source logging


var testSwitch = new SourceSwitch("sourceSwitch", "Logging Sample");
testSwitch.Level = SourceLevels.Warning;
loggerFactory.AddTraceSource(testSwitch,
new TextWriterTraceListener(writer: Console.Out));

O provedor do Serviço de Aplicativo do Azure


O pacote de provedor Microsoft.Extensions.Logging.AzureAppServices grava logs em arquivos de texto no
sistema de arquivos de um aplicativo do Serviço de Aplicativo do Azure e no armazenamento de blobs em
uma conta de Armazenamento do Azure. O provedor está disponível somente para aplicativos que se
destinam ao ASP.NET Core 1.1.0 ou superior.
ASP.NET Core 2.x
ASP.NET Core 1.x
Se o destino for o .NET Core, você não precisará instalar o pacote de provedor ou chamar explicitamente
AddAzureWebAppDiagnostics . O provedor fica automaticamente disponível para o aplicativo quando você
implanta o aplicativo do Serviço de Aplicativo do Azure.
Se o destino for o .NET Framework, adicione o pacote de provedor ao seu projeto e invoque
AddAzureWebAppDiagnostics :

logging.AddAzureWebAppDiagnostics();

Ao implantar um aplicativo do Serviço de Aplicativo, seu aplicativo respeita as configurações na seção Logs
de Diagnóstico da página Serviço de Aplicativo do Portal do Azure. Ao alterar essas configurações, elas
entram em vigor imediatamente, sem exigir que você reinicie o aplicativo ou reimplante o código nele.
O local padrão para arquivos de log é na pasta D:\home\LogFiles\Application e o nome de arquivo padrão é
diagnostics-aaaammdd.txt. O limite padrão de tamanho do arquivo é 10 MB e o número padrão máximo de
arquivos mantidos é 2. O nome de blob padrão é {app -name}{timestamp }/aaaa/mm/dd/hh/{guid }-
applicationLog.txt. Para obter mais informações sobre o comportamento padrão, consulte
AzureAppServicesDiagnosticsSettings.
O provedor funciona somente quando o projeto é executado no ambiente do Azure. Ele não tem nenhum
efeito quando é executado localmente — ele não grava em arquivos locais ou no armazenamento de
desenvolvimento local para blobs.

Provedores de log de terceiros


Aqui estão algumas estruturas de registros de terceiros que funcionam com o ASP.NET Core:
elmah.io – provedor para o serviço Elmah.Io
JSNLog – registra as exceções de JavaScript e outros eventos do lado do cliente no registro do lado do
servidor.
Loggr – provedor para o serviço Loggr
NLog – provedor para a biblioteca NLog
Serilog – provedor para a biblioteca Serilog
Algumas estruturas de terceiros podem fazer o log semântico, também conhecido como registro em log
estruturado.
O uso de uma estrutura de terceiros é semelhante ao uso de um dos provedores internos: adicione um pacote
NuGet ao seu projeto e chame um método de extensão em ILoggerFactory . Para obter mais informações,
consulte a documentação de cada estrutura.

Fluxo de log do Azure


O fluxo de log do Azure permite que você exiba a atividade de log em tempo real:
Do servidor de aplicativos
Do servidor Web
De uma solicitação de rastreio com falha
Para configurar o fluxo de log do Azure:
Navegue até a página Logs de Diagnóstico da página do portal do seu aplicativo
Defina o Log de aplicativo (Sistema de Arquivos) como ativado.

Navegue até a página Fluxo de Log para exibir as mensagens de aplicativo. Elas são registradas pelo
aplicativo por meio da interface ILogger .
Consulte também
Registro em log de alto desempenho com LoggerMessage
Registro em log de alto desempenho com o
LoggerMessage no ASP.NET Core
08/02/2018 • 12 min to read • Edit Online

Por Luke Latham


Os recursos do LoggerMessage criam delegados armazenáveis em cache que exigem menos alocações de objeto e
sobrecarga de computação reduzida comparado aos métodos de extensão do agente, como LogInformation ,
LogDebug e LogError . Para cenários de registro em log de alto desempenho, use o padrão LoggerMessage .

LoggerMessage fornece as seguintes vantagens de desempenho em relação aos métodos de extensão do Agente:
Métodos de extensão do agente exigem tipos de valor de conversão boxing, como int , em object . O padrão
LoggerMessage evita a conversão boxing usando campos Action estáticos e métodos de extensão com
parâmetros fortemente tipados.
Os métodos de extensão do agente precisam analisar o modelo de mensagem (cadeia de caracteres de formato
nomeada) sempre que uma mensagem de log é gravada. LoggerMessage exige apenas a análise de um modelo
uma vez quando a mensagem é definida.
Exibir ou baixar código de exemplo (como baixar)
O aplicativo de exemplo demonstra recursos do LoggerMessage com um sistema básico de acompanhamento de
aspas. O aplicativo adiciona e exclui aspas usando um banco de dados em memória. Conforme ocorrem essas
operações, são geradas mensagens de log usando o padrão LoggerMessage .

LoggerMessage.Define
Define(LogLevel, EventId, String) cria um delegado Action para registrar uma mensagem em log. Sobrecargas de
Define permitem passar até seis parâmetros de tipo para uma cadeia de caracteres de formato nomeada
(modelo).

LoggerMessage.DefineScope
DefineScope(String) cria um delegado Func para definir um escopo de log. Sobrecargas de DefineScope
permitem passar até três parâmetros de tipo para uma cadeia de caracteres de formato nomeada (modelo).

Modelo de mensagem (cadeia de caracteres de formato nomeada)


A cadeia de caracteres fornecida para os métodos Define e DefineScope é um modelo e não uma cadeia de
caracteres interpolada. Os espaços reservados são preenchidos na ordem em que os tipos são especificados. Os
nomes do espaço reservado no modelo devem ser descritivos e consistentes em todos os modelos. Eles servem
como nomes de propriedade em dados de log estruturado. Recomendamos o uso da formatação Pascal Case para
nomes de espaço reservado. Por exemplo, {Count} , {FirstName} .

Implementando LoggerMessage.Define
Cada mensagem de log é uma Action mantida em um campo estático criado por LoggerMessage.Define . Por
exemplo, o aplicativo de exemplo cria um campo para descrever uma mensagem de log para uma solicitação GET
para a página de Índice (Internal/LoggerExtensions.cs):
private static readonly Action<ILogger, Exception> _indexPageRequested;

Para a Action , especifique:


O nível de log.
Um identificador de evento exclusivo (EventId) com o nome do método de extensão estático.
O modelo de mensagem (cadeia de caracteres de formato nomeada).
Uma solicitação para a página de Índice do aplicativo de exemplo define:
O nível de log como Information .
A ID do evento como 1 com o nome do método IndexPageRequested .
Modelo de mensagem (cadeia de caracteres de formato nomeada) como uma cadeia de caracteres.

_indexPageRequested = LoggerMessage.Define(
LogLevel.Information,
new EventId(1, nameof(IndexPageRequested)),
"GET request for Index page");

Repositórios de log estruturado podem usar o nome do evento quando recebem a ID do evento para enriquecer o
log. Por exemplo, Serilog usa o nome do evento.
A Action é invocada por meio de um método de extensão fortemente tipado. O método IndexPageRequested
registra uma mensagem para uma solicitação GET da página de Índice no aplicativo de exemplo:

public static void IndexPageRequested(this ILogger logger)


{
_indexPageRequested(logger, null);
}

IndexPageRequested é chamado no agente no método OnGetAsync em Pages/Index.cshtml.cs:

public async Task OnGetAsync()


{
_logger.IndexPageRequested();

Quotes = await _db.Quotes.AsNoTracking().ToListAsync();


}

Inspecione a saída do console do aplicativo:

info: LoggerMessageSample.Pages.IndexModel[1]
=> RequestId:0HL90M6E7PHK4:00000001 RequestPath:/ => /Index
GET request for Index page

Para passar parâmetros para uma mensagem de log, defina até seis tipos ao criar o campo estático. O aplicativo de
exemplo registra uma cadeia de caracteres em log ao adicionar aspas definindo um tipo string para o campo
Action :

private static readonly Action<ILogger, string, Exception> _quoteAdded;

O modelo de mensagem de log do delegado recebe seus valores de espaço reservado dos tipos fornecidos. O
aplicativo de exemplo define um delegado para adicionar aspas quando o parâmetro de aspas é uma string :
_quoteAdded = LoggerMessage.Define<string>(
LogLevel.Information,
new EventId(2, nameof(QuoteAdded)),
"Quote added (Quote = '{Quote}')");

O método de extensão estático para adicionar aspas, QuoteAdded , recebe o valor do argumento de aspas e passa-o
para o delegado Action :

public static void QuoteAdded(this ILogger logger, string quote)


{
_quoteAdded(logger, quote, null);
}

No modelo da página de Índice (Pages/Index.cshtml.cs), QuoteAdded é chamado para registrar a mensagem em


log:

public async Task<IActionResult> OnPostAddQuoteAsync()


{
_db.Quotes.Add(Quote);
await _db.SaveChangesAsync();

_logger.QuoteAdded(Quote.Text);

return RedirectToPage();
}

Inspecione a saída do console do aplicativo:

info: LoggerMessageSample.Pages.IndexModel[2]
=> RequestId:0HL90M6E7PHK5:0000000A RequestPath:/ => /Index
Quote added (Quote = 'You can avoid reality, but you cannot avoid the consequences of avoiding reality.
- Ayn Rand')

O aplicativo de exemplo implementa um padrão try – catch para a exclusão de aspas. Uma mensagem
informativa é registrada em log para uma operação de exclusão bem-sucedida. Uma mensagem de erro é
registrada em log para uma operação de exclusão quando uma exceção é gerada. A mensagem de log para a
operação de exclusão sem êxito inclui o rastreamento de pilha da exceção (Internal/LoggerExtensions.cs):

private static readonly Action<ILogger, string, int, Exception> _quoteDeleted;


private static readonly Action<ILogger, int, Exception> _quoteDeleteFailed;

_quoteDeleted = LoggerMessage.Define<string, int>(


LogLevel.Information,
new EventId(4, nameof(QuoteDeleted)),
"Quote deleted (Quote = '{Quote}' Id = {Id})");

_quoteDeleteFailed = LoggerMessage.Define<int>(
LogLevel.Error,
new EventId(5, nameof(QuoteDeleteFailed)),
"Quote delete failed (Id = {Id})");

Observe como a exceção é passada para o delegado em QuoteDeleteFailed :


public static void QuoteDeleted(this ILogger logger, string quote, int id)
{
_quoteDeleted(logger, quote, id, null);
}

public static void QuoteDeleteFailed(this ILogger logger, int id, Exception ex)
{
_quoteDeleteFailed(logger, id, ex);
}

No modelo da página de Índice, uma exclusão de aspas bem-sucedida chama o método QuoteDeleted no agente.
Quando as aspas não são encontradas para exclusão, uma ArgumentNullException é gerada. A exceção é
interceptada pela instrução try – catch e registrada em log com uma chamada ao método QuoteDeleteFailed no
agente no bloco catch (Pages/Index.cshtml.cs):

public async Task<IActionResult> OnPostDeleteQuoteAsync(int id)


{
var quote = await _db.Quotes.FindAsync(id);

// DO NOT use this approach in production code!


// You should check quote to see if it's null before removing
// it and saving changes to the database. A try-catch is used
// here for demonstration purposes of LoggerMessage features.
try
{
_db.Quotes.Remove(quote);
await _db.SaveChangesAsync();

_logger.QuoteDeleted(quote.Text, id);
}
catch (ArgumentNullException ex)
{
_logger.QuoteDeleteFailed(id, ex);
}

return RedirectToPage();
}

Quando as aspas forem excluídas com êxito, inspecione a saída do console do aplicativo:

info: LoggerMessageSample.Pages.IndexModel[4]
=> RequestId:0HL90M6E7PHK5:00000016 RequestPath:/ => /Index
Quote deleted (Quote = 'You can avoid reality, but you cannot avoid the consequences of avoiding
reality. - Ayn Rand' Id = 1)

Quando a exclusão de aspas falha, inspecione a saída do console do aplicativo. Observe que a exceção é incluída
na mensagem de log:

fail: LoggerMessageSample.Pages.IndexModel[5]
=> RequestId:0HL90M6E7PHK5:00000010 RequestPath:/ => /Index
Quote delete failed (Id = 999)
System.ArgumentNullException: Value cannot be null.
Parameter name: entity
at Microsoft.EntityFrameworkCore.Utilities.Check.NotNull[T](T value, String parameterName)
at Microsoft.EntityFrameworkCore.DbContext.Remove[TEntity](TEntity entity)
at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.Remove(TEntity entity)
at LoggerMessageSample.Pages.IndexModel.<OnPostDeleteQuoteAsync>d__14.MoveNext() in
<PATH>\sample\Pages\Index.cshtml.cs:line 87
Implementando LoggerMessage.DefineScope
Defina um escopo de log a ser aplicado a uma série de mensagens de log usando o método DefineScope(String).
O aplicativo de exemplo tem um botão Limpar Tudo para excluir todas as aspas no banco de dados. As aspas são
excluídas com a remoção das aspas individualmente, uma por vez. Sempre que aspas são excluídas, o método
QuoteDeleted é chamado no agente. Um escopo de log é adicionado a essas mensagens de log.

Habilite IncludeScopes nas opções do agente do console:

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


WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureLogging((hostingContext, logging) =>
{
// Setting options.IncludeScopes is required in ASP.NET Core 2.0
// apps. Setting IncludeScopes via appsettings configuration files
// is a feature that's planned for the ASP.NET Core 2.1 release.
// See: https://github.com/aspnet/Logging/pull/706
logging.AddConsole(options => options.IncludeScopes = true);
})
.Build();

A configuração de IncludeScopes é necessária em aplicativos ASP.NET Core 2.0 para habilitar os escopos de log. A
configuração de IncludeScopes por meio dos arquivos de configuração appsettings é um recurso que foi planejado
para a versão ASP.NET Core 2.1.
O aplicativo de exemplo limpa outros provedores e adiciona filtros para reduzir a saída de log. Isso facilita a
visualização das mensagens de log da amostra que demonstram os recursos de LoggerMessage .
Para criar um escopo de log, adicione um campo para conter um delegado Func para o escopo. O aplicativo de
exemplo cria um campo chamado _allQuotesDeletedScope (Internal/LoggerExtensions.cs):

private static Func<ILogger, int, IDisposable> _allQuotesDeletedScope;

Use DefineScope para criar o delegado. Até três tipos podem ser especificados para uso como argumentos de
modelo quando o delegado é invocado. O aplicativo de exemplo usa um modelo de mensagem que inclui o
número de aspas excluídas (um tipo int ):

_allQuotesDeletedScope = LoggerMessage.DefineScope<int>("All quotes deleted (Count = {Count})");

Forneça um método de extensão estático para a mensagem de log. Inclua os parâmetros de tipo para propriedades
nomeadas exibidos no modelo de mensagem. O aplicativo de exemplo usa uma count de aspas a ser excluída e
retorna _allQuotesDeletedScope :

public static IDisposable AllQuotesDeletedScope(this ILogger logger, int count)


{
return _allQuotesDeletedScope(logger, count);
}

O escopo encapsula as chamadas de extensão de log em um bloco using :


public async Task<IActionResult> OnPostDeleteAllQuotesAsync()
{
var quoteCount = await _db.Quotes.CountAsync();

using (_logger.AllQuotesDeletedScope(quoteCount))
{
foreach (Quote quote in _db.Quotes)
{
_db.Quotes.Remove(quote);

_logger.QuoteDeleted(quote.Text, quote.Id);
}
await _db.SaveChangesAsync();
}

return RedirectToPage();
}

Inspecione as mensagens de log na saída do console do aplicativo. O seguinte resultado mostra três aspas
excluídas com a mensagem de escopo de log incluída:

info: LoggerMessageSample.Pages.IndexModel[4]
=> RequestId:0HL90M6E7PHK5:0000002E RequestPath:/ => /Index => All quotes deleted (Count = 3)
Quote deleted (Quote = 'Quote 1' Id = 2)
info: LoggerMessageSample.Pages.IndexModel[4]
=> RequestId:0HL90M6E7PHK5:0000002E RequestPath:/ => /Index => All quotes deleted (Count = 3)
Quote deleted (Quote = 'Quote 2' Id = 3)
info: LoggerMessageSample.Pages.IndexModel[4]
=> RequestId:0HL90M6E7PHK5:0000002E RequestPath:/ => /Index => All quotes deleted (Count = 3)
Quote deleted (Quote = 'Quote 3' Id = 4)

Consulte também
Registro em log
Introdução ao tratamento de erro no ASP.NET Core
08/02/2018 • 9 min to read • Edit Online

Por Steve Smith e Tom Dykstra


Este artigo apresenta abordagens comuns para o tratamento de erros em aplicativos ASP.NET Core.
Exibir ou baixar código de exemplo (como baixar)

A página de exceção do desenvolvedor


Para configurar um aplicativo para exibir uma página que mostra informações detalhadas sobre exceções, instale
o pacote NuGet Microsoft.AspNetCore.Diagnostics e adicione uma linha ao método Configure na classe Startup:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)


{
loggerFactory.AddConsole();
env.EnvironmentName = EnvironmentName.Production;
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/error");
}

Coloque UseDeveloperExceptionPage antes de qualquer middleware no qual você deseja capturar exceções, como
app.UseMvc .

AVISO
Habilite a página de exceção do desenvolvedor somente quando o aplicativo estiver em execução no ambiente de
Desenvolvimento. Não é recomendável compartilhar informações de exceção detalhadas publicamente quando o
aplicativo é executado em produção. Saiba mais sobre como configurar ambientes.

Para ver a página de exceção do desenvolvedor, execute o aplicativo de exemplo com o ambiente definido como
Development e adicione ?throw=true à URL base do aplicativo. A página inclui várias guias com informações
sobre a exceção e a solicitação. A primeira guia inclui um rastreamento de pilha.
A próxima guia mostra os parâmetros da cadeia de caracteres de consulta, se houver.

Essa solicitação não tinha nenhum cookie, mas se tivesse, eles seriam exibidos na guia Cookies. Veja os
cabeçalhos que foram passados na última guia.
Configurando uma página de tratamento de exceção personalizada
É uma boa ideia configurar uma página de manipulador de exceção a ser usada quando o aplicativo não está
sendo executado no ambiente Development .

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)


{
loggerFactory.AddConsole();
env.EnvironmentName = EnvironmentName.Production;
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/error");
}

Em um aplicativo MVC, não decore o método de ação do manipulador de erro de forma explícita com atributos
de método HTTP, como HttpGet . O uso de verbos explícitos pode impedir que algumas solicitações sejam
recebidas pelo método.

[Route("/Error")]
public IActionResult Index()
{
// Handle error here
}
Configurando páginas de código de status
Por padrão, o aplicativo não fornecerá uma página de código de status detalhada para códigos de status HTTP
como 500 (Erro Interno do Servidor) ou 404 (Não Encontrado). Configure o StatusCodePagesMiddleware
adicionando uma linha ao método Configure :

app.UseStatusCodePages();

Por padrão, esse middleware adiciona manipuladores simples e somente texto a códigos de status comuns,
como 404:

O middleware dá suporte a vários métodos de extensão diferentes. Um usa uma expressão lambda e o outro
usa uma cadeia de caracteres de formato e um tipo de conteúdo.

app.UseStatusCodePages(async context =>


{
context.HttpContext.Response.ContentType = "text/plain";
await context.HttpContext.Response.WriteAsync(
"Status code page, status code: " +
context.HttpContext.Response.StatusCode);
});

app.UseStatusCodePages("text/plain", "Status code page, status code: {0}");

Também há métodos de extensão de redirecionamento. Um envia um código de status 302 para o cliente e o
outro retorna o código de status original para o cliente, mas também executa o manipulador para a URL de
redirecionamento.

app.UseStatusCodePagesWithRedirects("/error/{0}");

app.UseStatusCodePagesWithReExecute("/error/{0}");

Se precisar desabilitar páginas de código de status para determinadas solicitações, faça isso:

var statusCodePagesFeature = context.Features.Get<IStatusCodePagesFeature>();


if (statusCodePagesFeature != null)
{
statusCodePagesFeature.Enabled = false;
}

Código de tratamento de exceção


Código em páginas de tratamento de exceção pode gerar exceções. Geralmente, é uma boa ideia que páginas de
erro de produção sejam compostas por conteúdo puramente estático.
Além disso, esteja ciente de que, depois que os cabeçalhos de uma resposta forem enviados, você não poderá
alterar o código de status da resposta e nenhuma página de exceção ou manipulador poderá ser executado. A
resposta deve ser concluída ou a conexão será anulada.

Tratamento de exceções do servidor


Além da lógica de tratamento de exceção no aplicativo, o servidor que hospeda o aplicativo executa uma parte
do tratamento de exceção. Se o servidor capturar uma exceção antes de os cabeçalhos serem enviados, o
servidor enviará uma resposta 500 Erro Interno do Servidor sem corpo. Se o servidor capturar uma exceção
depois que os cabeçalhos forem enviados, o servidor fechará a conexão. As solicitações que não são
manipuladas pelo aplicativo são manipuladas pelo servidor. Qualquer exceção ocorrida é tratada pelo
tratamento de exceção do servidor. As páginas de erro personalizadas, o middleware de tratamento de exceção
ou os filtros configurados não afetam esse comportamento.

Tratamento de exceção na inicialização


Apenas a camada de hospedagem pode tratar exceções que ocorrem durante a inicialização do aplicativo.
Configure como o host se comporta em resposta a erros durante a inicialização usando captureStartupErrors e
a chave detailedErrors .
A hospedagem apenas poderá mostrar uma página de erro para um erro de inicialização capturado se o erro
ocorrer após a associação de endereço do host/porta. Se uma associação falha por algum motivo, a camada de
hospedagem registra uma exceção crítica em log, o processo dotnet falha e nenhuma página de erro é exibida.

Tratamento de erro do ASP.NET MVC


Os aplicativos MVC contêm algumas opções adicionais para o tratamento de erros, como configurar filtros de
exceção e executar a validação do modelo.
Filtros de exceção
Os filtros de exceção podem ser configurados globalmente ou por controlador ou por ação em um aplicativo
MVC. Esses filtros tratam qualquer exceção sem tratamento ocorrida durante a execução de uma ação do
controlador ou de outro filtro e, caso contrário, não são chamadas. Saiba mais sobre filtros de exceção em
Filtros.

DICA
Filtros de exceção são bons para interceptar exceções que ocorrem em ações do MVC, mas não são tão flexíveis quanto o
middleware de tratamento de erro. Dê preferência ao uso de middleware para o caso geral e use filtros apenas quando
precisar fazer o tratamento de erro de modo diferente, dependendo da ação do MVC escolhida.

Tratando erros do estado do modelo


A validação do modelo ocorre antes da invocação de cada ação do controlador e é responsabilidade do método
de ação inspecionar ModelState.IsValid e responder de forma adequada.
Alguns aplicativos optarão por seguir uma convenção padrão para lidar com erros de validação do modelo, caso
em que um filtro pode ser um local adequado para implementar uma política como essa. É necessário testar o
comportamento das ações com estados de modelo inválidos. Saiba mais em Testando a lógica do controlador.
Provedores de arquivos no ASP.NET Core
08/05/2018 • 11 min to read • Edit Online

Por Steve Smith


O ASP.NET Core abstrai o acesso ao sistema de arquivos por meio do uso de provedores de arquivos.
Exibir ou baixar código de exemplo (como baixar)

Abstrações de provedor de arquivos


Provedores de arquivos são uma abstração dos sistemas de arquivos. A interface principal é IFileProvider .
IFileProvider expõe métodos para obter informações sobre o arquivo ( IFileInfo ), informações sobre o
diretório ( IDirectoryContents ) e para configurar notificações de alteração (usando um IChangeToken ).
IFileInfo fornece métodos e propriedades sobre arquivos ou diretórios individuais. Ele tem duas propriedades
boolianas, Exists e IsDirectory , bem como propriedades que descrevem o Name do arquivo, Length (em bytes)
e a data de LastModified . É possível ler do arquivo usando seu método CreateReadStream .

Implementações do provedor de arquivos


Três implementações de IFileProvider estão disponíveis: Físico, Inserido e Composto. O provedor físico é usado
para acessar os arquivos do sistema de fato. O provedor inserido é usado para acessar arquivos inseridos em
assemblies. O provedor composto é usado para fornecer acesso combinado a arquivos e diretórios de um ou mais
provedores.
PhysicalFileProvider
O PhysicalFileProvider fornece acesso ao sistema de arquivos físico. Ele encapsula o tipo System.IO.File (para o
provedor físico), tendo como escopo todos os caminhos para um diretório e seus filhos. Esse escopo limita o
acesso a um determinado diretório e seus filhos, impedindo o acesso ao sistema de arquivos fora desse limite. Ao
instanciar esse provedor, você deve fornecer a ele um caminho de diretório, que serve como o caminho base para
todas as solicitações feitas a esse provedor (e que restringe o acesso de fora desse caminho). Em um aplicativo do
ASP.NET Core, você pode instanciar um provedor PhysicalFileProvider diretamente, ou pode solicitar um
IFileProvider em um Controlador ou o construtor do serviço por meio da injeção de dependência. A segunda
abordagem normalmente produzirá uma solução mais flexível e passível de ser testada.
O exemplo a seguir mostra como criar um PhysicalFileProvider .

IFileProvider provider = new PhysicalFileProvider(applicationRoot);


IDirectoryContents contents = provider.GetDirectoryContents(""); // the applicationRoot contents
IFileInfo fileInfo = provider.GetFileInfo("wwwroot/js/site.js"); // a file under applicationRoot

Você pode iterar no conteúdo do diretório ou obter as informações sobre o arquivo específico fornecendo um
subcaminho.
Para solicitar um provedor de um controlador, especifique-o no construtor do controlador e atribua-o a um campo
local. Use a instância local de seus métodos de ação:
public class HomeController : Controller
{
private readonly IFileProvider _fileProvider;

public HomeController(IFileProvider fileProvider)


{
_fileProvider = fileProvider;
}

public IActionResult Index()


{
var contents = _fileProvider.GetDirectoryContents("");
return View(contents);
}

Em seguida, crie o provedor na classe Startup do aplicativo:

using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;

namespace FileProviderSample
{
public class Startup
{
private IHostingEnvironment _hostingEnvironment;
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();

_hostingEnvironment = env;
}

public IConfigurationRoot Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();

var physicalProvider = _hostingEnvironment.ContentRootFileProvider;


var embeddedProvider = new EmbeddedFileProvider(Assembly.GetEntryAssembly());
var compositeProvider = new CompositeFileProvider(physicalProvider, embeddedProvider);

// choose one provider to use for the app and register it


//services.AddSingleton<IFileProvider>(physicalProvider);
//services.AddSingleton<IFileProvider>(embeddedProvider);
services.AddSingleton<IFileProvider>(compositeProvider);
}

Na exibição Index.cshtml, itere no IDirectoryContents fornecido:


@using Microsoft.Extensions.FileProviders
@model IDirectoryContents

<h2>Folder Contents</h2>

<ul>
@foreach (IFileInfo item in Model)
{
if (item.IsDirectory)
{
<li><strong>@item.Name</strong></li>
}
else
{
<li>@item.Name - @item.Length bytes</li>
}
}
</ul>

O resultado:

EmbeddedFileProvider
O EmbeddedFileProvider é usado para acessar arquivos inseridos em assemblies. No .NET Core, você insere
arquivos em um assembly com o elemento <EmbeddedResource> no arquivo .csproj:

<ItemGroup>
<EmbeddedResource Include="Resource.txt;**\*.js"
Exclude="bin\**;obj\**;**\*.xproj;packages\**;@(EmbeddedResource)" />
<Content Update="wwwroot\**\*;Views\**\*;Areas\**\Views;appsettings.json;web.config">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>

Você pode usar padrões recurso de curinga ao especificar arquivos para serem inseridos no assembly. Esses
padrões podem ser usados para corresponder a um ou mais arquivos.

OBSERVAÇÃO
É improvável que você de fato queira inserir todos os arquivos .js do projeto em seu assembly; o exemplo acima é apenas a
fins de demonstração.

Ao criar um EmbeddedFileProvider , passe o assembly que ele lerá para o construtor.

var embeddedProvider = new EmbeddedFileProvider(Assembly.GetEntryAssembly());

O trecho de código acima demonstra como criar um EmbeddedFileProvider com acesso ao assembly em execução
no momento.
Atualizar o aplicativo de exemplo para usar um EmbeddedFileProvider resulta na seguinte saída:

OBSERVAÇÃO
Recursos inseridos não expõem diretórios. Em vez disso, o caminho para o recurso (por meio de seu namespace) é inserido
no nome de arquivo usando separadores . .

DICA
O construtor EmbeddedFileProvider aceita um parâmetro baseNamespace opcional. Especificar esse parâmetro definirá o
escopo de chamadas para GetDirectoryContents para esses recursos sob o namespace fornecido.

CompositeFileProvider
O CompositeFileProvider combina instâncias de IFileProvider , expondo uma interface única para trabalhar com
arquivos de vários provedores. Ao criar o CompositeFileProvider , você passa uma ou mais instâncias de
IFileProvider para o construtor:

var physicalProvider = _hostingEnvironment.ContentRootFileProvider;


var embeddedProvider = new EmbeddedFileProvider(Assembly.GetEntryAssembly());
var compositeProvider = new CompositeFileProvider(physicalProvider, embeddedProvider);

Atualizar o aplicativo de exemplo para usar um CompositeFileProvider que inclui os provedores físico e inserido
configurados anteriormente resulta na seguinte saída:

Monitorando alterações
O método IFileProvider Watch proporciona uma maneira de monitorar um ou mais arquivos ou diretórios
quanto a alterações. Esse método aceita uma cadeia de caracteres de caminho, que pode usar padrões de recurso
de curinga para especificar vários arquivos e retorna um IChangeToken . Esse token expõe uma propriedade
HasChanged que pode ser inspecionada e um método RegisterChangeCallback que é chamado quando são
detectadas alterações na cadeia de caracteres do caminho especificado. Observe que cada token de alteração
chama apenas seu retorno de chamada associado em resposta a uma única alteração. Para habilitar o
monitoramento constante, você pode usar um TaskCompletionSource conforme mostrado abaixo ou recriar
instâncias de IChangeToken em resposta a alterações.
No exemplo deste artigo, um aplicativo de console é configurado para exibir uma mensagem sempre que um
arquivo de texto é modificado:

private static PhysicalFileProvider _fileProvider =


new PhysicalFileProvider(Directory.GetCurrentDirectory());

public static void Main(string[] args)


{
Console.WriteLine("Monitoring quotes.txt for changes (Ctrl-c to quit)...");

while (true)
{
MainAsync().GetAwaiter().GetResult();
}
}

private static async Task MainAsync()


{
IChangeToken token = _fileProvider.Watch("quotes.txt");
var tcs = new TaskCompletionSource<object>();

token.RegisterChangeCallback(state =>
((TaskCompletionSource<object>)state).TrySetResult(null), tcs);

await tcs.Task.ConfigureAwait(false);

Console.WriteLine("quotes.txt changed");
}

O resultado, após salvar o arquivo várias vezes:

OBSERVAÇÃO
Alguns sistemas de arquivos, como contêineres do Docker e compartilhamentos de rede, podem não enviar notificações de
alteração de forma confiável. Defina a variável de ambiente DOTNET_USE_POLLINGFILEWATCHER como 1 ou true para
sondar o sistema de arquivos a cada quatro segundos em relação a alterações.
Padrões de recurso de curinga
Caminhos de sistema de arquivos usam padrões de curinga chamados padrões de recurso de curinga. Esses
padrões simples podem ser usados para especificar grupos de arquivos. Os dois caracteres curinga são * e ** .
*

Corresponde a qualquer coisa no nível da pasta atual, ou qualquer nome de arquivo ou qualquer extensão de
arquivo. As correspondências são terminadas pelos caracteres / e . no caminho do arquivo.
**

Coincide a qualquer coisa em vários níveis de diretório. Pode ser usada para fazer a correspondência recursiva
com vários arquivos em uma hierarquia de diretórios.
Exemplos de padrão de recurso de curinga
directory/file.txt

Corresponde a um arquivo específico em um diretório específico.


directory/*.txt

Corresponde a todos os arquivos com a extensão .txt em um diretório específico.


directory/*/bower.json

Corresponde a todos os arquivos bower.json em diretórios que estão exatamente um nível abaixo do diretório
directory .

directory/**/*.txt

Corresponde a todos os arquivos com a extensão .txt encontrados em qualquer lugar abaixo do diretório
directory .

Uso de provedores de arquivos no ASP.NET Core


Várias partes do ASP.NET Core utilizam provedores de arquivos. IHostingEnvironment expõe o conteúdo raiz do
aplicativo e a raiz da Web como tipos IFileProvider . O middleware de arquivos estáticos usa provedores de
arquivos para localizar arquivos estáticos. O Razor usa IFileProvider intensamente para localizar exibições. A
funcionalidade de publicação do dotnet usa padrões de recurso de curinga e provedores de arquivos para
especificar quais arquivos devem ser publicados.

Recomendações para uso em aplicativos


Se seu aplicativo ASP.NET Core precisar de acesso ao sistema de arquivos, você pode solicitar uma instância de
IFileProvider por meio da injeção de dependência e, em seguida, usar seus métodos para executar o acesso,
conforme mostrado neste exemplo. Isso permite configurar o provedor uma vez, quando o aplicativo é iniciado, e
reduz o número de tipos de implementação para os quais seu aplicativo cria uma instância.
Hospedagem no ASP.NET Core
08/02/2018 • 29 min to read • Edit Online

Por Luke Latham


Aplicativos ASP.NET Core configuram e inicializam um host. O host é responsável pelo gerenciamento de
tempo de vida e pela inicialização do aplicativo. No mínimo, o host configura um servidor e um pipeline de
processamento de solicitações.

Configurando um host
ASP.NET Core 2.x
ASP.NET Core 1.x
Crie um host usando uma instância de WebHostBuilder. Normalmente, isso é feito no ponto de entrada do
aplicativo, o método Main . Em modelos de projeto, Main está localizado em Program.cs. Um Program.cs
típico chama CreateDefaultBuilder para começar a configurar um host:

public class Program


{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}

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


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

CreateDefaultBuilder executa as seguintes tarefas:


Configura o Kestrel como o servidor Web. Para conhecer as opções padrão do Kestrel, consulte
Implementação do servidor Web Kestrel no ASP.NET Core.
Define a raiz do conteúdo como o caminho retornado por Directory.GetCurrentDirectory.
Carrega configurações opcionais de:
appsettings.json.
appsettings.{Environment}.json.
Segredos do usuário quando o aplicativo é executado no ambiente Development .
Variáveis de ambiente.
Argumentos de linha de comando.
Configura o registro em log para a saída do console e de depuração. O registro em log inclui regras de
filtragem de log especificadas em uma seção de configuração de registro em log de um arquivo
appsettings.json ou appsettings.{Environment}.json.
Quando executado por trás do IIS, permite a integração de IIS. Configura o caminho base e a porta que o
servidor escuta ao usar o Módulo do ASP.NET Core. O módulo cria um proxy reverso entre o IIS e o
Kestrel. Também configura o aplicativo para capturar erros de inicialização. Para conhecer as opções
padrão do IIS, consulte a seção sobre as opções do IIS para Hospedar o ASP.NET Core no Windows com
o IIS.
A raiz do conteúdo determina onde o host procura por arquivos de conteúdo, como arquivos de exibição do
MVC. Quando o aplicativo é iniciado na pasta raiz do projeto, essa pasta é usada como a raiz do conteúdo.
Esse é o padrão usado no Visual Studio e nos novos modelos dotnet.
Para obter mais informações sobre a configuração de aplicativos, consulte Configuração no ASP.NET Core.

OBSERVAÇÃO
Como uma alternativa ao uso do método CreateDefaultBuilder estático, criar um host de WebHostBuilder é uma
abordagem compatível com o ASP.NET Core 2. x. Para obter mais informações, consulte a guia do ASP.NET Core 1.x.

Ao configurar um host, os métodos Configure e ConfigureServices podem ser fornecidos. Se uma classe
Startup for especificada, ela deverá definir um método Configure . Para obter mais informações, consulte
Inicialização do aplicativo no ASP.NET Core. Diversas chamadas para ConfigureServices são acrescentadas
umas às outras. Diversas chamadas para Configure ou UseStartup no WebHostBuilder substituem
configurações anteriores.

Valores de configuração do host


WebHostBuilder conta com as seguintes abordagens para definir os valores de configuração do host:
Configuração do construtor do host, que inclui variáveis de ambiente com o formato
ASPNETCORE_{configurationKey} . Por exemplo, ASPNETCORE_URLS .
Métodos explícitos, como CaptureStartupErrors .
UseSetting e a chave associada. Ao definir um valor com UseSetting , o valor é definido como uma cadeia
de caracteres, independentemente do tipo.
O host usa a opção que define um valor por último. Para obter mais informações, consulte Substituindo a
configuração na próxima seção.
Capturar erros de inicialização
Esta configuração controla a captura de erros de inicialização.
Chave: captureStartupErrors
Tipo: bool ( true ou 1 )
Padrão: o padrão é false , a menos que o aplicativo seja executado com o Kestrel por trás do IIS, em que o
padrão é true .
Definido usando: CaptureStartupErrors
Variável de ambiente: ASPNETCORE_CAPTURESTARTUPERRORS
Quando false , erros durante a inicialização resultam no encerramento do host. Quando true , o host
captura exceções durante a inicialização e tenta iniciar o servidor.
ASP.NET Core 2.x
ASP.NET Core 1.x

WebHost.CreateDefaultBuilder(args)
.CaptureStartupErrors(true)
...

Raiz do conteúdo
Essa configuração determina onde o ASP.NET Core começa a procurar por arquivos de conteúdo, como
exibições do MVC.
Chave: contentRoot
Tipo: string
Padrão: o padrão é a pasta em que o assembly do aplicativo reside.
Definido usando: UseContentRoot
Variável de ambiente: ASPNETCORE_CONTENTROOT
A raiz do conteúdo também é usada como o caminho base para a Configuração da raiz da Web. Se o
caminho não existir, o host não será iniciado.
ASP.NET Core 2.x
ASP.NET Core 1.x

WebHost.CreateDefaultBuilder(args)
.UseContentRoot("c:\\mywebsite")
...

Erros detalhados
Determina se erros detalhados devem ser capturados.
Chave: detailedErrors
Tipo: bool ( true ou 1 )
Padrão: falso
Definido usando: UseSetting
Variável de ambiente: ASPNETCORE_DETAILEDERRORS
Quando habilitado (ou quando o Ambiente é definido como Development ), o aplicativo captura exceções
detalhadas.
ASP.NET Core 2.x
ASP.NET Core 1.x

WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.DetailedErrorsKey, "true")
...

Ambiente
Define o ambiente do aplicativo.
Chave: ambiente
Tipo: string
Padrão: Production
Definido usando: UseEnvironment
Variável de ambiente: ASPNETCORE_ENVIRONMENT
O ambiente pode ser definido como qualquer valor. Os valores definidos pela estrutura incluem Development ,
Staging e Production . Os valores não diferenciam maiúsculas de minúsculas. Por padrão, o Ambiente é lido
da variável de ambiente ASPNETCORE_ENVIRONMENT . Ao usar o Visual Studio, variáveis de ambiente podem ser
definidas no arquivo launchSettings.json. Para obter mais informações, consulte Trabalhando com vários
ambientes.
ASP.NET Core 2.x
ASP.NET Core 1.x
WebHost.CreateDefaultBuilder(args)
.UseEnvironment("Development")
...

Hospedando assemblies de inicialização


Define os assemblies de inicialização de hospedagem do aplicativo.
Chave: hostingStartupAssemblies
Tipo: string
Padrão: cadeia de caracteres vazia
Definido usando: UseSetting
Variável de ambiente: ASPNETCORE_HOSTINGSTARTUPASSEMBLIES
Uma cadeia de caracteres delimitada por ponto e vírgula de assemblies de inicialização de hospedagem para
carregamento na inicialização. Este recurso é novo no ASP.NET Core 2.0.
Embora o valor padrão da configuração seja uma cadeia de caracteres vazia, os assemblies de inicialização de
hospedagem sempre incluem o assembly do aplicativo. Quando assemblies de inicialização de hospedagem
são fornecidos, eles são adicionados ao assembly do aplicativo para carregamento quando o aplicativo
compilar seus serviços comuns durante a inicialização.
ASP.NET Core 2.x
ASP.NET Core 1.x

WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "assembly1;assembly2")
...

Preferir URLs de hospedagem


Indica se o host deve escutar as URLs configuradas com o WebHostBuilder em vez daquelas configuradas
com a implementação IServer .
Chave: preferHostingUrls
Tipo: bool ( true ou 1 )
Padrão: true
Definido usando: PreferHostingUrls
Variável de ambiente: ASPNETCORE_PREFERHOSTINGURLS
Este recurso é novo no ASP.NET Core 2.0.
ASP.NET Core 2.x
ASP.NET Core 1.x

WebHost.CreateDefaultBuilder(args)
.PreferHostingUrls(false)
...

Impedir inicialização de hospedagem


Impede o carregamento automático de assemblies de inicialização de hospedagem, incluindo assemblies de
inicialização de hospedagem configurados pelo assembly do aplicativo. Consulte Adicionar recursos de
aplicativo de um assembly externo usando IHostingStartup para obter mais informações.
Chave: preventHostingStartup
Tipo: bool ( true ou 1 )
Padrão: falso
Definido usando: UseSetting
Variável de ambiente: ASPNETCORE_PREVENTHOSTINGSTARTUP
Este recurso é novo no ASP.NET Core 2.0.
ASP.NET Core 2.x
ASP.NET Core 1.x

WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.PreventHostingStartupKey, "true")
...

URLs de servidor
Indica os endereços IP ou endereços de host com portas e protocolos que o servidor deve escutar para
solicitações.
Chave: urls
Tipo: string
Padrão: http://localhost:5000
Definido usando: UseUrls
Variável de ambiente: ASPNETCORE_URLS
Defina como uma lista separada por ponto e vírgula (;) de prefixos de URL aos quais o servidor deve
responder. Por exemplo, http://localhost:123 . Use "*" para indicar que o servidor deve escutar solicitações
em qualquer endereço IP ou nome do host usando a porta e o protocolo especificados (por exemplo,
http://*:5000 ). O protocolo ( http:// ou https:// ) deve ser incluído com cada URL. Os formatos
compatíveis variam dependendo dos servidores.
ASP.NET Core 2.x
ASP.NET Core 1.x

WebHost.CreateDefaultBuilder(args)
.UseUrls("http://*:5000;http://localhost:5001;https://hostname:5002")
...

O Kestrel tem sua própria API de configuração de ponto de extremidade. Para obter mais informações,
consulte Implementação do servidor Web Kestrel no ASP.NET Core.
Tempo limite de desligamento
Especifica o tempo de espera para o desligamento do host da Web.
Chave: shutdownTimeoutSeconds
Tipo: int
Padrão: 5
Definido usando: UseShutdownTimeout
Variável de ambiente: ASPNETCORE_SHUTDOWNTIMEOUTSECONDS
Embora a chave aceite um int com UseSetting(por exemplo,
.UseSetting(WebHostDefaults.ShutdownTimeoutKey, "10") ), o UseShutdownTimeout método de extensão usa um
TimeSpan . Este recurso é novo no ASP.NET Core 2.0.

ASP.NET Core 2.x


ASP.NET Core 1.x
WebHost.CreateDefaultBuilder(args)
.UseShutdownTimeout(TimeSpan.FromSeconds(10))
...

Assembly de inicialização
Determina o assembly para pesquisar pela classe Startup .
Chave: startupAssembly
Tipo: string
Padrão: o assembly do aplicativo
Definido usando: UseStartup
Variável de ambiente: ASPNETCORE_STARTUPASSEMBLY
O assembly por nome ( string ) ou por tipo ( TStartup ) pode ser referenciado. Se vários métodos
UseStartup forem chamados, o último terá precedência.

ASP.NET Core 2.x


ASP.NET Core 1.x

WebHost.CreateDefaultBuilder(args)
.UseStartup("StartupAssemblyName")
...

WebHost.CreateDefaultBuilder(args)
.UseStartup<TStartup>()
...

Raiz da Web
Define o caminho relativo para os ativos estáticos do aplicativo.
Chave: webroot
Tipo: string
Padrão: se não for especificado, o padrão será "(Raiz do conteúdo)/wwwroot", se o caminho existir. Se o
caminho não existir, um provedor de arquivo não operacional será usado.
Definido usando: UseWebRoot
Variável de ambiente: ASPNETCORE_WEBROOT
ASP.NET Core 2.x
ASP.NET Core 1.x

WebHost.CreateDefaultBuilder(args)
.UseWebRoot("public")
...

Substituindo a configuração
Use Configuração para configurar o host. No exemplo a seguir, a configuração do host é especificada,
opcionalmente, em um arquivo hosting.json. Qualquer configuração carregada do arquivo hosting.json pode
ser substituída por argumentos de linha de comando. A configuração interna (no config ) é usada para
configurar o host com UseConfiguration .
ASP.NET Core 2.x
ASP.NET Core 1.x
hosting.json:

{
urls: "http://*:5005"
}

Substituição da configuração fornecida por UseUrls pela configuração de hosting.json primeiro e pela
configuração de argumento da linha de comando depois:

public class Program


{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}

public static IWebHost BuildWebHost(string[] args)


{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("hosting.json", optional: true)
.AddCommandLine(args)
.Build();

return WebHost.CreateDefaultBuilder(args)
.UseUrls("http://*:5000")
.UseConfiguration(config)
.Configure(app =>
{
app.Run(context =>
context.Response.WriteAsync("Hello, World!"));
})
.Build();
}
}

OBSERVAÇÃO
Atualmente, o método de extensão UseConfiguration não é capaz de analisar uma seção de configuração retornada
por GetSection (por exemplo, .UseConfiguration(Configuration.GetSection("section")) ). O método
GetSection filtra as chaves de configuração da seção solicitada, mas deixa o nome da seção nas chaves (por exemplo,
section:urls , section:environment ). O método UseConfiguration espera que as chaves correspondam às
chaves WebHostBuilder (por exemplo, urls , environment ). A presença do nome da seção nas chaves impede que
os valores da seção configurem o host. Esse problema será corrigido em uma próxima versão. Para obter mais
informações e soluções alternativas, consulte Passar a seção de configuração para WebHostBuilder.UseConfiguration
usa chaves completas.

Para especificar o host executado em uma URL em particular, o valor desejado pode ser passado de um
prompt de comando ao executar dotnet run . O argumento de linha de comando substitui o valor urls do
arquivo hosting.json e o servidor escuta na porta 8080:

dotnet run --urls "http://*:8080"

Iniciando o host
ASP.NET Core 2.x
ASP.NET Core 1.x
Executar
O método Run inicia o aplicativo Web e bloqueia o thread de chamada até que o host seja desligado:

host.Run();

Iniciar
Execute o host sem bloqueio, chamando seu método Start :

using (host)
{
host.Start();
Console.ReadLine();
}

Se uma lista de URLs for passada para o método Start , ele escutará nas URLs especificadas:

var urls = new List<string>()


{
"http://*:5000",
"http://localhost:5001"
};

var host = new WebHostBuilder()


.UseKestrel()
.UseStartup<Startup>()
.Start(urls.ToArray());

using (host)
{
Console.ReadLine();
}

O aplicativo pode inicializar e iniciar um novo host usando os padrões pré-configurados de


CreateDefaultBuilder , usando um método estático conveniente. Esses métodos iniciam o servidor sem uma
saída do console e com WaitForShutdown e aguardam uma quebra (Ctrl-C/SIGINT ou SIGTERM ):
Start(RequestDelegate app)
Inicie com um RequestDelegate :

using (var host = WebHost.Start(app => app.Response.WriteAsync("Hello, World!")))


{
Console.WriteLine("Use Ctrl-C to shutdown the host...");
host.WaitForShutdown();
}

Faça uma solicitação no navegador para http://localhost:5000 para receber a resposta "Olá, Mundo!"
WaitForShutdown bloqueia até que uma quebra ( Ctrl-C/SIGINT ou SIGTERM ) seja emitida. O aplicativo exibe
a mensagem Console.WriteLine e aguarda um pressionamento de tecla para ser encerrado.
Start(string url, RequestDelegate app)
Inicie com uma URL e RequestDelegate :
using (var host = WebHost.Start("http://localhost:8080", app => app.Response.WriteAsync("Hello,
World!")))
{
Console.WriteLine("Use Ctrl-C to shutdown the host...");
host.WaitForShutdown();
}

Produz o mesmo resultado que Start(RequestDelegate app), mas o aplicativo responde em


http://localhost:8080 .

Start(Action routeBuilder)
Use uma instância de IRouteBuilder (Microsoft.AspNetCore.Routing) para usar o middleware de
roteamento:

using (var host = WebHost.Start(router => router


.MapGet("hello/{name}", (req, res, data) =>
res.WriteAsync($"Hello, {data.Values["name"]}!"))
.MapGet("buenosdias/{name}", (req, res, data) =>
res.WriteAsync($"Buenos dias, {data.Values["name"]}!"))
.MapGet("throw/{message?}", (req, res, data) =>
throw new Exception((string)data.Values["message"] ?? "Uh oh!"))
.MapGet("{greeting}/{name}", (req, res, data) =>
res.WriteAsync($"{data.Values["greeting"]}, {data.Values["name"]}!"))
.MapGet("", (req, res, data) => res.WriteAsync("Hello, World!"))))
{
Console.WriteLine("Use Ctrl-C to shutdown the host...");
host.WaitForShutdown();
}

Use as seguintes solicitações de navegador com o exemplo:

SOLICITAÇÃO RESPOSTA

http://localhost:5000/hello/Martin Hello, Martin!

http://localhost:5000/buenosdias/Catrina Buenos dias, Catrina!

http://localhost:5000/throw/ooops! Gera uma exceção com a cadeia de caracteres "ooops!"

http://localhost:5000/throw Gera uma exceção com a cadeia de caracteres "Uh oh!"

http://localhost:5000/Sante/Kevin Sante, Kevin!

http://localhost:5000 Olá, Mundo!

bloqueia até que uma quebra (Ctrl-C/SIGINT ou SIGTERM ) seja emitida. O aplicativo exibe
WaitForShutdown
a mensagem Console.WriteLine e aguarda um pressionamento de tecla para ser encerrado.
Start(string url, Action routeBuilder)
Use uma URL e uma instância de IRouteBuilder :
using (var host = WebHost.Start("http://localhost:8080", router => router
.MapGet("hello/{name}", (req, res, data) =>
res.WriteAsync($"Hello, {data.Values["name"]}!"))
.MapGet("buenosdias/{name}", (req, res, data) =>
res.WriteAsync($"Buenos dias, {data.Values["name"]}!"))
.MapGet("throw/{message?}", (req, res, data) =>
throw new Exception((string)data.Values["message"] ?? "Uh oh!"))
.MapGet("{greeting}/{name}", (req, res, data) =>
res.WriteAsync($"{data.Values["greeting"]}, {data.Values["name"]}!"))
.MapGet("", (req, res, data) => res.WriteAsync("Hello, World!"))))
{
Console.WriteLine("Use Ctrl-C to shutdown the host...");
host.WaitForShutdown();
}

Produz o mesmo resultado que Start(Action routeBuilder), mas o aplicativo responde em


http://localhost:8080 .

StartWith(Action app)
Forneça um delegado para configurar um IApplicationBuilder :

using (var host = WebHost.StartWith(app =>


app.Use(next =>
{
return async context =>
{
await context.Response.WriteAsync("Hello World!");
};
})))
{
Console.WriteLine("Use Ctrl-C to shutdown the host...");
host.WaitForShutdown();
}

Faça uma solicitação no navegador para http://localhost:5000 para receber a resposta "Olá, Mundo!"
WaitForShutdown bloqueia até que uma quebra ( Ctrl-C/SIGINT ou SIGTERM ) seja emitida. O aplicativo exibe
a mensagem Console.WriteLine e aguarda um pressionamento de tecla para ser encerrado.
StartWith(string url, Action app)
Forneça um delegado e uma URL para configurar um IApplicationBuilder :

using (var host = WebHost.StartWith("http://localhost:8080", app =>


app.Use(next =>
{
return async context =>
{
await context.Response.WriteAsync("Hello World!");
};
})))
{
Console.WriteLine("Use Ctrl-C to shutdown the host...");
host.WaitForShutdown();
}

Produz o mesmo resultado que StartWith(Action app), mas o aplicativo responde em


http://localhost:8080 .

Interface IHostingEnvironment
A interface IHostingEnvironment fornece informações sobre o ambiente de hospedagem na Web do
aplicativo. Use a injeção de construtor para obter o IHostingEnvironment para usar suas propriedades e
métodos de extensão:

public class CustomFileReader


{
private readonly IHostingEnvironment _env;

public CustomFileReader(IHostingEnvironment env)


{
_env = env;
}

public string ReadFile(string filePath)


{
var fileProvider = _env.WebRootFileProvider;
// Process the file here
}
}

Uma abordagem baseada em convenção pode ser usada para configurar o aplicativo na inicialização com
base no ambiente. Como alternativa, injete o IHostingEnvironment no construtor Startup para uso em
ConfigureServices :

public class Startup


{
public Startup(IHostingEnvironment env)
{
HostingEnvironment = env;
}

public IHostingEnvironment HostingEnvironment { get; }

public void ConfigureServices(IServiceCollection services)


{
if (HostingEnvironment.IsDevelopment())
{
// Development configuration
}
else
{
// Staging/Production configuration
}

var contentRootPath = HostingEnvironment.ContentRootPath;


}
}

OBSERVAÇÃO
Além do método de extensão IsDevelopment , IHostingEnvironment oferece os métodos IsStaging ,
IsProduction e IsEnvironment(string environmentName) . Consulte Trabalhando com vários ambientes para obter
mais detalhes.

O serviço IHostingEnvironment também pode ser injetado diretamente no método Configure para
configurar o pipeline de processamento:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
// In Development, use the developer exception page
app.UseDeveloperExceptionPage();
}
else
{
// In Staging/Production, route exceptions to /error
app.UseExceptionHandler("/error");
}

var contentRootPath = env.ContentRootPath;


}

IHostingEnvironment pode ser injetado no método Invoke ao criar um middleware personalizado:

public async Task Invoke(HttpContext context, IHostingEnvironment env)


{
if (env.IsDevelopment())
{
// Configure middleware for Development
}
else
{
// Configure middleware for Staging/Production
}

var contentRootPath = env.ContentRootPath;


}

Interface IApplicationLifetime
IApplicationLifetime permite atividades pós-inicialização e desligamento. Três propriedades na interface são
tokens de cancelamento usados para registrar métodos Action que definem eventos de inicialização e
desligamento. Também há um método StopApplication .

TOKEN DE CANCELAMENTO ACIONADO QUANDO…

ApplicationStarted O host foi iniciado totalmente.

ApplicationStopping O host está executando um desligamento normal.


Solicitações ainda podem estar sendo processadas. O
desligamento é bloqueado até que esse evento seja
concluído.

ApplicationStopped O host está concluindo um desligamento normal. Todas as


solicitações devem ser processadas. O desligamento é
bloqueado até que esse evento seja concluído.

MÉTODO AÇÃO

StopApplication Solicita o encerramento do aplicativo atual.


public class Startup
{
public void Configure(IApplicationBuilder app, IApplicationLifetime appLifetime)
{
appLifetime.ApplicationStarted.Register(OnStarted);
appLifetime.ApplicationStopping.Register(OnStopping);
appLifetime.ApplicationStopped.Register(OnStopped);

Console.CancelKeyPress += (sender, eventArgs) =>


{
appLifetime.StopApplication();
// Don't terminate the process immediately, wait for the Main thread to exit gracefully.
eventArgs.Cancel = true;
};
}

private void OnStarted()


{
// Perform post-startup activities here
}

private void OnStopping()


{
// Perform on-stopping activities here
}

private void OnStopped()


{
// Perform post-stopped activities here
}
}

Solução de problemas de System.ArgumentException


Aplica-se somente ao ASP.NET Core 2.0
Um host pode ser compilado injetando IStartup diretamente no contêiner de injeção de dependência em
vez de chamar UseStartup ou Configure :

services.AddSingleton<IStartup, Startup>();

Se o host for compilado dessa maneira, o seguinte erro poderá ocorrer:

Unhandled Exception: System.ArgumentException: A valid non-empty application name must be provided.

Isso ocorre porque o applicationName(ApplicationKey) (do assembly atual) é necessário para verificar se há
HostingStartupAttributes . Se o aplicativo injetar manualmente IStartup no contêiner de injeção de
dependência, adicione a seguinte chamada para WebHostBuilder com o nome do assembly especificado:

WebHost.CreateDefaultBuilder(args)
.UseSetting("applicationName", "<Assembly Name>")
...

Como alternativa, adicione um Configure fictício ao WebHostBuilder , que define o applicationName (


ApplicationKey ) automaticamente:
WebHost.CreateDefaultBuilder(args)
.Configure(_ => { })
...

Observação: isso só é necessário com a versão do ASP.NET Core 2.0 e somente quando o aplicativo não
chama UseStartup ou Configure .
Para obter mais informações, consulte Comunicado: Microsoft.Extensions.PlatformAbstractions foi removido
(comentário) e o Exemplo de StartupInjection.

Recursos adicionais
Hospedar no Windows com o IIS
Hospedar em Linux com o Nginx
Hospedar em Linux com o Apache
Hospedar em um serviço Windows
Introdução ao estado da sessão e do aplicativo no
ASP.NET Core
12/02/2018 • 25 min to read • Edit Online

Por Rick Anderson, Steve Smith e Diana LaRose


O HTTP é um protocolo sem estado. Um servidor Web trata cada solicitação HTTP como uma solicitação
independente e não mantém valores de usuários de solicitações anteriores. Este artigo aborda diferentes
maneiras de preservar o estado de sessão e de aplicativo entre solicitações.

Estado de sessão
O estado de sessão é um recurso do ASP.NET Core que você pode usar para salvar e armazenar dados de
usuário enquanto o usuário navega seu aplicativo Web. Composto por um dicionário ou tabela de hash no
servidor, o estado de sessão persiste dados entre solicitações de um navegador. É feito um back up dos dados da
sessão em um cache.
O ASP.NET Core mantém o estado de sessão fornecendo ao cliente um cookie que contém a ID da sessão, que é
enviada ao servidor com cada solicitação. O servidor usa a ID da sessão para buscar os dados da sessão. Como o
cookie da sessão é específico ao navegador, não é possível compartilhar sessões entre navegadores. Cookies da
sessão são excluídos somente quando a sessão do navegador termina. Se for recebido um cookie de uma sessão
expirada, uma nova sessão que usa o mesmo cookie será criada.
O servidor mantém uma sessão por um tempo limitado após a última solicitação. Defina o tempo limite da
sessão ou use o valor padrão de 20 minutos. O estado de sessão é ideal para armazenar dados do usuário que
são específicos a uma sessão específica, mas não precisam ser persistidos permanentemente. Dados são
excluídos do repositório de backup ao chamar Session.Clear ou quando a sessão expira no armazenamento de
dados. O servidor não sabe quando o navegador é fechado ou quando o cookie da sessão é excluído.

AVISO
Não armazene dados confidenciais na sessão. O cliente pode não fechar o navegador e limpar o cookie da sessão (e alguns
navegadores mantêm cookies de sessão ativos entre janelas diferentes). Além disso, uma sessão pode não ficar restrita a
um único usuário; o usuário seguinte pode continuar com a mesma sessão.

O provedor da sessão na memória armazena dados da sessão no servidor local. Caso planeje executar seu
aplicativo Web em um farm de servidores, você precisará usar sessões autoadesivas para vincular cada sessão a
um servidor específico. A plataforma Sites do Microsoft Azure usa sessões autoadesivas como padrão
(Application Request Routing ou ARR ). Entretanto, sessões autoadesivas podem afetar a escalabilidade e
complicar atualizações de aplicativos Web. Uma opção melhor é usar os caches distribuídos do Redis ou do SQL
Server, que não requerem sessões autoadesivas. Para obter mais informações, consulte Trabalhando com um
cache distribuído. Para obter detalhes sobre como configurar provedores de serviço, consulte Configurando a
sessão posteriormente neste artigo.

TempData
O ASP.NET Core MVC 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 é particularmente útil para redirecionamento em casos em que os dados são necessários para mais de
uma única solicitação. TempData é implementado por provedores de TempData, por exemplo, usando cookies ou
estado de sessão.
Provedores de TempData
ASP.NET Core 2.x
ASP.NET Core 1.x
No ASP.NET Core 2.0 e posteriores, o provedor de TempData baseado em cookies é usado por padrão para
armazenar TempData em cookies.
Os dados do cookie são codificados com o Base64UrlTextEncoder. Como o cookie é criptografado e dividido em
partes, o limite de tamanho de cookie único encontrado no ASP.NET Core 1. x não se aplica. Os dados do cookie
não são compactados porque a compactação de dados criptografados pode levar a problemas de segurança,
como os ataques CRIME e BREACH. Para obter mais informações sobre o provedor de TempData baseado em
cookie, consulte CookieTempDataProvider.
Escolhendo um provedor de TempData
Escolher um provedor de TempData envolve várias considerações, como:
1. O aplicativo já usa o estado de sessão para outras finalidades? Em caso afirmativo, usar o provedor de
TempData do estado de sessão não tem custos adicionais para o aplicativo (além do tamanho dos dados).
2. O aplicativo usa TempData raramente, para quantidades relativamente pequenas de dados (até 500 bytes)?
Em caso afirmativo, o provedor de TempData do cookie acrescentará um pequeno custo a cada solicitação que
transportar TempData. Caso contrário, o provedor de TempData do estado de sessão pode ser útil para evitar
fazer viagens de ida e volta para uma grande quantidade de dados a cada solicitação até que TempData seja
consumido.
3. O aplicativo é executado em um web farm (vários servidores)? Nesse caso, nenhuma configuração adicional é
necessária para usar o provedor de TempData do cookie.

OBSERVAÇÃO
A maioria dos clientes da Web (como navegadores da Web) impõem limites quanto ao tamanho máximo de cada cookie, o
número total de cookies ou ambos. Portanto, ao usar o provedor de TempData do cookie, verifique se o aplicativo não
ultrapassará esses limites. Considere o tamanho total dos dados, levando em consideração as sobrecargas de criptografia e
divisão em partes.

Configurar o provedor de TempData


ASP.NET Core 2.x
ASP.NET Core 1.x
O provedor de TempData baseado em cookie é habilitado por padrão. O código da classe Startup a seguir
configura o provedor de TempData baseado em sessão:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddSessionStateTempDataProvider();

services.AddSession();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)


{
app.UseSession();
app.UseMvcWithDefaultRoute();
}

A ordenação é crítica para componentes de middleware. No exemplo anterior, uma exceção do tipo
InvalidOperationException ocorre quando UseSession é invocado após UseMvcWithDefaultRoute . Consulte
Ordenação de Middleware para obter mais detalhes.

IMPORTANTE
Se o alvo for o .NET Framework e o provedor baseado em sessão for usado, adicione o pacote NuGet
Microsoft.AspNetCore.Session ao seu projeto.

Cadeias de consulta
Você pode passar uma quantidade limitada de dados de uma solicitação para outra adicionando-os à cadeia de
caracteres de consulta da nova solicitação. Isso é útil para capturar o estado de uma maneira persistente que
permita que links com estado inserido sejam compartilhados por email ou por redes sociais. No entanto, por esse
motivo, você nunca deve usar cadeias de consulta para dados confidenciais. Além de serem compartilhadas
facilmente, incluir dados em cadeias de consulta pode criar oportunidades para ataques de CSRF (solicitação
intersite forjada), que podem enganar os usuários para que eles visitem sites mal-intencionados enquanto estão
autenticados. Invasores podem, então, roubar dados do usuário de seu aplicativo ou executar ações mal-
intencionadas em nome do usuário. Qualquer estado de sessão ou aplicativo preservado deve proteger contra
ataques CSRF. Para obter mais informações sobre ataques CSRF, consulte Preventing Cross-Site Request
Forgery (XSRF/CSRF ) Attacks in ASP.NET Core (Impedindo ataques XSRF/CSRF [solicitação intersite forjada]
no ASP.NET Core).

Dados de postagem e campos ocultos


Dados podem ser salvos em campos de formulário ocultos e postados novamente na solicitação seguinte. Isso é
comum em formulários com várias páginas. No entanto, como o cliente pode adulterar os dados, o servidor
sempre deve validá-lo novamente.

Cookies
Cookies fornecem uma maneira de armazenar dados específicos do usuário em aplicativos Web. Como os
cookies são enviados com cada solicitação, seu tamanho deve ser reduzido ao mínimo. Idealmente, somente um
identificador deve ser armazenado em um cookie, com os dados reais armazenados no servidor. A maioria dos
navegadores restringe os cookies a 4096 bytes. Além disso, somente um número limitado de cookies está
disponível para cada domínio.
Como cookies estão sujeitos à adulteração, eles devem ser validados no servidor. Embora a durabilidade do
cookie em um cliente esteja sujeita à intervenção do usuário e à expiração, normalmente eles são a forma mais
durável de persistência de dados no cliente.
Frequentemente, cookies são usados para personalização quando o conteúdo é personalizado para um usuário
conhecido. Como na maioria dos casos o usuário é apenas identificado e não autenticado, normalmente você
pode proteger um cookie armazenando o nome de usuário, o nome da conta ou uma ID de usuário único (como
um GUID ) no cookie. É possível, então, usar o cookie para acessar a infraestrutura de personalização de usuários
de um site.

HttpContext.Items
A coleção Items é um bom local para armazenar dados que são necessários somente ao processar uma
solicitação específica. O conteúdo da coleção é descartado após cada solicitação. A coleção Items é melhor
usada como uma maneira de componentes ou middleware se comunicarem quando operam em momentos
diferentes durante uma solicitação e não têm nenhuma maneira direta de passar parâmetros. Para obter mais
informações, consulte Trabalhando com HttpContext.Items posteriormente neste artigo.

Cache
O cache é uma maneira eficiente de armazenar e recuperar dados. É possível controlar o tempo de vida dos itens
em cache com base na hora e em outras considerações. Saiba mais sobre o Caching.

Trabalhando com o estado de sessão


Configurando a sessão
O pacote Microsoft.AspNetCore.Session fornece middleware para gerenciar o estado de sessão. Para habilitar o
middleware da sessão, Startup deve conter:
Qualquer um dos caches de memória IDistributedCache. A implementação IDistributedCache é usada como
um repositório de backup para a sessão.
Chamada AddSession, que requer o pacote NuGet "Microsoft.AspNetCore.Session".
Chamada UseSession.
O código a seguir mostra como configurar o provedor de sessão na memória.
ASP.NET Core 2.x
ASP.NET Core 1.x
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using System;

public class Startup


{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();

// Adds a default in-memory implementation of IDistributedCache.


services.AddDistributedMemoryCache();

services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
});
}

public void Configure(IApplicationBuilder app)


{
app.UseSession();
app.UseMvcWithDefaultRoute();
}
}

É possível fazer referência à sessão de HttpContext após ele ser instalado e configurado.
Se você tentar acessar Session antes que UseSession tenha sido chamado, a exceção
InvalidOperationException: Session has not been configured for this application or request será lançada.
Se você tentar criar um novo Session (ou seja, nenhum cookie de sessão foi criado) após já ter começado a
gravar no fluxo Response , a exceção
InvalidOperationException: The session cannot be established after the response has started será lançada. A
exceção pode ser encontrada no log do servidor Web; ele não será exibido no navegador.
Carregamento a sessão de forma assíncrona
O provedor de sessão padrão no ASP.NET Core carrega o registro da sessão do repositório IDistributedCache
subjacente de forma assíncrona somente se o método ISession.LoadAsync for chamado explicitamente antes dos
métodos TryGetValue , Set ou Remove . Se LoadAsync não for chamado primeiro, o registro da sessão
subjacente é carregado de forma síncrona, o que poderia afetar a capacidade de dimensionamento do aplicativo.
Para que aplicativos imponham esse padrão, encapsule as implementações DistributedSessionStore e
DistributedSession com versões que geram uma exceção se o método LoadAsync não for chamado antes de
TryGetValue , Set ou Remove . Registre as versões encapsuladas no contêiner de serviços.

Detalhes da implementação
A sessão usa um cookie para rastrear e identificar solicitações de um único navegador. Por padrão, esse cookie é
denominado ". AspNet.Session" e usa um caminho de "/". Como o padrão do cookie não especifica um domínio,
ele não fica disponível para o script do lado do cliente na página (porque CookieHttpOnly tem true como
padrão).
Para substituir os padrões da sessão, use SessionOptions :
ASP.NET Core 2.x
ASP.NET Core 1.x
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();

// Adds a default in-memory implementation of IDistributedCache.


services.AddDistributedMemoryCache();

services.AddSession(options =>
{
options.Cookie.Name = ".AdventureWorks.Session";
options.IdleTimeout = TimeSpan.FromSeconds(10);
});
}

O servidor usa a propriedade IdleTimeout para determinar por quanto tempo uma sessão pode ficar ociosa
antes que seu conteúdo seja abandonado. Essa propriedade é independente da expiração do cookie. Cada
solicitação passada por meio do middleware de Sessão (lida ou gravada) redefine o tempo limite.
Como Session é sem bloqueio, se duas solicitações tentarem modificar o conteúdo da sessão, a última delas
substituirá a primeira. Session é implementado como uma sessão coerente, o que significa que todo o conteúdo
é armazenado junto. Duas solicitações que estão modificando partes diferentes da sessão (chaves diferentes)
ainda podem afetar umas às outras.
Configurando e obtendo valores de sessão
A sessão é acessada por meio da propriedade Session em HttpContext . Esta propriedade é uma implementação
de ISession.
O exemplo a seguir mostra a configuração e a obtenção de um int e uma cadeia de caracteres:

public class HomeController : Controller


{
const string SessionKeyName = "_Name";
const string SessionKeyYearsMember = "_YearsMember";
const string SessionKeyDate = "_Date";

public IActionResult Index()


{
// Requires using Microsoft.AspNetCore.Http;
HttpContext.Session.SetString(SessionKeyName, "Rick");
HttpContext.Session.SetInt32(SessionKeyYearsMember, 3);
return RedirectToAction("SessionNameYears");
}
public IActionResult SessionNameYears()
{
var name = HttpContext.Session.GetString(SessionKeyName);
var yearsMember = HttpContext.Session.GetInt32(SessionKeyYearsMember);

return Content($"Name: \"{name}\", Membership years: \"{yearsMember}\"");


}
}

Se adicionar os seguintes métodos de extensão, você poderá definir e obter objetos serializáveis da sessão:
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;

public static class SessionExtensions


{
public static void Set<T>(this ISession session, string key, T value)
{
session.SetString(key, JsonConvert.SerializeObject(value));
}

public static T Get<T>(this ISession session,string key)


{
var value = session.GetString(key);
return value == null ? default(T) :
JsonConvert.DeserializeObject<T>(value);
}
}

O exemplo a seguir mostra como definir e obter um objeto serializável:

public IActionResult SetDate()


{
// Requires you add the Set extension method mentioned in the article.
HttpContext.Session.Set<DateTime>(SessionKeyDate, DateTime.Now);
return RedirectToAction("GetDate");
}

public IActionResult GetDate()


{
// Requires you add the Get extension method mentioned in the article.
var date = HttpContext.Session.Get<DateTime>(SessionKeyDate);
var sessionTime = date.TimeOfDay.ToString();
var currentTime = DateTime.Now.TimeOfDay.ToString();

return Content($"Current time: {currentTime} - "


+ $"session time: {sessionTime}");
}

Trabalhando com HttpContext.Items


A abstração HttpContext dá suporte a uma coleção de dicionário do tipo IDictionary<object, object> , chamada
Items . Essa coleção está disponível desde o início de um HttpRequest e é descartada no final de cada solicitação.
Você pode acessá-la atribuindo um valor a uma entrada com chave ou solicitando o valor de uma determinada
chave.
No exemplo a seguir, Middleware adiciona isVerified à coleção Items .

app.Use(async (context, next) =>


{
// perform some verification
context.Items["isVerified"] = true;
await next.Invoke();
});

Posteriormente no pipeline, outro middleware poderia acessá-la:


app.Run(async (context) =>
{
await context.Response.WriteAsync("Verified request? " +
context.Items["isVerified"]);
});

Para middleware que será usado apenas por um único aplicativo, chaves string são aceitáveis. No entanto,
middleware que será compartilhado entre aplicativos deve usar chaves de objeto exclusivas para evitar qualquer
possibilidade de colisões de chaves. Se estiver desenvolvendo middleware que precisa funcionar em vários
aplicativos, use uma chave de objeto exclusiva definida na classe do middleware, como mostrado abaixo:

public class SampleMiddleware


{
public static readonly object SampleKey = new Object();

public async Task Invoke(HttpContext httpContext)


{
httpContext.Items[SampleKey] = "some value";
// additional code omitted
}
}

Outros códigos podem acessar o valor armazenado em HttpContext.Items usando a chave exposta pela classe
do middleware:

public class HomeController : Controller


{
public IActionResult Index()
{
string value = HttpContext.Items[SampleMiddleware.SampleKey];
}
}

Essa abordagem também tem a vantagem de eliminar a repetição de "cadeias de caracteres mágicas" em vários
locais no código.

Dados de estado do aplicativo


Use a Injeção de dependência para disponibilizar dados para todos os usuários:
1. Defina um serviço que contém os dados (por exemplo, uma classe chamada MyAppData ).

public class MyAppData


{
// Declare properties/methods/etc.
}

2. Adicione a classe de serviço a ConfigureServices (por exemplo, services.AddSingleton<MyAppData>(); ).


3. Consuma a classe do serviço de dados em cada controlador:
public class MyController : Controller
{
public MyController(MyAppData myService)
{
// Do something with the service (read some data from it,
// store it in a private field/property, etc.)
}
}

Erros comuns ao trabalhar com a sessão


"Não é possível resolver o serviço para o tipo
'Microsoft.Extensions.Caching.Distributed.IDistributedCache' ao tentar ativar
'Microsoft.AspNetCore.Session.DistributedSessionStore'."
Geralmente, isso é causado quando não é configurada pelo menos uma implementação de
IDistributedCache . Para obter mais informações, consulte Trabalhando com um cache distribuído e Cache
de memória.
Caso o middleware da sessão não consiga persistir uma sessão (por exemplo, se o banco de dados não
estiver disponível), ele registrará a exceção e a ignorará. A solicitação continuará normalmente, o que leva
a um comportamento muito imprevisível.
Um exemplo típico:
Alguém armazena um carrinho de compras na sessão. O usuário adiciona um item, mas a confirmação falha. O
aplicativo não sabe sobre a falha e exibe a mensagem "O item foi adicionado", o que não é verdadeiro.
A maneira recomendada de verificar esses erros é chamar await feature.Session.CommitAsync(); do código do
aplicativo quando você terminar de gravar a sessão. Em seguida, você pode fazer o que quiser com o erro. Isso
funciona da mesma forma ao chamar LoadAsync .
Recursos adicionais
ASP.NET Core 1. x: código de exemplo usado neste documento
ASP.NET Core 2. x: código de exemplo usado neste documento
Implementações de servidor Web em ASP.NET Core
10/04/2018 • 10 min to read • Edit Online

Por Tom Dykstra, Steve Smith, Stephen Halter e Chris Ross


Um aplicativo ASP.NET Core é executado com uma implementação do servidor HTTP em processo. A
implementação do servidor escuta solicitações HTTP e traz essas solicitações à tona para o aplicativo como
conjuntos de recursos de solicitação compostos em um HttpContext.
O ASP.NET Core envia duas implementações de servidor:
O Kestrel é um servidor HTTP de plataforma cruzada com base em libuv, uma biblioteca de E/S
assíncrona de plataforma cruzada.
O HTTP.sys é um servidor HTTP somente do Windows com base no driver do kernel HTTP.sys e na API
do servidor HTTP. (O HTTP.sys é chamado WebListener no ASP.NET Core 1.x.)

Kestrel
O Kestrel é o servidor Web que está incluído por padrão em modelos de novo projeto do ASP.NET Core.
ASP.NET Core 2.x
ASP.NET Core 1.x
O Kestrel pode ser usado sozinho ou com um servidor proxy reverso como IIS, Nginx ou Apache. Um servidor
proxy reverso recebe solicitações HTTP da Internet e as encaminha para o Kestrel após algum tratamento
preliminar.

Qualquer configuração — com ou sem um servidor proxy reverso — também pode ser usada se o Kestrel é
exposto somente a uma rede interna.
Para obter informações, veja Quando usar Kestrel com um proxy reverso.
O IIS, o Nginx ou o Apache não podem ser usados sem o Kestrel ou uma implementação de servidor
personalizado. O ASP.NET Core foi projetado para ser executado em seu próprio processo, de modo que ele
pode se comportar de forma consistente entre plataformas. O IIS, o Nginx e o Apache determinam seu próprio
procedimento de inicialização e o ambiente. Para usar essas tecnologias de servidor diretamente, o ASP.NET
Core precisaria adaptar-se aos requisitos de cada servidor. Usando uma implementação do servidor Web, tal
como Kestrel, o ASP.NET Core tem controle sobre o processo de inicialização e o ambiente quando hospedado
em tecnologias de servidor diferentes.
IIS com Kestrel
Ao usas o IIS ou IIS Express como um proxy reverso para o ASP.NET Core, o aplicativo ASP.NET Core é
executado em um processo separado do processo de trabalho do IIS. No processo do IIS, o Módulo do ASP.NET
Core coordena a relação do proxy reverso. As funções primárias do módulo do ASP.NET Core são iniciar o
aplicativo do ASP.NET Core, reiniciá-lo quando ele falhar e encaminhar o tráfego HTTP para o aplicativo. Para
obter mais informações, consulte Módulo ASP.NET Core.
Nginx com Kestrel
Para obter informações sobre como usar Nginx no Linux como um servidor proxy reverso para Kestrel, veja
Hospedar em Linux com o Nginx.
Apache com Kestrel
Para obter informações sobre como usar Apache no Linux como um servidor proxy reverso para Kestrel, veja
Hospedar em Linux com o Apache.

HTTP.sys
ASP.NET Core 2.x
ASP.NET Core 1.x
Se os aplicativos ASP.NET Core forem executados no Windows, o HTTP.sys será uma alternativa ao Kestrel. O
Kestrel geralmente é recomendado para melhor desempenho. O HTTP.sys pode ser usado em cenários em que o
aplicativo é exposto à Internet e os recursos necessários são compatíveis com HTTP.sys, mas não com Kestrel.
Para obter informações sobre os recursos do HTTP.sys, veja HTTP.sys.

O HTTP.sys também pode ser usado para aplicativos que são expostos somente a uma rede interna.

Infraestrutura de servidor do ASP.NET Core


O IApplicationBuilder disponível no método Startup.Configure expõe a propriedade ServerFeatures do tipo
IFeatureCollection. Kestrel e HTTP.sys (WebListener no ASP.NET Core 1.x) expõem apenas um único recurso
cada, IServerAddressesFeature, mas as implementações de servidor diferentes podem expor funcionalidade
adicional.
IServerAddressesFeature pode ser usado para descobrir a qual porta a implementação do servidor se acoplou
durante o tempo de execução.

Servidores personalizados
Se os servidores internos não atenderem aos requisitos do aplicativo, um servidor personalizado poderá ser
criado. O Guia de OWIN (Open Web Interface para .NET) demonstra como gravar uma implementação de
IServer com base em Nowin. Somente as interfaces de recurso que o aplicativo usa exigem implementação.
Porém, no mínimo IHttpRequestFeature e IHttpResponseFeature devem ter suporte.

Inicialização do servidor
Ao usar o Visual Studio, o Visual Studio para Mac ou o Visual Studio Code, o servidor é iniciado quando o
aplicativo é iniciado pelo IDE (ambiente de desenvolvimento integrado). No Visual Studio no Windows, os perfis
de inicialização podem ser usados para iniciar o aplicativo e o servidor com o IIS Express/Módulo do ASP.NET
Core ou o console. No Visual Studio Code, o aplicativo e o servidor são iniciados por Omnisharp, que ativa o
depurador CoreCLR. Usando o Visual Studio para Mac, o aplicativo e o servidor são iniciados pelo depurador de
modo reversível Mono.
Ao iniciar um aplicativo com um prompt de comando na pasta do projeto, a execução dotnet inicia o aplicativo e
o servidor (somente no Kestrel e no HTTP.sys). A configuração é especificada pela opção -c|--configuration ,
que é definida como Debug (padrão) ou Release . Se os perfis de inicialização estiverem presentes em um
arquivo launchSettings.json, use a opção --launch-profile <NAME> para definir o perfil de inicialização (por
exemplo, Development ou Production ). Para obter mais informações, veja os tópicos execução dotnet e
Empacotamento de distribuição do .NET Core.

Recursos adicionais
Kestrel
Kestrel com IIS
Hospedar em Linux com o Nginx
Hospedar em Linux com o Apache
HTTP.sys (para o ASP.NET Core 1.x, veja WebListener)
Introdução à implementação do servidor Web
Kestrel no ASP.NET Core
08/02/2018 • 21 min to read • Edit Online

Por Tom Dykstra, Chris Ross e Stephen Halter


O Kestrel é um servidor Web multiplataforma para o ASP.NET Core baseado no libuv, uma biblioteca de
E/S assíncrona multiplataforma. O Kestrel é o servidor Web que está incluído por padrão em modelos de
projeto do ASP.NET Core.
O Kestrel dá suporte aos seguintes recursos:
HTTPS
Atualização do Opaque usado para habilitar o WebSockets
Soquetes do UNIX para alto desempenho protegidos pelo Nginx
Há suporte para o Kestrel em todas as plataformas e versões compatíveis com o .NET Core.
ASP.NET Core 2.x
ASP.NET Core 1.x
Exibir ou baixar um código de exemplo para o 2.x (como baixar)

Quando usar o Kestrel com um proxy reverso


ASP.NET Core 2.x
ASP.NET Core 1.x
Você pode usar o Kestrel sozinho ou com um servidor proxy reverso como IIS, Nginx ou Apache. Um
servidor proxy reverso recebe solicitações HTTP da Internet e as encaminha para o Kestrel após algum
tratamento preliminar.

Qualquer configuração — com ou sem um servidor proxy reverso — também pode ser usada se o Kestrel é
exposto somente a uma rede interna.
Um cenário que exige um proxy reverso é quando você tem vários aplicativos que compartilham o mesmo
IP e porta em execução em um único servidor. Isso não funciona com o Kestrel diretamente, pois o Kestrel
não dá suporte ao compartilhamento do mesmo IP e porta entre vários processos. Quando você configura
o Kestrel para escutar em uma porta, ele manipula todo o tráfego para essa porta, independentemente do
cabeçalho de host. Um proxy reverso que pode compartilhar portas, em seguida, precisa encaminhá-las
para o Kestrel em um IP e porta exclusivos.
Mesmo se um servidor proxy reverso não é necessário, o uso de um pode ser uma boa opção por outros
motivos:
Ele pode limitar a área da superfície exposta.
Fornece uma camada de configuração e proteção adicional opcional.
Pode ser integrado melhor à infraestrutura existente.
Simplifica o balanceamento de carga e a configuração do SSL. Somente o servidor proxy reverso exige
um certificado SSL e esse servidor pode se comunicar com os servidores de aplicativos na rede interna
usando HTTP simples.

Como usar o Kestrel em aplicativos ASP.NET Core


ASP.NET Core 2.x
ASP.NET Core 1.x
O pacote Microsoft.AspNetCore.Server.Kestrel está incluído no metapacote Microsoft.AspNetCore.All.
Os modelos de projeto do ASP.NET Core usam o Kestrel por padrão. Em Program.cs, o código de modelo
chama CreateDefaultBuilder , que chama UseKestrel em segundo plano.

public static void Main(string[] args)


{
BuildWebHost(args).Run();
}

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


WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseKestrel(options =>
{
options.Listen(IPAddress.Loopback, 5000);
options.Listen(IPAddress.Loopback, 5001, listenOptions =>
{
listenOptions.UseHttps("testCert.pfx", "testPassword");
});
})
.Build();

Caso precise configurar as opções do Kestrel, chame UseKestrel em Program.cs, conforme mostrado no
seguinte exemplo:

public static void Main(string[] args)


{
BuildWebHost(args).Run();
}

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


WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseKestrel(options =>
{
options.Listen(IPAddress.Loopback, 5000);
options.Listen(IPAddress.Loopback, 5001, listenOptions =>
{
listenOptions.UseHttps("testCert.pfx", "testPassword");
});
})
.Build();

Opções do Kestrel
ASP.NET Core 2.x
ASP.NET Core 1.x
O servidor Web do Kestrel tem opções de configuração de restrição especialmente úteis em implantações
para a Internet. Estes são alguns dos limites que podem ser definidos:
Número máximo de conexões de cliente
Tamanho máximo do corpo da solicitação
Taxa de dados mínima do corpo da solicitação
Defina essas restrições e outras na propriedade Limits da classe KestrelServerOptions. A propriedade
Limits contém uma instância da classe KestrelServerLimits.

Número máximo de conexões de cliente


O número máximo de conexões TCP abertas simultâneas pode ser definido para todo o aplicativo com o
seguinte código:

.UseKestrel(options =>
{
options.Limits.MaxConcurrentConnections = 100;
options.Limits.MaxConcurrentUpgradedConnections = 100;
options.Limits.MaxRequestBodySize = 10 * 1024;
options.Limits.MinRequestBodyDataRate =
new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
options.Limits.MinResponseDataRate =
new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
options.Listen(IPAddress.Loopback, 5000);
options.Listen(IPAddress.Loopback, 5001, listenOptions =>
{
listenOptions.UseHttps("testCert.pfx", "testPassword");
});
})

Há um limite separado para conexões que foram atualizadas do HTTP ou HTTPS para outro protocolo (por
exemplo, em uma solicitação do WebSockets). Depois que uma conexão é atualizada, ela não é contada em
relação ao limite de MaxConcurrentConnections .
O número máximo de conexões é ilimitado (nulo) por padrão.
Tamanho máximo do corpo da solicitação
O tamanho máximo do corpo da solicitação padrão é de 30.000.000 bytes, que equivale aproximadamente
a 28,6 MB.
A maneira recomendada para substituir o limite em um aplicativo ASP.NET Core MVC é usar o atributo
RequestSizeLimit em um método de ação:

[RequestSizeLimit(100000000)]
public IActionResult MyActionMethod()

Este é um exemplo que mostra como configurar a restrição para todo o aplicativo, em cada solicitação:
.UseKestrel(options =>
{
options.Limits.MaxConcurrentConnections = 100;
options.Limits.MaxConcurrentUpgradedConnections = 100;
options.Limits.MaxRequestBodySize = 10 * 1024;
options.Limits.MinRequestBodyDataRate =
new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
options.Limits.MinResponseDataRate =
new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
options.Listen(IPAddress.Loopback, 5000);
options.Listen(IPAddress.Loopback, 5001, listenOptions =>
{
listenOptions.UseHttps("testCert.pfx", "testPassword");
});
})

Substitua a configuração em uma solicitação específica no middleware:

app.Run(async (context) =>


{
context.Features.Get<IHttpMaxRequestBodySizeFeature>()
.MaxRequestBodySize = 10 * 1024;
context.Features.Get<IHttpMinRequestBodyDataRateFeature>()
.MinDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
context.Features.Get<IHttpMinResponseDataRateFeature>()
.MinDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));

Uma exceção é gerada se você tenta configurar o limite de uma solicitação depois que o aplicativo iniciou a
leitura da solicitação. Há uma propriedade IsReadOnly que indica se a propriedade MaxRequestBodySize
está no estado somente leitura, o que significa que é tarde demais para configurar o limite.
Taxa de dados mínima do corpo da solicitação
O Kestrel verifica a cada segundo se os dados estão sendo recebidos na taxa especificada em
bytes/segundo. Se a taxa cair abaixo do mínimo, a conexão atingirá o tempo limite. O período de cortesia é
o tempo que o Kestrel fornece ao cliente para aumentar sua taxa de envio até o mínimo; a taxa não é
verificada durante esse período. O período de cortesia ajuda a evitar a remoção de conexões que
inicialmente enviam dados em uma taxa baixa devido ao início lento do TCP.
A taxa mínima padrão é de 240 bytes/segundo, com um período de cortesia de 5 segundos.
Uma taxa mínima também se aplica à resposta. O código para definir o limite de solicitação e o limite de
resposta é o mesmo, exceto por ter RequestBody ou Response nos nomes da propriedade e da interface.
Este é um exemplo que mostra como configurar as taxas mínima de dados em Program.cs:
.UseKestrel(options =>
{
options.Limits.MaxConcurrentConnections = 100;
options.Limits.MaxConcurrentUpgradedConnections = 100;
options.Limits.MaxRequestBodySize = 10 * 1024;
options.Limits.MinRequestBodyDataRate =
new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
options.Limits.MinResponseDataRate =
new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
options.Listen(IPAddress.Loopback, 5000);
options.Listen(IPAddress.Loopback, 5001, listenOptions =>
{
listenOptions.UseHttps("testCert.pfx", "testPassword");
});
})

Configure as taxas por solicitação no middleware:

app.Run(async (context) =>


{
context.Features.Get<IHttpMaxRequestBodySizeFeature>()
.MaxRequestBodySize = 10 * 1024;
context.Features.Get<IHttpMinRequestBodyDataRateFeature>()
.MinDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
context.Features.Get<IHttpMinResponseDataRateFeature>()
.MinDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));

Para obter informações sobre outras opções do Kestrel, consulte as seguintes classes:
KestrelServerOptions
KestrelServerLimits
ListenOptions
Configuração do ponto de extremidade
ASP.NET Core 2.x
ASP.NET Core 1.x
Por padrão, o ASP.NET Core é associado a http://localhost:5000 . Configure prefixos de URL e portas nas
quais o Kestrel escuta chamando métodos Listen ou ListenUnixSocket em KestrelServerOptions . (
UseUrls , o argumento de linha de comando urls e a variável de ambiente ASPNETCORE_URLS também
funcionam, mas têm as limitações indicadas mais adiante neste artigo.)
Associar a um soquete TCP
O método Listen é associado a um soquete TCP e um lambda de opções permite que você configure um
certificado SSL:
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}

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


WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseKestrel(options =>
{
options.Listen(IPAddress.Loopback, 5000);
options.Listen(IPAddress.Loopback, 5001, listenOptions =>
{
listenOptions.UseHttps("testCert.pfx", "testPassword");
});
})
.Build();

Observe como esse exemplo configura o SSL de um ponto de extremidade específico usando
ListenOptions. Use a mesma API para definir outras configurações do Kestrel para pontos de extremidade
específicos.
For generating self-signed SSL certificates on Windows, you can use the PowerShell cmdlet New -
SelfSignedCertificate. For a third-party tool that makes it easier for you to generate self-signed certificates,
see SelfCert.
On macOS and Linux you can create a self-signed certificate using OpenSSL.
Associar a um soquete do UNIX
Escute em um soquete do UNIX para um melhor desempenho com o Nginx, conforme mostrado neste
exemplo:

.UseKestrel(options =>
{
options.ListenUnixSocket("/tmp/kestrel-test.sock");
options.ListenUnixSocket("/tmp/kestrel-test.sock", listenOptions =>
{
listenOptions.UseHttps("testCert.pfx", "testpassword");
});
})

Porta 0
Se você especificar o número de porta 0, o Kestrel associa-o dinamicamente a uma porta disponível. O
seguinte exemplo mostra como determinar à qual porta o Kestrel realmente está associado em tempo de
execução:
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
var serverAddressesFeature = app.ServerFeatures.Get<IServerAddressesFeature>();

app.UseStaticFiles();

app.Run(async (context) =>


{
context.Response.ContentType = "text/html";
await context.Response
.WriteAsync("<p>Hosted by Kestrel</p>");

if (serverAddressesFeature != null)
{
await context.Response
.WriteAsync("<p>Listening on the following addresses: " +
string.Join(", ", serverAddressesFeature.Addresses) +
"</p>");
}

await context.Response.WriteAsync($"<p>Request URL: {context.Request.GetDisplayUrl()}<p>");


});
}

Limitações de UseUrls
Configure pontos de extremidade chamando o método UseUrls ou usando o argumento de linha de
comando urls ou a variável de ambiente ASPNETCORE_URLS. Esses métodos são úteis se você deseja
que o código funcione com servidores que não sejam o Kestrel. No entanto, esteja ciente dessas limitações:
Não é possível usar o SSL com esses métodos.
Se você usar o método Listen e UseUrls , os pontos de extremidade Listen substituirão os pontos de
extremidade UseUrls .

Configuração de ponto de extremidade para o IIS


Se você usar o IIS, as associações de URL do IIS substituirão as associações definidas com uma chamada a
Listen ou UseUrls . Para obter mais informações, consulte Introdução ao Módulo do ASP.NET Core.

Prefixos de URL
Se você chamar UseUrls ou usar o argumento de linha de comando urls ou a variável de ambiente
ASPNETCORE_URLS, os prefixos de URL poderão estar em um dos formatos a seguir.
ASP.NET Core 2.x
ASP.NET Core 1.x
Somente prefixos de URL HTTP são válidos. O Kestrel não dá suporte ao SSL quando associações de URL
são configuradas com UseUrls .
Endereço IPv4 com o número da porta

http://65.55.39.10:80/

0.0.0.0 é um caso especial que é associado a todos os endereços IPv4.


Endereço IPv6 com número da porta

http://[0:0:0:0:0:ffff:4137:270a]:80/
[::] é o equivalente de IPv6 do IPv4 0.0.0.0.
Nome do host com o número da porta

http://contoso.com:80/
http://*:80/

Nomes de host, * e +, não são especiais. Tudo o que não é um endereço IP reconhecido ou o
"localhost" será associado a todos os IPs do IPv4 e IPv6. Se você precisa associar diferentes nomes
de host a diferentes aplicativos ASP.NET Core na mesma porta, use o HTTP.sys ou um servidor proxy
reverso, como o IIS, Nginx ou Apache.
Nome do "localhost" com o número da porta ou IP de loopback com o número da porta

http://localhost:5000/
http://127.0.0.1:5000/
http://[::1]:5000/

Quando localhost é especificado, o Kestrel tenta associar a interfaces de loopback IPv4 e IPv6. Se a
porta solicitada está sendo usada por outro serviço em uma das interfaces de loopback, o Kestrel
falha ao ser iniciado. Se uma das interfaces de loopback não estiver disponível por qualquer outro
motivo (geralmente porque não há suporte para o IPv6), o Kestrel registra um aviso em log.

Próximas etapas
Para obter mais informações, consulte os seguintes recursos:
ASP.NET Core 2.x
ASP.NET Core 1.x
Aplicativo de exemplo para o 2.x
Código-fonte do kestrel
Introdução ao Módulo do ASP.NET Core
08/02/2018 • 13 min to read • Edit Online

Por Tom Dykstra, Rick Strahl e Chris Ross


O ANCM (Módulo do ASP.NET Core) permite que você execute aplicativos ASP.NET Core protegidos
pelo IIS, usando o IIS para o que ele é bom (segurança, capacidade de gerenciamento e muito mais) e
usando o Kestrel para o que ele é bom (ser realmente rápido) e obtenha os benefícios de ambas as
tecnologias ao mesmo tempo. O ANCM funciona apenas com o Kestrel; não é compatível com o
WebListener (no ASP.NET Core 1.x) ou HTTP.sys (no 2.x).
Versões do Windows compatíveis:
Windows 7 e Windows Server 2008 R2 e posterior
Exibir ou baixar código de exemplo (como baixar)

O que o Módulo do ASP.NET Core faz


O ANCM é um módulo nativo do IIS que é vinculado ao pipeline do IIS e redireciona o tráfego para o
aplicativo ASP.NET Core back-end. A maioria dos outros módulos, como a autenticação do Windows,
ainda obtém uma oportunidade de ser executada. O ANCM apenas assume o controle quando um
manipulador é selecionado para a solicitação e o mapeamento de manipulador é definido no arquivo
web.config do aplicativo.
Como os aplicativos ASP.NET Core são executados em um processo separado do processo de trabalho
do IIS, o ANCM também realiza o gerenciamento de processos. O ANCM inicia o processo para o
aplicativo ASP.NET Core quando a primeira solicitação é recebida e reinicia-o quando ele falha.
Basicamente, esse é o mesmo comportamento dos aplicativos ASP.NET clássicos executados em
processo no IIS e gerenciados pelo WAS (Serviço de Ativação do Windows).
Veja a seguir um diagrama que ilustra a relação entre o IIS, o ANCM e aplicativos ASP.NET Core.

As solicitações são recebidas da Web e chegam ao driver Http.Sys de modo kernel, que encaminha-as
para o IIS na porta primária (80) ou porta SSL (443). O ANCM encaminha as solicitações para o
aplicativo ASP.NET Core na porta HTTP configurada para o aplicativo, que não é a porta 80/443.
O Kestrel ouve o tráfego proveniente do ANCM. O ANCM especifica a porta por meio da variável de
ambiente na inicialização e o método UseIISIntegration configura o servidor para escutar em
http://localhost:{port} . Existem verificações adicionais para rejeitar as solicitações não provenientes do
ANCM. (O ANCM não dá suporte ao encaminhamento de HTTPS e, portanto, as solicitações são
encaminhadas por HTTP, mesmo se forem recebidas pelo IIS por HTTPS.)
O Kestrel coleta solicitações do ANCM e envia-as por push para o pipeline de middleware do ASP.NET
Core, que as manipula e as passa como instâncias HttpContext para a lógica do aplicativo. As respostas
do aplicativo são passadas novamente para o IIS, que as envia novamente para o cliente HTTP que
iniciou as solicitações.
O ANCM também tem algumas outras funções:
Define as variáveis de ambiente.
Registra em log a saída stdout no armazenamento de arquivos.
Encaminha tokens de autenticação do Windows.

Como usar o ANCM em aplicativos ASP.NET Core


Esta seção fornece uma visão geral do processo para configurar um servidor IIS e um aplicativo ASP.NET
Core. Para obter instruções detalhadas, consulte Hospedar no Windows com o IIS.
Instalar o ANCM
O Módulo do ASP.NET Core precisa ser instalado no IIS nos servidores e no IIS Express nos
computadores de desenvolvimento. Para servidores, o ANCM está incluído no pacote de Hospedagem
do Windows Server do .NET Core. Para computadores de desenvolvimento, o Visual Studio instala o
ANCM automaticamente no IIS Express e no IIS, caso ele já esteja instalado no computador.
Instalar o pacote NuGet IISIntegration
ASP.NET Core 2.x
ASP.NET Core 1.x
O pacote Microsoft.AspNetCore.Server.IISIntegration está incluído nos metapacotes do ASP.NET Core
(Microsoft.AspNetCore e Microsoft.AspNetCore.All). Caso não use um dos metapacotes, instale
Microsoft.AspNetCore.Server.IISIntegration separadamente. O pacote IISIntegration é um pacote de
interoperabilidade que lê as variáveis de ambiente difundidas pelo ANCM para configurar seu aplicativo.
As variáveis de ambiente fornecem informações de configuração, como a porta na qual escutar.
Chamar UseIISIntegration
ASP.NET Core 2.x
ASP.NET Core 1.x
O método de extensão UseIISIntegration no WebHostBuilder é chamado automaticamente quando você
executa com o IIS.
Caso não esteja usando um dos metapacotes do ASP.NET Core e não tenha instalado o pacote
Microsoft.AspNetCore.Server.IISIntegration , você obterá um erro de tempo de execução. Se você chamar
UseIISIntegration de forma explícita, receberá um erro de tempo de compilação se o pacote não estiver
instalado.
O método UseIISIntegration procura as variáveis de ambiente definidas pelo ANCM e fica inoperante
caso elas não sejam encontradas. Esse comportamento facilita cenários como o desenvolvimento e teste
no macOS ou Linux e a implantação em um servidor que executa o IIS. Durante a execução no macOS
ou Linux, o Kestrel atua como o servidor Web; mas quando o aplicativo é implantado no ambiente do IIS,
ele usa o ANCM e o IIS automaticamente.
A associação de porta do ANCM substitui outras associações de porta
ASP.NET Core 2.x
ASP.NET Core 1.x
O ANCM gera uma porta dinâmica a ser atribuída ao processo de back-end. O método
UseIISIntegration seleciona essa porta dinâmica e configura o Kestrel para escutar em
http://locahost:{dynamicPort}/ . Isso substitui outras configurações de URL, como chamadas a UseUrls
ou à API de Escuta do Kestrel. Portanto, não é necessário chamar UseUrls ou a API Listen do Kestrel
quando você usar o ANCM. Se você chamar UseUrls ou Listen , o Kestrel escutará na porta
especificada quando você executar o aplicativo sem o IIS.
Configurar opções do ANCM em Web.config
A configuração do Módulo do ASP.NET Core é armazenada no arquivo web.config localizado na pasta
raiz do aplicativo. As configurações desse arquivo apontam para o comando de inicialização e os
argumentos que iniciam o aplicativo ASP.NET Core. Para obter um código de exemplo de web.config e
diretrizes sobre opções de configuração, consulte Referência de configuração do Módulo do ASP.NET
Core.
Executar com o IIS Express em desenvolvimento
O IIS Express pode ser iniciado pelo Visual Studio usando o perfil padrão definido pelos modelos do
ASP.NET Core.

A configuração de proxy usa o protocolo HTTP e um token de


emparelhamento
O proxy criado entre o ANCM e o Kestrel usa o protocolo HTTP. O uso de HTTP é uma otimização de
desempenho na qual o tráfego entre o ANCM e o Kestrel ocorre em um endereço de loopback fora do
adaptador de rede. Não há nenhum risco de interceptação do tráfego entre o ANCM e o Kestrel em um
local fora do servidor.
Um token de emparelhamento é usado para assegurar que as solicitações recebidas pelo Kestrel foram
transmitidas por proxy pelo IIS e que não são provenientes de outra origem. O token de
emparelhamento é criado e definido em uma variável de ambiente ( ASPNETCORE_TOKEN ) pelo ANCM. O
token de emparelhamento também é definido em um cabeçalho ( MSAspNetCoreToken ) em cada solicitação
com proxy. O Middleware do IIS verifica cada solicitação recebida para confirmar se o valor de cabeçalho
do token de emparelhamento corresponde ao valor da variável de ambiente. Se os valores do token
forem incompatíveis, a solicitação será registrada em log e rejeitada. A variável de ambiente do token de
emparelhamento e o tráfego entre o ANCM e o Kestrel não são acessíveis em um local fora do servidor.
Sem saber o valor do token de emparelhamento, um invasor não pode enviar solicitações que ignoram a
verificação no Middleware do IIS.

Próximas etapas
Para obter mais informações, consulte os seguintes recursos:
Aplicativo de exemplo para este artigo
Código-fonte do Módulo do ASP.NET Core
Referência de configuração do módulo do ASP.NET Core
Hospedar no Windows com o IIS
Implementação do servidor Web HTTP.sys no
ASP.NET Core
15/03/2018 • 13 min to read • Edit Online

Por Tom Dykstra, Chris Ross e Luke Latham

OBSERVAÇÃO
Este tópico se aplica ao ASP.NET Core 2.0 ou versão superior. Em versões anteriores do ASP.NET Core, o HTTP.sys é
chamado WebListener.

O HTTP.sys é um servidor Web para ASP.NET Core executado apenas no Windows. O HTTP.sys é uma
alternativa ao Kestrel e oferece alguns recursos que não são disponibilizados por esse servidor Web.

IMPORTANTE
Não é possível usar o HTTP.sys com o IIS ou o IIS Express, pois ele é incompatível com o Módulo do ASP.NET Core.

O HTTP.sys dá suporte aos seguintes recursos:


Autenticação do Windows
Compartilhamento de porta
HTTPS com SNI
HTTP/2 sobre TLS (Windows 10 ou posterior)
Transmissão direta de arquivo
Cache de resposta
WebSockets (Windows 8 ou posterior)
Versões do Windows compatíveis:
Windows 7 ou posterior
Windows Server 2008 R2 ou posterior
Exibir ou baixar código de exemplo (como baixar)

Quando usar o HTTP.sys


O HTTP.sys é útil nas implantações em que:
É necessário expor o servidor diretamente à Internet sem usar o IIS.

As implantações internas exigem um recurso que não está disponível no Kestrel, como a Autenticação
do Windows.
O HTTP.sys é uma tecnologia madura que protege contra vários tipos de ataques e proporciona as
propriedades de robustez, segurança e escalabilidade de um servidor Web completo. O próprio IIS é
executado como um ouvinte HTTP sobre o HTTP.sys.

Como usar o HTTP.sys


Configurar o aplicativo ASP.NET Core para usar o HTTP.sys
1. Não é necessário usar uma referência do pacote no arquivo de projeto com o metapacote
Microsoft.AspNetCore.All (nuget.org) (ASP.NET Core 2.0 ou posterior). Se não estiver usando o
metapacote Microsoft.AspNetCore.All , adicione uma referência do pacote a
Microsoft.AspNetCore.Server.HttpSys.
2. Chame o método de extensão UseHttpSys quando criar o Host Web, especificando todas as opções
do HTTP.sys necessárias:

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


WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseHttpSys(options =>
{
// The following options are set to default values.
options.Authentication.Schemes = AuthenticationSchemes.None;
options.Authentication.AllowAnonymous = true;
options.MaxConnections = null;
options.MaxRequestBodySize = 30000000;
options.UrlPrefixes.Add("http://localhost:5000");
})
.Build();

A configuração adicional do HTTP.sys é tratada por meio das configurações do registro.


Opções do HTTP.sys

PROPRIEDADE DESCRIÇÃO PADRÃO

AllowSynchronousIO Controlar quando a Entrada/Saída true


síncrona deve ser permitida para
HttpContext.Request.Body e
HttpContext.Response.Body .

Authentication.AllowAnonymous Permitir solicitações anônimas. true

Authentication.Schemes Especificar os esquemas de None


autenticação permitidos. É possível
modificar a qualquer momento
antes de descartar o ouvinte. Os
valores são fornecidos pela
enumeração
AuthenticationSchemes: Basic ,
Kerberos , Negotiate , None e
NTLM .
PROPRIEDADE DESCRIÇÃO PADRÃO

EnableResponseCaching Tentativa de cache do modo kernel true


para obtenção de respostas com
cabeçalhos qualificados. A resposta
pode não incluir Set-Cookie ,
Vary ou cabeçalhos Pragma . Ela
deve incluir um cabeçalho
Cache-Control que seja public
e um valor shared-max-age ou
max-age , ou um cabeçalho
Expires .

MaxAccepts O número máximo de aceitações 5 × Ambiente.


simultâneas. ProcessorCount

MaxConnections O número máximo de conexões null


simultâneas a serem aceitas. Usar (ilimitado)
-1 como infinito. Usar null a
fim de usar a configuração que
abranja toda máquina do registro.

MaxRequestBodySize Confira a seção 30.000.000 de bytes


MaxRequestBodySize. (28,6 MB)

RequestQueueLimit O número máximo de solicitações 1000


que podem ser colocadas na fila.

ThrowWriteExceptions Indica se as gravações do corpo da false


resposta que falham quando o (concluir normalmente)
cliente se desconecta devem gerar
exceções ou serem concluídas
normalmente.
PROPRIEDADE DESCRIÇÃO PADRÃO

Timeouts Expor a configuração


TimeoutManager do HTTP.sys, que
também pode ser definida no
registro. Siga os links de API para
saber mais sobre cada
configuração, inclusive os valores
padrão:
Timeouts.DrainEntityBody –
O tempo permitido para
que a API do servidor HTTP
esvazie o corpo da entidade
em uma conexão keep alive.
Timeouts.EntityBody – O
tempo permitido para a
chegada do corpo da
entidade de solicitação.
Timeouts.HeaderWait – O
tempo permitido para que
a API do servidor HTTP
analise o cabeçalho da
solicitação.
Timeouts.IdleConnection –
O tempo permitido para
uma conexão ociosa.
Timeouts.MinSendBytesPer
Second – A taxa de envio
mínima para a resposta.
Timeouts.RequestQueue –
O tempo permitido para
que a solicitação permaneça
na fila de solicitações até o
aplicativo coletá-la.

UrlPrefixes Especifique a UrlPrefixCollection


para registrar com o HTTP.sys. A
mais útil é UrlPrefixCollection.Add,
que é usada para adicionar um
prefixo à coleção. É possível
modificá-las a qualquer momento
antes de descartar o ouvinte.

MaxRequestBodySize
O tamanho máximo permitido em bytes para todos os corpos de solicitação. Quando é definido como
null , o tamanho máximo do corpo da solicitação é ilimitado. Esse limite não afeta as conexões
atualizadas que são sempre ilimitadas.
O método recomendado para substituir o limite em um aplicativo ASP.NET Core MVC para um único
IActionResult é usar o atributo RequestSizeLimitAttribute em um método de ação:

[RequestSizeLimit(100000000)]
public IActionResult MyActionMethod()

Uma exceção é gerada quando o aplicativo tenta configurar o limite de uma solicitação, depois que o
aplicativo inicia a leitura da solicitação. É possível usar uma propriedade IsReadOnly para indicar se a
propriedade MaxRequestBodySize está no estado somente leitura, o que significa que é tarde demais
para configurar o limite.
Se o aplicativo tiver que substituir MaxRequestBodySize por solicitação, use
IHttpMaxRequestBodySizeFeature:

public void Configure(IApplicationBuilder app, IHostingEnvironment env,


ILogger<Startup> logger)
{
app.Use(async (context, next) =>
{
context.Features.Get<IHttpMaxRequestBodySizeFeature>()
.MaxRequestBodySize = 10 * 1024;

var serverAddressesFeature = app.ServerFeatures.Get<IServerAddressesFeature>();


var addresses = string.Join(", ", serverAddressesFeature?.Addresses);

logger.LogInformation($"Addresses: {addresses}");

await next.Invoke();
});

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();
app.UseMvc();
}

3. Quando usar o Visual Studio, verifique se que o aplicativo está configurado para executar o IIS ou IIS
Express.
No Visual Studio, o perfil de inicialização padrão destina-se ao IIS Express. Para executar o projeto
como um aplicativo de console, altere manualmente o perfil selecionado, conforme mostrado na
captura de tela a seguir:

Configurar o Windows Server


1. Se o aplicativo for uma implantação dependente de estrutura, instale o .NET Core, o .NET Framework
ou ambos (caso o aplicativo .NET Core seja direcionado ao .NET Framework).
.NET Core – Se o aplicativo exigir o .NET Core, baixe e execute o instalador do .NET Core da
página de Downloads do .NET.
.NET Framework – Se o aplicativo exigir o .NET Framework, confira Guia de instalação do .NET
Framework para obter instruções de instalação. Instale o .NET Framework necessário. É possível
encontrar o instalador para o .NET Framework mais recente na página de Downloads do .NET.
2. Configurar URLs e portas para o aplicativo.
Por padrão, o ASP.NET Core é associado a http://localhost:5000 . Para configurar portas e prefixos
de URL, as opções incluem usar:
UseUrls
O argumento de linha de comando urls
A variável de ambiente ASPNETCORE_URLS
UrlPrefixes
O exemplo de código a seguir mostra como usar UrlPrefixes:

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


WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseHttpSys(options =>
{
// The following options are set to default values.
options.Authentication.Schemes = AuthenticationSchemes.None;
options.Authentication.AllowAnonymous = true;
options.MaxConnections = null;
options.MaxRequestBodySize = 30000000;
options.UrlPrefixes.Add("http://localhost:5000");
})
.Build();

Uma vantagem de usar UrlPrefixes é que uma mensagem de erro é gerada imediatamente no caso
de prefixos formatados de forma incorreta.
As configurações de UrlPrefixes substituem as configurações UseUrls / urls / ASPNETCORE_URLS .
Portanto, uma vantagem de usar UseUrls , urls e a variável de ambiente ASPNETCORE_URLS é que fica
mais fácil alternar entre o Kestrel e o HTTP.sys. Para saber mais sobre UseUrls , urls e
ASPNETCORE_URLS , confira o tópico Hospedagem.

O HTTP.sys usa os formatos de cadeia de caracteres UrlPrefix da API do Servidor HTTP.

AVISO
Associações de curinga de nível superior ( http://*:80/ e http://+:80 ) não devem ser usadas. Associações
de curinga de nível superior podem abrir o aplicativo para vulnerabilidades de segurança. Isso se aplica a
curingas fortes e fracos. Use nomes de host explícitos em vez de curingas. Associações de curinga de
subdomínio (por exemplo, *.mysub.com ) não têm esse risco de segurança se você controlar o domínio pai
completo (em vez de *.com , o qual é vulnerável). Veja rfc7230 section-5.4 para obter mais informações.

3. Faça o pré-registro dos prefixos de URL para associá-los ao HTTP.sys e configurar certificados X.509.
Se os prefixos de URL não estiverem pré-registrados no Windows, execute o aplicativo com
privilégios de administrador. A única exceção ocorre durante a associação ao localhost usando HTTP
(não HTTPS ) com um número de porta superior a 1024. Nesse caso, não é necessário usar privilégios
de administrador.
a. O netsh.exe é a ferramenta interna destinada a configurar o HTTP.sys. Com o netsh.exe, é
possível reservar prefixos de URL e atribuir certificados X.509. A ferramenta exige privilégios
de administrador.
O exemplo a seguir mostra os comandos necessários para reservar prefixos de URL para as
portas 80 e 443:

netsh http add urlacl url=http://+:80/ user=Users


netsh http add urlacl url=https://+:443/ user=Users
O exemplo a seguir mostra como atribuir um certificado X.509:

netsh http add sslcert ipport=0.0.0.0:443 certhash=MyCertHash_Here appid={00000000-0000-


0000-0000-000000000000}"

Documentação de referência do netsh.exe:


Comandos do Netsh para o protocolo HTTP
Cadeias de caracteres de UrlPrefix
b. Crie certificados X.509 autoassinados, quando necessário.
No Windows, é possível criar certificados autoassinados usando o cmdlet New -SelfSignedCertificate
do PowerShell. Para ver um exemplo sem suporte, confira UpdateIISExpressSSLForChrome.ps1.
Nas plataformas macOS, Linux e Windows, é possível criar certificados usando o OpenSSL.
4. Abra as portas do firewall para permitir que o tráfego chegue ao HTTP.sys. Use o netsh.exe ou os
cmdlets do PowerShell.

Recursos adicionais
API do servidor HTTP
Repositório aspnet/HttpSysServer do GitHub (código-fonte)
Hospedagem
Globalização e localização no ASP.NET Core
12/02/2018 • 30 min to read • Edit Online

Por Rick Anderson, Damien Bowden, Bart Calixto, Nadeem Afana e Hisham Bin Ateya
A criação de um site multilíngue com o ASP.NET Core permitirá que seu site alcance um público maior. O
ASP.NET Core fornece serviços e middleware para localização em diferentes idiomas e culturas.
A internacionalização envolve Globalização e Localização. Globalização é o processo de criação de aplicativos
que dão suporte a diferentes culturas. A globalização adiciona suporte para entrada, exibição e saída de um
conjunto definido de scripts de idiomas relacionados a áreas geográficas específicas.
Localização é o processo de adaptar um aplicativo globalizado, que você já processou para possibilidade de
localização, a determinada cultura/localidade. Para obter mais informações, consulte Termos de
globalização e localização próximo ao final deste documento.
A localização de aplicativos envolve o seguinte:
1. Tornar o conteúdo do aplicativo localizável
2. Fornecer recursos localizados para as culturas e os idiomas aos quais você dá suporte
3. Implementar uma estratégia para selecionar o idioma e a cultura para cada solicitação

Tornar o conteúdo do aplicativo localizável


Introduzidos no ASP.NET Core IStringLocalizer e IStringLocalizer<T> foram projetados para melhorar a
produtividade ao desenvolver aplicativos localizados. IStringLocalizer usa o ResourceManager e o
ResourceReader para fornecer recursos específicos a uma cultura em tempo de execução. A interface simples
tem um indexador e um IEnumerable para retornar cadeias de caracteres localizadas. IStringLocalizer não
exige o armazenamento das cadeias de caracteres de idioma padrão em um arquivo de recurso. Você pode
desenvolver um aplicativo direcionado à localização e não precisa criar arquivos de recurso no início do
desenvolvimento. O código abaixo mostra como encapsular a cadeia de caracteres "About Title" para
localização.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;

namespace Localization.StarterWeb.Controllers
{
[Route("api/[controller]")]
public class AboutController : Controller
{
private readonly IStringLocalizer<AboutController> _localizer;

public AboutController(IStringLocalizer<AboutController> localizer)


{
_localizer = localizer;
}

[HttpGet]
public string Get()
{
return _localizer["About Title"];
}
}
}

No código acima, a implementação IStringLocalizer<T> é obtida da Injeção de Dependência. Se o valor


localizado de "About Title" não é encontrado, a chave do indexador é retornada, ou seja, a cadeia de caracteres
"About Title". Deixe as cadeias de caracteres literais de idioma padrão no aplicativo e encapsule-as no
localizador, de modo que você possa se concentrar no desenvolvimento do aplicativo. Você pode desenvolve
seu aplicativo com o idioma padrão e prepará-lo para a etapa de localização sem primeiro criar um arquivo de
recurso padrão. Como alternativa, você pode usar a abordagem tradicional e fornecer uma chave para
recuperar a cadeia de caracteres de idioma padrão. Para muitos desenvolvedores, o novo fluxo de trabalho de
não ter um arquivo .resx de idioma padrão e simplesmente encapsular os literais de cadeia de caracteres pode
reduzir a sobrecarga de localizar um aplicativo. Outros desenvolvedores preferirão o fluxo de trabalho
tradicional, pois ele pode facilitar o trabalho com literais de cadeia de caracteres mais longas e a atualização de
cadeias de caracteres localizadas.
Use a implementação IHtmlLocalizer<T> para recursos que contêm HTML. O IHtmlLocalizer codifica em
HTML os argumentos que são formatados na cadeia de caracteres de recurso, mas não codifica em HTML a
cadeia de caracteres de recurso em si. Na amostra realçada abaixo, apenas o valor do parâmetro name é
codificado em HTML.
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;

namespace Localization.StarterWeb.Controllers
{
public class BookController : Controller
{
private readonly IHtmlLocalizer<BookController> _localizer;

public BookController(IHtmlLocalizer<BookController> localizer)


{
_localizer = localizer;
}

public IActionResult Hello(string name)


{
ViewData["Message"] = _localizer["<b>Hello</b><i> {0}</i>", name];

return View();
}

Observação: em geral, recomendamos localizar somente o texto e não o HTML.


No nível mais baixo, você pode obter IStringLocalizerFactory com a Injeção de Dependência:

{
public class TestController : Controller
{
private readonly IStringLocalizer _localizer;
private readonly IStringLocalizer _localizer2;

public TestController(IStringLocalizerFactory factory)


{
var type = typeof(SharedResource);
var assemblyName = new AssemblyName(type.GetTypeInfo().Assembly.FullName);
_localizer = factory.Create(type);
_localizer2 = factory.Create("SharedResource", assemblyName.Name);
}

public IActionResult About()


{
ViewData["Message"] = _localizer["Your application description page."]
+ " loc 2: " + _localizer2["Your application description page."];

O código acima demonstra cada um dos dois métodos Create de alocador.


Você pode particionar as cadeias de caracteres localizadas por controlador, área ou ter apenas um contêiner.
No aplicativo de exemplo, uma classe fictícia chamada SharedResource é usada para recursos compartilhados.

// Dummy class to group shared resources

namespace Localization.StarterWeb
{
public class SharedResource
{
}
}
Alguns desenvolvedores usam a classe Startup para conter cadeias de caracteres globais ou compartilhadas.
Na amostra abaixo, os localizadores InfoController e SharedResource são usados:

public class InfoController : Controller


{
private readonly IStringLocalizer<InfoController> _localizer;
private readonly IStringLocalizer<SharedResource> _sharedLocalizer;

public InfoController(IStringLocalizer<InfoController> localizer,


IStringLocalizer<SharedResource> sharedLocalizer)
{
_localizer = localizer;
_sharedLocalizer = sharedLocalizer;
}

public string TestLoc()


{
string msg = "Shared resx: " + _sharedLocalizer["Hello!"] +
" Info resx " + _localizer["Hello!"];
return msg;
}

Localização de exibição
O serviço fornece cadeias de caracteres localizadas para uma exibição. A classe
IViewLocalizer
ViewLocalizer implementa essa interface e encontra o local do recurso no caminho do arquivo de exibição. O
seguinte código mostra como usar a implementação padrão de IViewLocalizer :

@using Microsoft.AspNetCore.Mvc.Localization

@inject IViewLocalizer Localizer

@{
ViewData["Title"] = Localizer["About"];
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<p>@Localizer["Use this area to provide additional information."]</p>

A implementação padrão de IViewLocalizer encontra o arquivo de recurso com base no nome de arquivo da
exibição. Não há nenhuma opção para usar um arquivo de recurso compartilhado global. ViewLocalizer
implementa o localizador usando IHtmlLocalizer e, portanto, o Razor não codifica em HTML a cadeia de
caracteres localizada. Parametrize cadeias de recurso e o IViewLocalizer codificará em HTML os parâmetros,
mas não a cadeia de caracteres de recurso. Considere a seguinte marcação do Razor:

@Localizer["<i>Hello</i> <b>{0}!</b>", UserManager.GetUserName(User)]

Um arquivo de recurso em francês pode conter o seguinte:

CHAVE VALOR

<i>Hello</i> <b>{0}!</b> <i>Bonjour</i> <b>{0} !</b>

A exibição renderizada contém a marcação HTML do arquivo de recurso.


Observação: em geral, recomendamos localizar somente o texto e não o HTML.
Para usar um arquivo de recurso compartilhado em uma exibição, injete IHtmlLocalizer<T> :

@using Microsoft.AspNetCore.Mvc.Localization
@using Localization.StarterWeb.Services

@inject IViewLocalizer Localizer


@inject IHtmlLocalizer<SharedResource> SharedLocalizer

@{
ViewData["Title"] = Localizer["About"];
}
<h2>@ViewData["Title"].</h2>

<h1>@SharedLocalizer["Hello!"]</h1>

Localização de DataAnnotations
As mensagens de erro de DataAnnotations são localizadas com IStringLocalizer<T> . Usando a opção
ResourcesPath = "Resources" , as mensagens de erro em RegisterViewModel podem ser armazenadas em um
dos seguintes caminhos:
Resources/ViewModels.Account.RegisterViewModel.fr.resx
Resources/ViewModels/Account/RegisterViewModel.fr.resx

public class RegisterViewModel


{
[Required(ErrorMessage = "The Email field is required.")]
[EmailAddress(ErrorMessage = "The Email field is not a valid email address.")]
[Display(Name = "Email")]
public string Email { get; set; }

[Required(ErrorMessage = "The Password field is required.")]


[StringLength(8, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }

[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}

No ASP.NET Core MVC 1.1.0 e superior, atributos que não sejam de validação são localizados. O ASP.NET
Core MVC 1.0 não pesquisa cadeias de caracteres localizadas para atributos que não sejam de validação.
Usando uma cadeia de caracteres de recurso para várias classes
O seguinte código mostra como usar uma cadeia de caracteres de recurso para atributos de validação com
várias classes:

public void ConfigureServices(IServiceCollection services)


{
services.AddMvc()
.AddDataAnnotationsLocalization(options => {
options.DataAnnotationLocalizerProvider = (type, factory) =>
factory.Create(typeof(SharedResource));
});
}
No código anterior, SharedResource é a classe correspondente ao resx em que as mensagens de validação são
armazenadas. Com essa abordagem, DataAnnotations usará apenas SharedResource , em vez de o recurso
para cada classe.

Fornecer recursos localizados para as culturas e os idiomas aos quais


você dá suporte
SupportedCultures e SupportedUICultures
O ASP.NET Core permite que você especifique dois valores de cultura, SupportedCultures e
SupportedUICultures . O objeto CultureInfo para SupportedCultures determina os resultados das funções
dependentes de cultura, como data, hora, número e formatação de moeda. SupportedCultures também
determina a ordem de classificação de texto, convenções de uso de maiúsculas e comparações de cadeia de
caracteres. Consulte CultureInfo.CurrentCulture para obter mais informações sobre como o servidor obtém a
Cultura. O SupportedUICultures determina quais cadeias de caracteres traduzidas (de arquivos .resx) são
pesquisadas pelo ResourceManager. O ResourceManager apenas pesquisa cadeias de caracteres específicas a
uma cultura determinadas por CurrentUICulture . Cada thread no .NET tem objetos CurrentCulture e
CurrentUICulture . O ASP.NET Core inspeciona esses valores durante a renderização de funções dependentes
de cultura. Por exemplo, se a cultura do thread atual estiver definida como "en-US" (inglês, Estados Unidos),
DateTime.Now.ToLongDateString() exibirá "Thursday, February 18, 2016", mas se CurrentCulture estiver
definida como "es-ES" (espanhol, Espanha), o resultado será "jueves, 18 de febrero de 2016".

Arquivos de recurso
Um arquivo de recurso é um mecanismo útil para separar cadeias de caracteres localizáveis do código.
Cadeias de caracteres traduzidas para o idioma não padrão são arquivos de recurso .resx isolados. Por
exemplo, talvez você queira criar um arquivo de recurso em espanhol chamado Welcome.es.resx contendo
cadeias de caracteres traduzidas. "es" são o código de idioma para o espanhol. Para criar esse arquivo de
recurso no Visual Studio:
1. No Gerenciador de Soluções, clique com o botão direito do mouse na pasta que conterá o arquivo de
recurso > Adicionar > Novo Item.
2. Na caixa Pesquisar modelos instalados, insira "recurso" e nomeie o arquivo.

3. Insira o valor da chave (cadeia de caracteres nativa) na coluna Nome e a cadeia de caracteres traduzida
na coluna Valor.
O Visual Studio mostra o arquivo Welcome.es.resx.

Se estiver usando o Visual Studio 2017 Preview versão 15.3, você obterá um indicador de erro no editor de
recursos. Remova o valor ResXFileCodeGenerator da grade de propriedades de Ferramenta Personalizada
para evitar essa mensagem de erro:
Como alternativa, você pode ignorar esse erro. Esperamos corrigi-lo na próxima versão.

Nomenclatura do arquivo de recurso


Os recursos são nomeados com o nome completo do tipo de sua classe menos o nome do assembly. Por
exemplo, um recurso em francês em um projeto cujo assembly principal é LocalizationWebsite.Web.dll para a
classe LocalizationWebsite.Web.Startup será nomeado Startup.fr.resx. Um recurso para a classe
LocalizationWebsite.Web.Controllers.HomeController será nomeado Controllers.HomeController.fr.resx. Se o
namespace da classe de destino não for o mesmo que o nome do assembly, você precisará do nome completo
do tipo. Por exemplo, no projeto de exemplo, um recurso para o tipo ExtraNamespace.Tools será nomeado
ExtraNamespace.Tools.fr.resx.
No projeto de exemplo, o método ConfigureServices define o ResourcesPath como "Resources", de modo
que o caminho relativo do projeto para o arquivo de recurso em francês do controlador principal seja
Resources/Controllers.HomeController.fr.resx. Como alternativa, você pode usar pastas para organizar arquivos
de recurso. Para o controlador principal, o caminho será Resources/Controllers/HomeController.fr.resx. Se você
não usar a opção ResourcesPath , o arquivo .resx entrará no diretório base do projeto. O arquivo de recurso
para HomeController será nomeado Controllers.HomeController.fr.resx. A opção de usar a convenção de
nomenclatura de ponto ou caminho depende de como você deseja organizar os arquivos de recurso.

NOME DO RECURSO NOMENCLATURA DE PONTO OU CAMINHO

Resources/Controllers.HomeController.fr.resx Ponto

Resources/Controllers/HomeController.fr.resx Caminho

Os arquivos de recurso que usam @inject IViewLocalizer em exibições do Razor seguem um padrão
semelhante. O arquivo de recurso de uma exibição pode ser nomeado usando a nomenclatura de ponto ou de
caminho. Os arquivos de recurso da exibição do Razor simulam o caminho de seu arquivo de exibição
associado. Supondo que definimos o ResourcesPath como "Resources", o arquivo de recurso em francês
associado à exibição Views/Home/About.cshtml pode ser um dos seguintes:
Resources/Views/Home/About.fr.resx
Resources/Views.Home.About.fr.resx
Se você não usar a opção ResourcesPath , o arquivo .resx de uma exibição estará localizado na mesma pasta da
exibição.

Comportamento de fallback da cultura


Por exemplo, se você remover o designador de cultura ".fr" e tiver a cultura definida como francês, o arquivo
de recurso padrão será lido e as cadeias de caracteres serão localizadas. O Gerenciador de Recursos designa
um padrão ou um recurso de fallback para quando nenhuma opção atende à cultura solicitada. Se você deseja
apenas retornar a chave quando um recurso está ausente para a cultura solicitada, não deve ter um arquivo de
recurso padrão.
Gerar arquivos de recurso com o Visual Studio
Se você criar um arquivo de recurso no Visual Studio sem uma cultura no nome do arquivo (por exemplo,
Welcome.resx), o Visual Studio criará uma classe do C# com uma propriedade para cada cadeia de caracteres.
Em geral, não é isso que você quer obter com o ASP.NET Core; normalmente, você não tem um arquivo de
recurso .resx padrão (um arquivo .resx sem o nome da cultura). Sugerimos que você crie o arquivo .resx com
um nome de cultura (por exemplo Welcome.fr.resx). Quando você criar um arquivo .resx com um nome de
cultura, o Visual Studio não gerará o arquivo de classe. Prevemos que muitos desenvolvedores não criarão
um arquivo de recurso de idioma padrão.
Adicionar outras culturas
Cada combinação de idioma e cultura (que não seja o idioma padrão) exige um arquivo de recurso exclusivo.
Crie arquivos de recurso para diferentes culturas e localidades criando novos arquivos de recurso, nos quais
os códigos ISO e de idioma fazem parte do nome do arquivo (por exemplo, en-us, fr-ca e en-gb). Esses
códigos ISO são colocados entre o nome do arquivo e a extensão de nome de arquivo .resx, como em
Welcome.es-MX.resx (espanhol/México). Para especificar um idioma culturalmente neutro, remova o código
do país ( MX no exemplo anterior). O nome do arquivo de recurso em espanhol culturalmente neutro é
Welcome.es.resx.

Implementar uma estratégia para selecionar o idioma e a cultura


para cada solicitação
Configurar a localização
A localização é configurada no método ConfigureServices :

services.AddLocalization(options => options.ResourcesPath = "Resources");

services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization();

AddLocalizationAdiciona os serviços de localização ao contêiner de serviços. O código acima também


define o caminho de recursos como "Resources".
AddViewLocalization Adiciona suporte para os arquivos de exibição localizados. Nesta amostra, a
localização de exibição se baseia no sufixo do arquivo de exibição. Por exemplo, "fr" no arquivo
Index.fr.cshtml.
AddDataAnnotationsLocalization Adiciona suporte para as mensagens de validação DataAnnotations
localizadas por meio de abstrações IStringLocalizer .
Middleware de localização
A cultura atual em uma solicitação é definida no Middleware de localização. O middleware de localização é
habilitado no método Configure . O middleware de localização precisa ser configurado antes de qualquer
middleware que possa verificar a cultura de solicitação (por exemplo, app.UseMvcWithDefaultRoute() ).
var supportedCultures = new[]
{
new CultureInfo(enUSCulture),
new CultureInfo("en-AU"),
new CultureInfo("en-GB"),
new CultureInfo("en"),
new CultureInfo("es-ES"),
new CultureInfo("es-MX"),
new CultureInfo("es"),
new CultureInfo("fr-FR"),
new CultureInfo("fr"),
};

app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture(enUSCulture),
// Formatting numbers, dates, etc.
SupportedCultures = supportedCultures,
// UI strings that we have localized.
SupportedUICultures = supportedCultures
});

app.UseStaticFiles();
// To configure external authentication,
// see: http://go.microsoft.com/fwlink/?LinkID=532715
app.UseAuthentication();
app.UseMvcWithDefaultRoute();

UseRequestLocalization inicializa um objeto RequestLocalizationOptions . Em cada solicitação, a lista de


RequestCultureProvider em RequestLocalizationOptions é enumerada e o primeiro provedor que pode
determinar com êxito a cultura de solicitação é usado. Os provedores padrão são obtidos da classe
RequestLocalizationOptions :

1. QueryStringRequestCultureProvider
2. CookieRequestCultureProvider
3. AcceptLanguageHeaderRequestCultureProvider

A lista padrão é apresentada do mais específico ao menos específico. Mais adiante neste artigo, veremos
como você pode alterar a ordem e até mesmo adicionar um provedor de cultura personalizado. Se nenhum
dos provedores pode determinar a cultura de solicitação, o DefaultRequestCulture é usado.
QueryStringRequestCultureProvider
Alguns aplicativos usarão uma cadeia de caracteres de consulta para definir a cultura e a cultura da interface
do usuário. Para aplicativos que usam a abordagem do cabeçalho Accept-Language ou do cookie, a adição de
uma cadeia de caracteres de consulta à URL é útil para depurar e testar o código. Por padrão, o
QueryStringRequestCultureProvider é registrado como o primeiro provedor de localização na lista
RequestCultureProvider . Passe os parâmetros culture e ui-culture da cadeia de caracteres de consulta. O
seguinte exemplo define a cultura específica (idioma e região) como espanhol/México:
http://localhost:5000/?culture=es-MX&ui-culture=es-MX

Se você passar somente uma das duas ( culture ou ui-culture ), o provedor da cadeia de caracteres de
consulta definirá os dois valores usando aquela que foi passada. Por exemplo, a definição apenas da cultura
definirá a Culture e a UICulture :
http://localhost:5000/?culture=es-MX

CookieRequestCultureProvider
Em geral, aplicativos de produção fornecerão um mecanismo para definir a cultura com o cookie de cultura do
ASP.NET Core. Use o método MakeCookieValue para criar um cookie.
O CookieRequestCultureProvider DefaultCookieName retorna o nome padrão do cookie usado para
acompanhar as informações de cultura preferencial do usuário. O nome padrão do cookie é
.AspNetCore.Culture .

O formato do cookie é c=%LANGCODE%|uic=%LANGCODE% , em que c é Culture e uic é UICulture , por exemplo:

c=en-UK|uic=en-US

Se você especificar apenas as informações de cultura e a cultura da interface do usuário, a cultura especificada
será usada para as informações de cultura e para a cultura da interface do usuário.
O cabeçalho HTTP Accept-Language
O cabeçalho Accept-Language é configurável na maioria dos navegadores e originalmente foi criado para
especificar o idioma do usuário. Essa configuração indica que o navegador foi definido para enviar ou herdou
do sistema operacional subjacente. O cabeçalho HTTP Accept-Language de uma solicitação do navegador não
é uma maneira infalível de detectar o idioma preferencial do usuário (consulte Definindo preferências de
idioma em um navegador). Um aplicativo de produção deve incluir uma maneira para que um usuário
personalize sua opção de cultura.
Definir o cabeçalho HTTP Accept-Language no IE
1. No ícone de engrenagem, toque em Opções da Internet.
2. Toque em Idiomas.

3. Toque em Definir Preferências de Idioma.


4. Toque em Adicionar um idioma.
5. Adicione o idioma.
6. Toque no idioma e, em seguida, em Mover Para Cima.
Usar um provedor personalizado
Suponha que você deseje permitir que os clientes armazenem seus idiomas e culturas nos bancos de dados.
Você pode escrever um provedor para pesquisar esses valores para o usuário. O seguinte código mostra
como adicionar um provedor personalizado:

private const string enUSCulture = "en-US";

services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new[]
{
new CultureInfo(enUSCulture),
new CultureInfo("fr")
};

options.DefaultRequestCulture = new RequestCulture(culture: enUSCulture, uiCulture: enUSCulture);


options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;

options.RequestCultureProviders.Insert(0, new CustomRequestCultureProvider(async context =>


{
// My custom request culture logic
return new ProviderCultureResult("en");
}));
});

Use RequestLocalizationOptions para adicionar ou remover provedores de localização.


Definir a cultura de forma programática
Este projeto de exemplo Localization.StarterWeb no GitHub contém a interface do usuário para definir a
Culture . O arquivo Views/Shared/_SelectLanguagePartial.cshtml permite que você selecione a cultura na
lista de culturas compatíveis:

@using Microsoft.AspNetCore.Builder
@using Microsoft.AspNetCore.Http.Features
@using Microsoft.AspNetCore.Localization
@using Microsoft.AspNetCore.Mvc.Localization
@using Microsoft.Extensions.Options

@inject IViewLocalizer Localizer


@inject IOptions<RequestLocalizationOptions> LocOptions

@{
var requestCulture = Context.Features.Get<IRequestCultureFeature>();
var cultureItems = LocOptions.Value.SupportedUICultures
.Select(c => new SelectListItem { Value = c.Name, Text = c.DisplayName })
.ToList();
var returnUrl = string.IsNullOrEmpty(Context.Request.Path) ? "~/" : $"~{Context.Request.Path.Value}";
}

<div title="@Localizer["Request culture provider:"] @requestCulture?.Provider?.GetType().Name">


<form id="selectLanguage" asp-controller="Home"
asp-action="SetLanguage" asp-route-returnUrl="@returnUrl"
method="post" class="form-horizontal" role="form">
<label asp-for="@requestCulture.RequestCulture.UICulture.Name">@Localizer["Language:"]</label>
<select name="culture"
onchange="this.form.submit();"
asp-for="@requestCulture.RequestCulture.UICulture.Name" asp-items="cultureItems">
</select>
</form>
</div>
O arquivo Views/Shared/_SelectLanguagePartial.cshtml é adicionado à seção footer do arquivo de layout
para que ele fique disponível para todos os exibições:

<div class="container body-content" style="margin-top:60px">


@RenderBody()
<hr>
<footer>
<div class="row">
<div class="col-md-6">
<p>&copy; @System.DateTime.Now.Year - Localization.StarterWeb</p>
</div>
<div class="col-md-6 text-right">
@await Html.PartialAsync("_SelectLanguagePartial")
</div>
</div>
</footer>
</div>

O método SetLanguage define o cookie de cultura.

[HttpPost]
public IActionResult SetLanguage(string culture, string returnUrl)
{
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
);

return LocalRedirect(returnUrl);
}

Não é possível conectar o _SelectLanguagePartial.cshtml ao código de exemplo para este projeto. O projeto
Localization.StarterWeb no GitHub contém o código para o fluxo do RequestLocalizationOptions para uma
parcial do Razor por meio do contêiner de Injeção de Dependência.

Termos de globalização e localização


O processo de localização do aplicativo também exige uma compreensão básica dos conjuntos de caracteres
relevantes geralmente usados no desenvolvimento moderno de software e uma compreensão dos problemas
associados a eles. Embora todos os computadores armazenem texto como números (códigos), diferentes
sistemas armazenam o mesmo texto usando números diferentes. O processo de localização se refere à
tradução da interface do usuário do aplicativo para uma cultura/localidade específica.
Possibilidade de localização é um processo intermediário usado para verificar se um aplicativo globalizado
está pronto para localização.
O formato RFC 4646 para o nome de cultura é <languagecode2>-<country/regioncode2> , em que
<languagecode2> é o código de idioma e <country/regioncode2> é o código de subcultura. Por exemplo, es-CL
para Espanhol (Chile), en-US para inglês (Estados Unidos) e en-AU para inglês (Austrália). O RFC 4646 é
uma combinação de um código de cultura de duas letras minúsculas ISO 639 associado a um idioma e um
código de subcultura de duas letras maiúsculas ISO 3166 associado a um país ou uma região. Consulte Nome
da cultura de idioma.
Muitas vezes, internacionalização é abreviada como "I18N". A abreviação usa a primeira e última letras e o
número de letras entre elas e, portanto, 18 significa o número de letras entre o primeiro "I" e o último "N". O
mesmo se aplica a Globalização (G11N ) e Localização (L10N ).
Termos:
Globalização (G11N ): o processo de fazer com que um aplicativo dê suporte a diferentes idiomas e regiões.
Localização (L10N ): o processo de personalizar um aplicativo para determinado idioma e região.
Internacionalização (I18N ): descreve a globalização e a localização.
Cultura: um idioma e, opcionalmente, uma região.
Cultura neutra: uma cultura que tem um idioma especificado, mas não uma região. (por exemplo, "en", "es")
Cultura específica: uma cultura que tem um idioma e uma região especificados. (por exemplo, "en-US",
"en-GB", "es-CL")
Cultura pai: a cultura neutra que contém uma cultura específica. (por exemplo, "en" é a cultura pai de "en-
US" e "en-GB")
Localidade: uma localidade é o mesmo que uma cultura.

Recursos adicionais
O projeto Localization.StarterWeb usado no artigo.
Arquivos de recurso no Visual Studio
Recursos em arquivos .resx
Configurar a localização do objeto portátil com o
Orchard Core
08/02/2018 • 12 min to read • Edit Online

Por Sébastien Ros e Scott Addie


Este artigo explica as etapas para usar arquivos PO (Objeto Portátil) em um aplicativo ASP.NET Core com a
estrutura Orchard Core.
Observação: o Orchard Core não é um produto da Microsoft. Consequentemente, a Microsoft não fornece
suporte para esse recurso.
Exibir ou baixar código de exemplo (como baixar)

O que é um arquivo PO?


Os arquivos PO são distribuídos como arquivos de texto que contém cadeias de caracteres traduzidas em
determinado idioma. Algumas vantagens do uso de arquivos PO em vez de arquivos .resx incluem:
Os arquivos PO dão suporte à pluralização, ao contrário dos arquivos .resx.
Os arquivos PO não são compilados como os arquivos .resx. Dessa forma, não são necessárias ferramentas
especializadas nem etapas de build.
Os arquivos PO funcionam bem com ferramentas de colaboração de edição online.
Exemplo
Este é um arquivo PO de exemplo que contém a tradução de duas cadeias de caracteres em francês, incluindo uma
com sua forma plural:
fr.po

#: Services/EmailService.cs:29
msgid "Enter a comma separated list of email addresses."
msgstr "Entrez une liste d'emails séparés par une virgule."

#: Views/Email.cshtml:112
msgid "The email address is \"{0}\"."
msgid_plural "The email addresses are \"{0}\"."
msgstr[0] "L'adresse email est \"{0}\"."
msgstr[1] "Les adresses email sont \"{0}\""

Este exemplo usa a seguinte sintaxe:


#: : um comentário que indica o contexto da cadeia de caracteres a ser traduzida. A mesma cadeia de
caracteres pode ser traduzida de modo diferente, dependendo do local em que ela está sendo usada.
msgid : a cadeia de caracteres não traduzida.
msgstr : a cadeia de caracteres traduzida.

No caso de suporte à pluralização, entradas adicionais podem ser definidas.


msgid_plural : a cadeia de caracteres no plural não traduzida.
msgstr[0] : a cadeia de caracteres traduzida para o caso 0.
msgstr[N] : a cadeia de caracteres traduzida para o caso N.
A especificação de arquivo PO pode ser encontrada aqui.

Configurando o suporte a arquivos PO no ASP.NET Core


Este exemplo se baseia em um aplicativo ASP.NET Core MVC gerado com base em um modelo de projeto do
Visual Studio 2017.
Referenciando o pacote
Adicione uma referência ao pacote NuGet OrchardCore.Localization.Core . Ela está disponível em MyGet na
seguinte origem do pacote: https://www.myget.org/F/orchardcore-preview/api/v3/index.json
O arquivo .csproj agora contém uma linha semelhante à seguinte (o número de versão pode variar):

<PackageReference Include="OrchardCore.Localization.Core" Version="1.0.0-beta1-3187" />

Registrando o serviço
Adicione os serviços necessários ao método ConfigureServices de Startup.cs:

public void ConfigureServices(IServiceCollection services)


{
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix);

services.AddPortableObjectLocalization();

services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-US"),
new CultureInfo("en"),
new CultureInfo("fr-FR"),
new CultureInfo("fr")
};

options.DefaultRequestCulture = new RequestCulture("en-US");


options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
}

Adicione o middleware necessário ao método Configure de Startup.cs:


public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}

app.UseStaticFiles();

app.UseRequestLocalization();

app.UseMvcWithDefaultRoute();
}

Adicione o código a seguir à exibição do Razor de sua escolha. About.cshtml é usado neste exemplo.

@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer Localizer

<p>@Localizer["Hello world!"]</p>

Uma instância IViewLocalizer é injetada e usada para traduzir o texto “Olá, Mundo!”.
Criando um arquivo PO
Crie um arquivo chamado .po na pasta raiz do aplicativo. Neste exemplo, o nome do arquivo é fr.po porque o
idioma francês é usado:

msgid "Hello world!"


msgstr "Bonjour le monde!"

Esse arquivo armazena a cadeia de caracteres a ser traduzida e a cadeia de caracteres traduzida do francês. As
traduções são revertidas para a cultura pai, se necessário. Neste exemplo, o arquivo fr.po é usado se a cultura
solicitada é fr-FR ou fr-CA .
Testando o aplicativo
Execute o aplicativo e navegue para a URL /Home/About . O texto Olá, Mundo! é exibido.
Navegue para a URL /Home/About?culture=fr-FR . O texto Bonjour le monde! é exibido.

Pluralização
Os arquivos PO dão suporte a formas de pluralização, que são úteis quando a mesma cadeia de caracteres precisa
ser traduzida de modo diferente de acordo com uma cardinalidade. Essa tarefa torna-se complicada pelo fato de
que cada idioma define regras personalizadas para selecionar qual cadeia de caracteres será usada de acordo com
a cardinalidade.
O pacote de Localização do Orchard fornece uma API para invocar essas diferentes formas plurais
automaticamente.
Criando arquivos PO de pluralização
Adicione o seguinte conteúdo ao arquivo fr.po mencionado anteriormente:
msgid "There is one item."
msgid_plural "There are {0} items."
msgstr[0] "Il y a un élément."
msgstr[1] "Il y a {0} éléments."

Consulte O que é um arquivo PO? para obter uma explicação do que representa cada entrada neste exemplo.
Adicionando um idioma usando diferentes formas de pluralização
Cadeias de caracteres em inglês e francês foram usadas no exemplo anterior. O inglês e o francês têm apenas duas
formas de pluralização e compartilham as mesmas regras de forma, o que significa que uma cardinalidade de um é
mapeada para a primeira forma plural. Qualquer outra cardinalidade é mapeada para a segunda forma plural.
Nem todos os idiomas compartilham as mesmas regras. Isso é ilustrado com o idioma tcheco, que tem três formas
plurais.
Crie o arquivo cs.po da seguinte maneira e observe como a pluralização precisa de três traduções diferentes:

msgid "Hello world!"


msgstr "Ahoj světe!!"

msgid "There is one item."


msgid_plural "There are {0} items."
msgstr[0] "Existuje jedna položka."
msgstr[1] "Existují {0} položky."
msgstr[2] "Existuje {0} položek."

Para aceitar localizações para o tcheco, adicione "cs" à lista de culturas com suporte no método
ConfigureServices :

var supportedCultures = new List<CultureInfo>


{
new CultureInfo("en-US"),
new CultureInfo("en"),
new CultureInfo("fr-FR"),
new CultureInfo("fr"),
new CultureInfo("cs")
};

Edite o arquivo Views/Home/About.cshtml para renderizar cadeias de caracteres localizadas no plural para várias
cardinalidades:

<p>@Localizer.Plural(1, "There is one item.", "There are {0} items.")</p>


<p>@Localizer.Plural(2, "There is one item.", "There are {0} items.")</p>
<p>@Localizer.Plural(5, "There is one item.", "There are {0} items.")</p>

Observação: em um cenário do mundo real, uma variável é usada para representar a contagem. Aqui, repetimos o
mesmo código com três valores diferentes para expor um caso muito específico.
Ao mudar as culturas, o seguinte é observado:
Para /Home/About :

There is one item.


There are 2 items.
There are 5 items.
Para /Home/About?culture=fr :

Il y a un élément.
Il y a 2 éléments.
Il y a 5 éléments.

Para /Home/About?culture=cs :

Existuje jedna položka.


Existují 2 položky.
Existuje 5 položek.

Observe que, para a cultura do tcheco, as três traduções são diferentes. As culturas do francês e do inglês
compartilham a mesma construção para as duas últimas cadeias de caracteres traduzidas.

Tarefas avançadas
Contextualizando cadeias de caracteres
Os aplicativos costumam conter as cadeias de caracteres a serem traduzidas em vários locais. A mesma cadeia de
caracteres pode ter uma tradução diferente em determinados locais em um aplicativo (exibições do Razor ou
arquivos de classe). Um arquivo PO dá suporte à noção de um contexto de arquivo, que pode ser usado para
categorizar a cadeia de caracteres que está sendo representada. Usando um contexto de arquivo, uma cadeia de
caracteres pode ser traduzida de forma diferente, dependendo do contexto de arquivo (ou de sua ausência).
Os serviços de localização de PO usam o nome da classe completa ou da exibição que é usado ao traduzir uma
cadeia de caracteres. Isso é feito definindo o valor na entrada msgctxt .
Considere uma adição mínima ao exemplo anterior de fr.po. Uma exibição do Razor localizada em
Views/Home/About.cshtml pode ser definida com o contexto de arquivo definindo o valor da entrada msgctxt
reservada:

msgctxt "Views.Home.About"
msgid "Hello world!"
msgstr "Bonjour le monde!"

Com o msgctxt definido assim, a tradução de texto ocorre durante a navegação para /Home/About?culture=fr-FR .A
tradução não ocorre durante a navegação para /Home/Contact?culture=fr-FR .
Quando não é encontrada a correspondência de nenhuma entrada específica com um contexto de arquivo
fornecido, o mecanismo de fallback do Orchard Core procura um arquivo PO apropriado sem contexto. Supondo
que não haja nenhum contexto de arquivo específico definido para Views/Home/Contact.cshtml, a navegação para
/Home/Contact?culture=fr-FR carrega um arquivo PO, como:

msgid "Hello world!"


msgstr "Bonjour le monde!"

Alterando o local dos arquivos PO


A localização padrão dos arquivos PO pode ser alterada em ConfigureServices :

services.AddPortableObjectLocalization(options => options.ResourcesPath = "Localization");

Neste exemplo, os arquivos PO são carregados da pasta Localization.


Implementando uma lógica personalizada para encontrar arquivos de localização
Quando uma lógica mais complexa é necessária para localizar arquivos PO, a interface
OrchardCore.Localization.PortableObject.ILocalizationFileLocationProvider pode ser implementada e registrada
como um serviço. Isso é útil quando arquivos PO podem ser armazenados em locais variáveis ou quando os
arquivos precisam ser encontrados em uma hierarquia de pastas.
Usando um idioma pluralizado padrão diferente
O pacote inclui um método de extensão Plural específico a duas formas plurais. Para idiomas que exigem mais
formas plurais, crie um método de extensão. Com um método de extensão, você não precisará fornecer nenhum
arquivo de localização para o idioma padrão – as cadeias de caracteres originais já estão disponíveis diretamente
no código.
Use a sobrecarga Plural(int count, string[] pluralForms, params object[] arguments) mais genérica que aceita
uma matriz de cadeia de caracteres de traduções.
Solicitar recursos no ASP.NET Core
08/02/2018 • 5 min to read • Edit Online

Por Steve Smith


Os detalhes de implementação de servidor Web relacionados a solicitações HTTP e respostas são definidos em
interfaces. Essas interfaces são usadas pelas implementações de servidor e pelo middleware para criar e
modificar o pipeline de hospedagem do aplicativo.

Interfaces de recurso
O ASP.NET Core define várias interfaces de recurso HTTP em Microsoft.AspNetCore.Http.Features , que são
usadas pelos servidores para identificar os recursos para os quais eles dão suporte. As seguintes interfaces de
recurso manipulam solicitações e respostas de retorno:
IHttpRequestFeature Define a estrutura de uma solicitação HTTP, incluindo o protocolo, o caminho, a cadeia de
caracteres de consulta, os cabeçalhos e o corpo.
IHttpResponseFeature Define a estrutura de uma resposta HTTP, incluindo o código de status, os cabeçalhos e o
corpo da resposta.
IHttpAuthenticationFeature Define o suporte para identificar os usuários com base em um ClaimsPrincipal e
especificando um manipulador de autenticação.
IHttpUpgradeFeature Define o suporte para Upgrades de HTTP, que permitem ao cliente especificar quais
protocolos adicionais ele desejará usar se o servidor quiser mudar os protocolos.
IHttpBufferingFeature Define métodos para desabilitar o buffer de solicitações e/ou respostas.
IHttpConnectionFeature Define propriedades para portas e endereços locais e remotos.
Define o suporte para anular conexões ou detectar se uma solicitação foi encerrada
IHttpRequestLifetimeFeature
prematuramente, como por uma desconexão do cliente.
IHttpSendFileFeature Define um método para enviar arquivos de forma assíncrona.
IHttpWebSocketFeature Define uma API para dar suporte a soquetes da Web.
IHttpRequestIdentifierFeature Adiciona uma propriedade que pode ser implementada para identificar as
solicitações de forma exclusiva.
ISessionFeature Define abstrações ISessionFactory e ISession para dar suporte a sessões de usuário.
ITlsConnectionFeature Define uma API para recuperar os certificados de cliente.
ITlsTokenBindingFeature Define métodos para trabalhar com parâmetros de associação de token de TLS.

OBSERVAÇÃO
ISessionFeature não é um recurso de servidor, mas é implementado por SessionMiddleware (consulte Gerenciando o
estado do aplicativo).

Coleções de recursos
A propriedade Features de HttpContext fornece uma interface para obter e definir os recursos HTTP
disponíveis para a solicitação atual. Como a coleção de recursos é mutável, mesmo no contexto de uma
solicitação, o middleware pode ser usado para modificar a coleção e adicionar suporte para recursos adicionais.

Middleware e recursos de solicitação


Embora os servidores sejam responsáveis por criar a coleção de recursos, o middleware pode adicionar recursos
a essa coleção e consumi-los. Por exemplo, o StaticFileMiddleware acessa o recurso IHttpSendFileFeature . Se o
recurso existir, ele será usado para enviar o arquivo estático solicitado de seu caminho físico. Caso contrário, um
método alternativo mais lento será usado para enviar o arquivo. Quando disponível, o IHttpSendFileFeature
permite que o sistema operacional abra o arquivo e faça uma cópia direta de modo kernel para a placa de rede.
Além disso, o middleware pode adicionar recursos à coleção de recursos estabelecida pelo servidor. Os recursos
existentes podem até mesmo ser substituídos pelo middleware, permitindo que o middleware aumente a
funcionalidade do servidor. Os recursos adicionados à coleção ficam disponíveis imediatamente para outro
middleware ou para o próprio aplicativo subjacente posteriormente no pipeline de solicitação.
Combinando implementações personalizadas de servidor e melhorias específicas de middleware, o conjunto
preciso de recursos exigido por um aplicativo pode ser construído. Isso permite que os recursos ausentes sejam
adicionados, sem a necessidade de uma alteração no servidor, além de garantir que somente a quantidade
mínima de recursos seja exposta, limitando a área da superfície de ataque e melhorando o desempenho.

Resumo
As interfaces de recurso definem recursos HTTP específicos para os quais uma solicitação específica pode dar
suporte. Os servidores definem coleções de recursos e o conjunto inicial de recursos compatíveis com o servidor,
mas o middleware pode ser usado para aprimorar esses recursos.

Recursos adicionais
Servidores
Middleware
OWIN (Open Web Interface para .NET)
Primitivos no ASP.NET Core
31/01/2018 • 1 min to read • Edit Online

Os primitivos do ASP.NET Core são blocos de construção de baixo nível compartilhados por extensões de
estrutura. Você pode usar esses blocos de construção em seu próprio código.
Detectar alterações com Tokens de alteração
Detectar alterações com tokens de alteração no
ASP.NET Core
14/02/2018 • 16 min to read • Edit Online

Por Luke Latham


Um token de alteração é um bloco de construção de uso geral e de baixo nível, usado para controlar as alterações.
Exibir ou baixar código de exemplo (como baixar)

Interface IChangeToken
IChangeToken propaga notificações de que ocorreu uma alteração. IChangeToken reside no namespace
Microsoft.Extensions.Primitives. Para aplicativos que não usam o metapacote Microsoft.AspNetCore.All,
referencie o pacote NuGet Microsoft.Extensions.Primitives no arquivo de projeto.
IChangeToken tem duas propriedades:
ActiveChangedCallbacks indique se o token gera retornos de chamada de forma proativa. Se
ActiveChangedCallbacks é definido como false , um retorno de chamada nunca é chamado e o aplicativo
precisa sondar HasChanged em busca de alterações. Também é possível que um token nunca seja cancelado se
não ocorrerem alterações ou o ouvinte de alteração subjacente for removido ou desabilitado.
HasChanged obtém um valor que indica se uma alteração ocorreu.
A interface tem um método, RegisterChangeCallback(Action<Object>, Object), que registra um retorno de
chamada que é invocado quando o token é alterado. HasChanged precisa ser definido antes de o retorno de
chamada ser invocado.

Classe ChangeToken
ChangeToken é uma classe estática usada para propagar notificações de que ocorreu uma alteração. ChangeToken
reside no namespace Microsoft.Extensions.Primitives. Para aplicativos que não usam o metapacote
Microsoft.AspNetCore.All, referencie o pacote NuGet Microsoft.Extensions.Primitives no arquivo de projeto.
O método ChangeToken OnChange(Func<IChangeToken>, Action) registra um Action para chamar sempre que
o token é alterado:
Func<IChangeToken> produz o token.
Action é chamado quando o token é alterado.
ChangeTokentem uma sobrecarga OnChange<TState>(Func<IChangeToken>, Action<TState>, TState) que usa
um parâmetro TState adicional que é passado para o consumidor de token Action .
OnChange retorna um IDisposable. A chamada a Dispose interrompe a escuta do token de outras alterações e
libera os recursos do token.

Usos de exemplo de tokens de alteração no ASP.NET Core


Tokens de alteração são usados nas áreas proeminentes do monitoramento do ASP.NET Core de alterações em
objetos:
Para monitorar as alterações em arquivos, o método Watch de IFileProvider cria um IChangeToken para os
arquivos especificados ou para pasta a ser inspecionada.
Tokens IChangeToken podem ser adicionados a entradas de cache para disparar remoções do cache após as
alterações.
Para alterações TOptions , a implementação padrão de OptionsMonitor de IOptionsMonitor tem uma
sobrecarga que aceita uma ou mais instâncias IOptionsChangeTokenSource. Cada instância retorna um
IChangeToken para registrar um retorno de chamada de notificação de alteração para o controle de alterações
de opções.

Monitorando alterações de configuração


Por padrão, os modelos do ASP.NET Core usam arquivos de configuração JSON (appsettings.json,
appsettings.Development.json e appsettings.Production.json) para carregar as definições de configuração do
aplicativo.
Esses arquivos são configurados com o método de extensão AddJsonFile(IConfigurationBuilder, String, Boolean,
Boolean) no ConfigurationBuilder que aceita um parâmetro reloadOnChange (ASP.NET Core 1.1 e posterior).
reloadOnChange indica se a configuração deve ser recarregada após alterações de arquivo. Veja esta configuração
no método de conveniência WebHost CreateDefaultBuilder (fonte de referência):

config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)


.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);

A configuração baseada em arquivo é representada por FileConfigurationSource. FileConfigurationSource usa


IFileProvider (fonte de referência) para monitorar arquivos.
Por padrão, o IFileMonitor é fornecido por um PhysicalFileProvider (fonte de referência), que usa
FileSystemWatcher para monitorar as alterações do arquivo de configuração.
O aplicativo de exemplo demonstra duas implementações para monitorar as alterações de configuração. Se o
arquivo appsettings.json é alterado ou a versão do Ambiente do arquivo é alterada, cada implementação executa
um código personalizado. O aplicativo de exemplo grava uma mensagem no console.
O FileSystemWatcher de um arquivo de configuração pode disparar vários retornos de chamada de token para
uma única alteração de arquivo de configuração. A implementação da amostra protege contra esse problema
verificando os hashes de arquivo nos arquivos de configuração. A verificação de hashes de arquivo garante que,
pelo menos, um dos arquivos de configuração foi alterado antes de executar o código personalizado. A amostra
usa o hash de arquivo SHA1 (Utilities/Utilities.cs):
public static byte[] ComputeHash(string filePath)
{
var runCount = 1;

while(runCount < 4)
{
try
{
if (File.Exists(filePath))
{
using (var fs = File.OpenRead(filePath))
{
return System.Security.Cryptography.SHA1.Create().ComputeHash(fs);
}
}
else
{
throw new FileNotFoundException();
}
}
catch (IOException ex)
{
if (runCount == 3 || ex.HResult != -2147024864)
{
throw;
}
else
{
Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, runCount)));
runCount++;
}
}
}

return new byte[20];


}

Uma nova tentativa é implementada com uma retirada exponencial. A nova tentativa está presente porque o
bloqueio de arquivo pode ocorrer, o que impede temporariamente a computação de um novo hash em um dos
arquivos.
Token de alteração de inicialização simples
Registre um retorno de chamada Action do consumidor de token para notificações de alteração no token de
recarregamento de configuração (Startup.cs):

ChangeToken.OnChange(
() => config.GetReloadToken(),
(state) => InvokeChanged(state),
env);

config.GetReloadToken() fornece o token. O retorno de chamada é o método InvokeChanged :


private void InvokeChanged(IHostingEnvironment env)
{
byte[] appsettingsHash = ComputeHash("appSettings.json");
byte[] appsettingsEnvHash =
ComputeHash($"appSettings.{env.EnvironmentName}.json");

if (!_appsettingsHash.SequenceEqual(appsettingsHash) ||
!_appsettingsEnvHash.SequenceEqual(appsettingsEnvHash))
{
_appsettingsHash = appsettingsHash;
_appsettingsEnvHash = appsettingsEnvHash;

WriteConsole("Configuration changed (Simple Startup Change Token)");


}
}

O state do retorno de chamada é usado para passar o IHostingEnvironment . Isso é útil para determinar o
arquivo de configuração JSON appsettings correto a ser monitorado, appsettings.<Environment>.json. Hashes de
arquivo são usados para impedir que a instrução WriteConsole seja executada várias vezes, devido a vários
retornos de chamada de token quando o arquivo de configuração é alterado somente uma vez.
Esse sistema é executado, desde que o aplicativo esteja em execução e não possa ser desabilitado pelo usuário.
Monitorando alterações de configuração como um serviço
A amostra implementa:
Monitoramento de token de inicialização básica.
Monitoramento como serviço.
Um mecanismo para habilitar e desabilitar o monitoramento.
A amostra estabelece uma interface IConfigurationMonitor (Extensions/ConfigurationMonitor.cs):

public interface IConfigurationMonitor


{
bool MonitoringEnabled { get; set; }
string CurrentState { get; set; }
}

O construtor da classe implementada, ConfigurationMonitor , registra um retorno de chamada para notificações


de alteração:

public ConfigurationMonitor(IConfiguration config, IHostingEnvironment env)


{
_env = env;

ChangeToken.OnChange<string>(
() => config.GetReloadToken(),
(state) => InvokeChanged(state),
"Not monitoring");
}

public bool MonitoringEnabled { get; set; } = false;


public string CurrentState { get; set; } = "Not monitoring";

config.GetReloadToken() fornece o token. InvokeChanged é o método de retorno de chamada. O state nesta


instância é uma cadeia de caracteres que descreve o estado de monitoramento. Duas propriedades são usadas:
MonitoringEnabled indica se o retorno de chamada deve executar seu código personalizado.
CurrentState descreve o estado atual de monitoramento para uso na interface do usuário.

O método InvokeChanged é semelhante à abordagem anterior, exceto que ele:


Não executa o código, a menos que MonitoringEnabled seja true .
Define a cadeia de caracteres de propriedade CurrentState com uma mensagem descritiva que registra a hora
em que o código foi executado.
Anota o state atual e sua saída WriteConsole .

private void InvokeChanged(string state)


{
if (MonitoringEnabled)
{
byte[] appsettingsHash = ComputeHash("appSettings.json");
byte[] appsettingsEnvHash =
ComputeHash($"appSettings.{_env.EnvironmentName}.json");

if (!_appsettingsHash.SequenceEqual(appsettingsHash) ||
!_appsettingsEnvHash.SequenceEqual(appsettingsEnvHash))
{
state = $"State updated at {DateTime.Now}";
CurrentState = state;

_appsettingsHash = appsettingsHash;
_appsettingsEnvHash = appsettingsEnvHash;

WriteConsole($"Configuration changed (ConfigurationMonitor Class) {state}");


}
}
}

Uma instância ConfigurationMonitor é registrada como um serviço em ConfigureServices de Startup.cs:

services.AddSingleton<IConfigurationMonitor, ConfigurationMonitor>();

A página Índice oferece ao usuário o controle sobre o monitoramento de configuração. A instância de


IConfigurationMonitor é injetada no IndexModel :

public IndexModel(
IConfiguration config,
IConfigurationMonitor monitor,
FileService fileService)
{
_config = config;
_monitor = monitor;
_fileService = fileService;
}

Um botão habilita e desabilita o monitoramento:

<button class="btn btn-danger" asp-page-handler="StopMonitoring">Stop Monitoring</button>


public IActionResult OnPostStartMonitoring()
{
_monitor.MonitoringEnabled = true;
_monitor.CurrentState = string.Empty;

return RedirectToPage();
}

public IActionResult OnPostStopMonitoring()


{
_monitor.MonitoringEnabled = false;
_monitor.CurrentState = "Not monitoring";

return RedirectToPage();
}

Quando OnPostStartMonitoring é disparado, o monitoramento é habilitado e o estado atual é desmarcado.


Quando OnPostStopMonitoring é disparado, o monitoramento é desabilitado e o estado é definido para refletir que
o monitoramento não está ocorrendo.

Monitorando alterações de arquivos armazenados em cache


O conteúdo do arquivo pode ser armazenado em cache em memória usando IMemoryCache. O cache em
memória é descrito no tópico Cache em memória. Sem realizar etapas adicionais, como a implementação descrita
abaixo, dados obsoletos (desatualizados) são retornados de um cache se os dados de origem são alterados.
Não levando em conta o status de um arquivo de origem armazenado em cache durante a renovação de um
período de expiração deslizante resulta em dados de cache obsoletos. Cada solicitação de dados renova o período
de expiração deslizante, mas o arquivo nunca é recarregado no cache. Os recursos do aplicativo que usam o
conteúdo armazenado em cache do arquivo estão sujeitos ao possível recebimento de conteúdo obsoleto.
O uso de tokens de alteração em um cenário de cache de arquivo impede o conteúdo de arquivo obsoleto no
cache. O aplicativo de exemplo demonstra uma implementação da abordagem.
A amostra usa GetFileContent para:
Retornar o conteúdo do arquivo.
Implementar um algoritmo de repetição com retirada exponencial para abranger casos em que um bloqueio
de arquivo está impedindo temporariamente a leitura de um arquivo.
Utilities/Utilities.cs:
public async static Task<string> GetFileContent(string filePath)
{
var runCount = 1;

while(runCount < 4)
{
try
{
if (File.Exists(filePath))
{
using (var fileStreamReader = File.OpenText(filePath))
{
return await fileStreamReader.ReadToEndAsync();
}
}
else
{
throw new FileNotFoundException();
}
}
catch (IOException ex)
{
if (runCount == 3 || ex.HResult != -2147024864)
{
throw;
}
else
{
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, runCount)));
runCount++;
}
}
}

return null;
}

Um FileServiceé criado para manipular pesquisas de arquivos armazenados em cache. A chamada de método
GetFileContent do serviço tenta obter o conteúdo do arquivo do cache em memória e retorná-lo para o
chamador (Services/FileService.cs).
Se o conteúdo armazenado em cache não é encontrado com a chave de cache, as seguintes ações são executadas:
1. O conteúdo do arquivo é obtido com GetFileContent .
2. Um token de alteração é obtido do provedor de arquivo com IFileProviders.Watch. O retorno de chamada do
token é disparado quando o arquivo é modificado.
3. O conteúdo do arquivo é armazenado em cache com um período de expiração deslizante. O token de alteração
é anexado com MemoryCacheEntryExtensions.AddExpirationToken para remover a entrada do cache se o
arquivo é alterado enquanto ele é armazenado em cache.
public class FileService
{
private readonly IMemoryCache _cache;
private readonly IFileProvider _fileProvider;
private List<string> _tokens = new List<string>();

public FileService(IMemoryCache cache, IHostingEnvironment env)


{
_cache = cache;
_fileProvider = env.ContentRootFileProvider;
}

public async Task<string> GetFileContents(string fileName)


{
// For the purposes of this example, files are stored
// in the content root of the app. To obtain the physical
// path to a file at the content root, use the
// ContentRootFileProvider on IHostingEnvironment.
var filePath = _fileProvider.GetFileInfo(fileName).PhysicalPath;
string fileContent;

// Try to obtain the file contents from the cache.


if (_cache.TryGetValue(filePath, out fileContent))
{
return fileContent;
}

// The cache doesn't have the entry, so obtain the file


// contents from the file itself.
fileContent = await GetFileContent(filePath);

if (fileContent != null)
{
// Obtain a change token from the file provider whose
// callback is triggered when the file is modified.
var changeToken = _fileProvider.Watch(fileName);

// Configure the cache entry options for a five minute


// sliding expiration and use the change token to
// expire the file in the cache if the file is
// modified.
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(5))
.AddExpirationToken(changeToken);

// Put the file contents into the cache.


_cache.Set(filePath, fileContent, cacheEntryOptions);

return fileContent;
}

return string.Empty;
}
}

O FileService é registrado no contêiner de serviço junto com o serviço de cache da memória (Startup.cs):

services.AddMemoryCache();
services.AddSingleton<FileService>();

O modelo de página carrega o conteúdo do arquivo usando o serviço (Pages/Index.cshtml.cs):

var fileContent = await _fileService.GetFileContents("poem.txt");


Classe CompositeChangeToken
Para representar uma ou mais instâncias IChangeToken em um único objeto, use a classe CompositeChangeToken
(fonte de referência).

var firstCancellationTokenSource = new CancellationTokenSource();


var secondCancellationTokenSource = new CancellationTokenSource();

var firstCancellationToken = firstCancellationTokenSource.Token;


var secondCancellationToken = secondCancellationTokenSource.Token;

var firstCancellationChangeToken = new CancellationChangeToken(firstCancellationToken);


var secondCancellationChangeToken = new CancellationChangeToken(secondCancellationToken);

var compositeChangeToken =
new CompositeChangeToken(
new List<IChangeToken>
{
firstCancellationChangeToken,
secondCancellationChangeToken
});

HasChanged nos relatórios de token compostos true se um token representado HasChanged é true .
ActiveChangeCallbacks nos relatórios de token compostos true se um token representado
ActiveChangeCallbacks é true . Se ocorrerem vários eventos de alteração simultâneos, o retorno de chamada de
alteração composto será invocado exatamente uma vez.

Consulte também
Cache in-memory
Trabalhando com um cache distribuído
Detectar alterações com tokens de alteração
Cache de resposta
Middleware de Cache de Resposta
Auxiliar de marca de cache
Auxiliar de marca de cache distribuído
Introdução ao OWIN (Open Web Interface para
.NET)
08/02/2018 • 9 min to read • Edit Online

Por Steve Smith e Rick Anderson


O ASP.NET Core dá suporte para OWIN (Open Web Interface para .NET). O OWIN permite que os aplicativos
Web sejam separados dos servidores Web. Ele define uma maneira padrão de usar o middleware em um
pipeline para manipular solicitações e respostas associadas. O middleware e os aplicativos ASP.NET Core podem
interoperar com aplicativos, servidores e middleware baseados no OWIN.
O OWIN fornece uma camada de desacoplamento que permite duas estruturas com modelos de objeto
diferentes para ser usadas juntas. O pacote Microsoft.AspNetCore.Owin fornece duas implementações de
adaptador:
ASP.NET Core para OWIN
OWIN para ASP.NET Core
Isso permite que o ASP.NET Core seja hospedado em um servidor/host compatível com OWIN ou que outros
componentes compatíveis com OWIN sejam executados no ASP.NET Core.
Observação: o uso desses adaptadores implica um custo de desempenho. Os aplicativos que usam somente
componentes do ASP.NET Core não devem usar o pacote ou os adaptadores do OWIN.
Exibir ou baixar código de exemplo (como baixar)

Executando o middleware do OWIN no pipeline do ASP.NET


O suporte ao OWIN do ASP.NET Core é implantado como parte do pacote Microsoft.AspNetCore.Owin . Importe
o suporte ao OWIN para seu projeto instalando esse pacote.
O middleware do OWIN está em conformidade com a especificação do OWIN, que exige uma interface
Func<IDictionary<string, object>, Task> e a definição de chaves específicas (como owin.ResponseBody ). O
seguinte middleware simples do OWIN exibe “Olá, Mundo”:

public Task OwinHello(IDictionary<string, object> environment)


{
string responseText = "Hello World via OWIN";
byte[] responseBytes = Encoding.UTF8.GetBytes(responseText);

// OWIN Environment Keys: http://owin.org/spec/spec/owin-1.0.0.html


var responseStream = (Stream)environment["owin.ResponseBody"];
var responseHeaders = (IDictionary<string, string[]>)environment["owin.ResponseHeaders"];

responseHeaders["Content-Length"] = new string[] {


responseBytes.Length.ToString(CultureInfo.InvariantCulture) };
responseHeaders["Content-Type"] = new string[] { "text/plain" };

return responseStream.WriteAsync(responseBytes, 0, responseBytes.Length);


}

A assinatura de exemplo retorna uma Task e aceita uma IDictionary<string, object> , conforme solicitado pelo
OWIN.
O código a seguir mostra como adicionar o middleware OwinHello (mostrado acima) ao pipeline do ASP.NET
com o método de extensão UseOwin .

public void Configure(IApplicationBuilder app)


{
app.UseOwin(pipeline =>
{
pipeline(next => OwinHello);
});
}

Configure outras ações a serem executadas no pipeline do OWIN.

OBSERVAÇÃO
Os cabeçalhos de resposta devem ser modificados apenas antes da primeira gravação no fluxo de resposta.

OBSERVAÇÃO
Não é recomendado fazer várias chamadas a UseOwin por motivos de desempenho. Os componentes do OWIN
funcionarão melhor se forem agrupados.

app.UseOwin(pipeline =>
{
pipeline(next =>
{
// do something before
return OwinHello;
// do something after
});
});

Usando a Hospedagem ASP.NET em um servidor baseado no OWIN


Servidores baseados no OWIN podem hospedar aplicativos ASP.NET. Um desses servidores é o Nowin, um
servidor Web do OWIN no .NET. Na amostra para este artigo, incluí um projeto que referencia o Nowin e usa-o
para criar um IServer com capacidade de auto-hospedar o ASP.NET Core.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;

namespace NowinSample
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseNowin()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();

host.Run();
}
}
}

IServer é uma interface que exige uma propriedade Features e um método Start .
Start é responsável por configurar e iniciar o servidor, que, nesse caso, é feito por meio de uma série de
chamadas à API fluente que definem endereços analisados do IServerAddressesFeature. Observe que a
configuração fluente da variável _builder especifica que as solicitações serão manipuladas pelo appFunc
definido anteriormente no método. Esse Func é chamado em cada solicitação para processar as solicitações de
entrada.
Adicionaremos também uma extensão IWebHostBuilder para facilitar a adição e configuração do servidor Nowin.

using System;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.Extensions.DependencyInjection;
using Nowin;
using NowinSample;

namespace Microsoft.AspNetCore.Hosting
{
public static class NowinWebHostBuilderExtensions
{
public static IWebHostBuilder UseNowin(this IWebHostBuilder builder)
{
return builder.ConfigureServices(services =>
{
services.AddSingleton<IServer, NowinServer>();
});
}

public static IWebHostBuilder UseNowin(this IWebHostBuilder builder, Action<ServerBuilder> configure)


{
builder.ConfigureServices(services =>
{
services.Configure(configure);
});
return builder.UseNowin();
}
}
}
Com isso em vigor, temos tudo o que é necessário para executar um aplicativo ASP.NET usando esse servidor
personalizado para chamar a extensão em Program.cs:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;

namespace NowinSample
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseNowin()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();

host.Run();
}
}
}

Saiba mais sobre os Servidores ASP.NET.

Executar o ASP.NET Core em um servidor baseado no OWIN e usar


seu suporte do WebSockets
Outro exemplo de como os recursos dos servidores baseados no OWIN podem ser aproveitados pelo ASP.NET
Core é o acesso a recursos como o WebSockets. O servidor Web do OWIN no .NET usado no exemplo anterior
tem suporte para Soquetes da Web internos, que podem ser aproveitados por um aplicativo ASP.NET Core. O
exemplo abaixo mostra um aplicativo Web simples que dá suporte a Soquetes da Web e repete tudo o que foi
enviado ao servidor por meio do WebSockets.
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
if (context.WebSockets.IsWebSocketRequest)
{
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
await EchoWebSocket(webSocket);
}
else
{
await next();
}
});

app.Run(context =>
{
return context.Response.WriteAsync("Hello World");
});
}

private async Task EchoWebSocket(WebSocket webSocket)


{
byte[] buffer = new byte[1024];
WebSocketReceiveResult received = await webSocket.ReceiveAsync(
new ArraySegment<byte>(buffer), CancellationToken.None);

while (!webSocket.CloseStatus.HasValue)
{
// Echo anything we receive
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, received.Count),
received.MessageType, received.EndOfMessage, CancellationToken.None);

received = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer),


CancellationToken.None);
}

await webSocket.CloseAsync(webSocket.CloseStatus.Value,
webSocket.CloseStatusDescription, CancellationToken.None);
}
}

Essa amostra é configurada usando o mesmo NowinServer que o anterior – a única diferença está em como o
aplicativo é configurado em seu método Configure . Um teste usando um cliente simples do WebSocket
demonstra o aplicativo:
Ambiente do OWIN
Construa um ambiente do OWIN usando o HttpContext .

var environment = new OwinEnvironment(HttpContext);


var features = new OwinFeatureCollection(environment);

Chaves do OWIN
O OWIN depende de um objeto IDictionary<string,object> para transmitir informações em uma troca de
Solicitação/Resposta HTTP. O ASP.NET Core implementa as chaves listadas abaixo. Consulte a especificação
primária, extensões e as Diretrizes de chaves do OWIN e chaves comuns.
Dados de solicitação (OWIN v1.0.0)
CHAVE VALOR (TIPO) DESCRIÇÃO

owin.RequestScheme String

owin.RequestMethod String

owin.RequestPathBase String

owin.RequestPath String

owin.RequestQueryString String

owin.RequestProtocol String

owin.RequestHeaders IDictionary<string,string[]>
CHAVE VALOR (TIPO) DESCRIÇÃO

owin.RequestBody Stream

Dados de solicitação (OWIN v1.1.0)


CHAVE VALOR (TIPO) DESCRIÇÃO

owin.RequestId String Opcional

Dados de resposta (OWIN v1.0.0)


CHAVE VALOR (TIPO) DESCRIÇÃO

owin.ResponseStatusCode int Opcional

owin.ResponseReasonPhrase String Opcional

owin.ResponseHeaders IDictionary<string,string[]>

owin.ResponseBody Stream

Outros dados (OWIN v1.0.0)


CHAVE VALOR (TIPO) DESCRIÇÃO

owin.CallCancelled CancellationToken

owin.Version String

Chaves comuns
CHAVE VALOR (TIPO) DESCRIÇÃO

ssl.ClientCertificate X509Certificate

ssl.LoadClientCertAsync Func<Task>

server.RemoteIpAddress String

server.RemotePort String

server.LocalIpAddress String

server.LocalPort String

server.IsLocal bool

server.OnSendingHeaders Action<Action<object>,object>

SendFiles v0.3.0
CHAVE VALOR (TIPO) DESCRIÇÃO

sendfile.SendAsync Consulte Assinatura do delegado Por solicitação

Opaque v0.3.0
CHAVE VALOR (TIPO) DESCRIÇÃO

opaque.Version String

opaque.Upgrade OpaqueUpgrade Consulte Assinatura do delegado

opaque.Stream Stream

opaque.CallCancelled CancellationToken

WebSocket v0.3.0
CHAVE VALOR (TIPO) DESCRIÇÃO

websocket.Version String

websocket.Accept WebSocketAccept Consulte Assinatura do delegado

websocket.AcceptAlt Sem especificação

websocket.SubProtocol String Consulte a etapa 5.5 RFC6455 Seção


4.2.2

websocket.SendAsync WebSocketSendAsync Consulte Assinatura do delegado

websocket.ReceiveAsync WebSocketReceiveAsync Consulte Assinatura do delegado

websocket.CloseAsync WebSocketCloseAsync Consulte Assinatura do delegado

websocket.CallCancelled CancellationToken

websocket.ClientCloseStatus int Opcional

websocket.ClientCloseDescription String Opcional

Recursos adicionais
Middleware
Servidores
Introdução ao WebSockets no ASP.NET Core
08/02/2018 • 7 min to read • Edit Online

Por Tom Dykstra e Andrew Stanton-Nurse


Este artigo explica como começar a usar o WebSockets no ASP.NET Core. O WebSocket é um protocolo que
permite canais de comunicação persistentes bidirecionais em conexões TCP. Ele é usado em aplicativos como
chat, cotações da bolsa, jogos e em qualquer lugar que você desejar inserir uma funcionalidade em tempo real
em um aplicativo Web.
Exibir ou baixar um código de exemplo (como baixar). Consulte a seção Próximas etapas para obter mais
informações.

Pré-requisitos
ASP.NET Core 1.1 (não executado no 1.0)
Qualquer sistema operacional no qual o ASP.NET Core é executado:
Windows 7/Windows Server 2008 e posterior
Linux
macOS
Exceção: caso o aplicativo seja executado no Windows com o IIS ou com WebListener, você precisará
usar:
Windows 8/Windows Server 2012 ou posterior
IIS 8/IIS 8 Express
O WebSocket precisa estar habilitado no IIS
Para navegadores compatíveis, consulte http://caniuse.com/#feat=websockets.

Quando usar
Use o WebSockets quando você precisar trabalhar diretamente com uma conexão de soquete. Por exemplo,
talvez você precise obter o melhor desempenho possível para um jogo em tempo real.
O ASP.NET SignalR fornece um modelo de aplicativo mais avançado para a funcionalidade em tempo real, mas
ele é executado somente no ASP.NET, não no ASP.NET Core. Uma versão Core do SignalR está em
desenvolvimento; para acompanhar seu progresso, acesse o repositório GitHub e pesquise SignalR Core.
Caso não deseje aguardar o SignalR Core, use o WebSockets diretamente agora. No entanto, talvez você precise
desenvolver recursos fornecidos pelo SignalR, como:
Suporte para uma variedade maior de versões de navegador usando fallback automático para métodos de
transporte alternativos.
Reconexão automática quando houver uma queda de conexão.
Suporte para clientes que chamam métodos no servidor ou vice-versa.
Suporte para dimensionamento para vários servidores.

Como usá-lo
Instale o pacote Microsoft.AspNetCore.WebSockets.
Configure o middleware.
Aceite solicitações do WebSocket.
Envie e receba mensagens.
Configurar o middleware
Adicione o middleware do WebSockets ao método Configure da classe Startup .

app.UseWebSockets();

As seguintes configurações podem ser definidas:


KeepAliveInterval – a frequência de envio de quadros “ping” para o cliente, a fim de garantir que os proxies
mantenham a conexão aberta.
ReceiveBufferSize – o tamanho do buffer usado para receber dados. Somente usuários avançados precisam
alterar isso, para ajuste de desempenho de acordo com o tamanho de seus dados.

var webSocketOptions = new WebSocketOptions()


{
KeepAliveInterval = TimeSpan.FromSeconds(120),
ReceiveBufferSize = 4 * 1024
};
app.UseWebSockets(webSocketOptions);

Aceitar solicitações do WebSocket


Em algum lugar e em um momento futuro no ciclo de vida da solicitação (posteriormente no método Configure
ou em uma ação do MVC, por exemplo), verifique se é uma solicitação do WebSocket e aceite-a.
Este exemplo é obtido de uma fase posterior do método Configure .

app.Use(async (context, next) =>


{
if (context.Request.Path == "/ws")
{
if (context.WebSockets.IsWebSocketRequest)
{
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
await Echo(context, webSocket);
}
else
{
context.Response.StatusCode = 400;
}
}
else
{
await next();
}

});

Uma solicitação do WebSocket pode entrar em qualquer URL, mas esse código de exemplo aceita apenas
solicitações de /ws .
Enviar e receber mensagens
O método AcceptWebSocketAsync faz upgrade da conexão TCP para uma conexão WebSocket e fornece um
objeto WebSocket. Use o objeto WebSocket para enviar e receber mensagens.
O código mostrado anteriormente que aceita a solicitação do WebSocket passa o objeto WebSocket para um
método Echo ; esse é o método Echo . O código recebe uma mensagem e envia de volta imediatamente a
mesma mensagem. Ela permanece em um loop, fazendo isso até que o cliente feche a conexão.

private async Task Echo(HttpContext context, WebSocket webSocket)


{
var buffer = new byte[1024 * 4];
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer),
CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType,
result.EndOfMessage, CancellationToken.None);

result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);


}
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription,
CancellationToken.None);
}

Quando você aceita o WebSocket antes de iniciar esse loop, o pipeline de middleware termina. Ao fechar o
soquete, o pipeline é desenrolado. Ou seja, a solicitação para de avançar no pipeline quando você aceita o
WebSocket, assim como faria quando você encontra uma ação do MVC, por exemplo. No entanto, quando você
conclui esse loop e fecha o soquete, a solicitação volta para o pipeline.

Próximas etapas
O aplicativo de exemplo que acompanha este artigo é um aplicativo de eco simples. Ele contém uma página da
Web que faz conexões WebSocket e o servidor apenas reenvia para o cliente as mensagens recebidas. Execute-o
em um prompt de comando (ele não está configurado para execução no Visual Studio com o IIS Express) e
navegue para http://localhost:5000. A página da Web mostra o status de conexão no canto superior esquerdo:

Selecione Conectar para enviar uma solicitação WebSocket para a URL exibida. Insira uma mensagem de teste e
selecione Enviar. Quando terminar, selecione Fechar Soquete. A seção Log de Comunicação relata cada ação
de abertura, envio e fechamento conforme ela ocorre.
Metapacote Microsoft.AspNetCore.All para
ASP.NET Core 2.x
07/05/2018 • 2 min to read • Edit Online

Este recurso exige o ASP.NET Core 2.x direcionado ao .NET Core 2.x.
O Metapacote do Microsoft.AspNetCore.All para ASP.NET Core inclui:
Todos os pacotes com suporte da equipe do ASP.NET Core.
Todos os pacotes com suporte pelo Entity Framework Core.
Dependências internas e de terceiros usadas por ASP.NET Core e pelo Entity Framework Core.
Todos os recursos do ASP.NET Core 2.x e do Entity Framework Core 2.x são incluídos no pacote
Microsoft.AspNetCore.All . Os modelos de projeto padrão direcionados para ASP.NET Core 2.0 usam este
pacote.
O número de versão do metapacote Microsoft.AspNetCore.All representa a versão do ASP.NET Core e a
versão do Entity Framework Core (alinhadas com a versão do .NET Core).
Aplicativos que usam o metapacote Microsoft.AspNetCore.All aproveitam automaticamente o novo
Repositório de Tempo de Execução do .NET Core. O Repositório de Tempo de Execução contém todos os
ativos de tempo de execução necessários para executar aplicativos ASP.NET Core 2.x. Quando você usa o
metapacote Microsoft.AspNetCore.All , nenhum ativo dos pacotes NuGet do ASP.NET Core referenciados é
implantado com o aplicativo —, porque o Repositório de Tempo de Execução do .NET Core contém esse
ativos. Os ativos no Repositório de Tempo de Execução são pré-compilados para melhorar o tempo de
inicialização do aplicativo.
Use o processo de filtragem de pacote para remover os pacotes não utilizados. Pacotes filtrados são
excluídos na saída do aplicativo publicado.
O seguinte arquivo .csproj referencia os metapacotes Microsoft.AspNetCore.All para o ASP.NET Core:

<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<MvcRazorCompileOnPublish>true</MvcRazorCompileOnPublish>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>

</Project>
Escolher entre o ASP.NET e o ASP.NET Core
31/01/2018 • 2 min to read • Edit Online

Independentemente do aplicativo Web que você está criando, o ASP.NET tem uma solução para você: de
aplicativos Web empresariais direcionados ao Windows Server a microsserviços pequenos direcionados a
contêineres do Linux e tudo o que há entre essas duas categorias.

ASP.NET Core
O ASP.NET Core é uma estrutura de software livre e de multiplaforma para a criação de aplicativos Web
modernos e baseados em nuvem no Windows, macOS ou Linux.

ASP.NET
O ASP.NET é uma estrutura consolidada que fornece todos os serviços necessários para criar aplicativos Web
baseados em servidor de nível empresarial no Windows.

Qual é a opção ideal para mim?


ASP.NET CORE ASP.NET

Build para Windows, macOS ou Linux Build para Windows

As Páginas Razor são a abordagem recomendada para criar Use o Web Forms, o SignalR, o MVC, a API Web ou páginas
uma interface do usuário da Web com o ASP.NET Core 2.0. da Web
Consulte também MVC e API Web

Várias versões por computador Uma versão por computador

Desenvolva com o Visual Studio, Visual Studio para Mac ou Desenvolva com o Visual Studio usando o C#, VB ou F#
Visual Studio Code usando o C# ou o F#

Desempenho superior ao ASP.NET Bom desempenho

Escolha o .NET Framework ou o tempo de execução do .NET Use o tempo de execução do .NET Framework
Core

Cenários do ASP.NET Core


As Páginas Razor são a abordagem recomendada para criar uma interface do usuário da Web com o ASP.NET
Core 2.0.
Sites
APIs

Cenários do ASP.NET
Sites
APIs
Em tempo real
Recursos
Introdução ao ASP.NET
Introdução ao ASP.NET Core
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.


RedirectToPage("./Index")
O 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
Recursos de convenção de rota e aplicativo das
Páginas do Razor no ASP.NET Core
11/04/2018 • 26 min to read • Edit Online

Por Luke Latham


Saiba como usar os recursos de convenção do provedor de modelo de aplicativo e rota de página para controlar o
roteamento, a descoberta e o processamento de página nos aplicativos das Páginas do Razor. Quando precisar
configurar rotas de página personalizadas para páginas individuais, configure o roteamento para páginas com a
convenção AddPageRoute descrita mais adiante neste tópico.
Use o aplicativo de exemplo (como baixar) para explorar os recursos descritos neste tópico.

RECURSOS A AMOSTRA EXPLICA...

Convenções de modelo de rota e aplicativo Como adicionar um modelo de rota e cabeçalho às páginas de
um aplicativo.
Conventions.Add
IPageRouteModelConvention
IPageApplicationModelConvention

Convenções de ação da rota de página Como adicionar um modelo de rota às páginas de uma pasta
AddFolderRouteModelConvention e a uma única página.
AddPageRouteModelConvention
AddPageRoute

Convenções de ação do modelo de página Adicionar um cabeçalho às páginas de uma pasta, adicionar
AddFolderApplicationModelConvention um cabeçalho a uma única página e configurar um alocador
AddPageApplicationModelConvention de filtro para adicionar um cabeçalho às páginas de um
ConfigureFilter (classe de filtro, expressão lambda ou aplicativo.
alocador de filtro)

Provedor de modelo de aplicativo de página padrão Como substituir o provedor de modelo de página padrão para
alterar as convenções de nomenclatura do manipulador.

Adicionar convenções de modelo de rota e aplicativo


Adicione um delegado a IPageConvention para adicionar convenções de modelo de rota e aplicativo que se
aplicam às Páginas do Razor.
Adicionar uma convenção de modelo de rota a todas as páginas
Use Conventions para criar e adicionar uma IPageRouteModelConvention à coleção de instâncias
IPageConvention que são aplicadas durante a construção do modelo de rota e de página.
O aplicativo de exemplo adiciona um modelo de rota {globalTemplate?} a todas as páginas no aplicativo:
public class GlobalTemplatePageRouteModelConvention
: IPageRouteModelConvention
{
public void Apply(PageRouteModel model)
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = -1,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel.Template,
"{globalTemplate?}"),
}
});
}
}
}

OBSERVAÇÃO
A propriedade Order para o AttributeRouteModel é definido como 0 (zero). Isso garante que esse modelo tenha
prioridade para a primeira posição de valor de dados de rota quando um único valor de rota é fornecido. Por exemplo, a
amostra adiciona um modelo de rota {aboutTemplate?} mais adiante no tópico. O modelo {aboutTemplate?} recebe
uma Order de 1 . Quando a página About é solicitada no /About/RouteDataValue , "RouteDataValue" é carregado no
RouteData.Values["globalTemplate"] ( Order = 0 ) e não RouteData.Values["aboutTemplate"] ( Order = 1 ) devido
à configuração da propriedade Order .

Startup.cs:

options.Conventions.Add(new GlobalTemplatePageRouteModelConvention());

Solicite a página About da amostra em localhost:5000/About/GlobalRouteValue e inspecione o resultado:

Adicionar uma convenção de modelo de aplicativo a todas as páginas


Use Conventions para criar e adicionar uma IPageApplicationModelConvention à coleção de instâncias
IPageConvention que são aplicadas durante a construção do modelo de rota e de página.
Para demonstrar isso e outras convenções mais adiante no tópico, o aplicativo de exemplo inclui uma classe
AddHeaderAttribute . O construtor de classe aceita uma cadeia de caracteres name e uma matriz de cadeia de
caracteres values . Esses valores são usados em seu método OnResultExecuting para definir um cabeçalho de
resposta. A classe completa é mostrada na seção Convenções de ação do modelo de página mais adiante no
tópico.
O aplicativo de exemplo usa a classe AddHeaderAttribute para adicionar um cabeçalho, GlobalHeader , a todas as
páginas no aplicativo:
public class GlobalHeaderPageApplicationModelConvention
: IPageApplicationModelConvention
{
public void Apply(PageApplicationModel model)
{
model.Filters.Add(new AddHeaderAttribute(
"GlobalHeader", new string[] { "Global Header Value" }));
}
}

Startup.cs:

options.Conventions.Add(new GlobalHeaderPageApplicationModelConvention());

Solicite a página About da amostra em localhost:5000/About e inspecione os cabeçalhos para exibir o resultado:

Convenções de ação da rota de página


O provedor de modelo de rota padrão derivado de IPageRouteModelProvider invoca convenções que foram
projetadas para fornecer pontos de extensibilidade para configuração de rotas de página.
Convenção de modelo de rota de pasta
Use AddFolderRouteModelConvention para criar e adicionar uma IPageRouteModelConvention que invoca uma
ação no PageRouteModel para todas as páginas da pasta especificada.
O aplicativo de exemplo usa AddFolderRouteModelConvention para adicionar um modelo de rota
{otherPagesTemplate?} às páginas da pasta OtherPages:

options.Conventions.AddFolderRouteModelConvention("/OtherPages", model =>


{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 1,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel.Template,
"{otherPagesTemplate?}"),
}
});
}
});

OBSERVAÇÃO
A propriedade Order do AttributeRouteModel é definida como 1 . Isso garante que o modelo para
{globalTemplate?} definido anteriormente no tópico) tenha prioridade para a primeira posição de valor de dados de rota
(
quando um único valor de rota é fornecido. Se a página Page1 é solicitada no /OtherPages/Page1/RouteDataValue ,
"RouteDataValue" é carregado no RouteData.Values["globalTemplate"] ( Order = 0 ) e não
RouteData.Values["otherPagesTemplate"] ( Order = 1 ) devido à configuração da propriedade Order .
Solicite a página Page1 da amostra em localhost:5000/OtherPages/Page1/GlobalRouteValue/OtherPagesRouteValue e
inspecione o resultado:

Convenção de modelo de rota de página


Use AddPageRouteModelConvention para criar e adicionar uma IPageRouteModelConvention que invoca uma
ação no PageRouteModel para a página com o nome especificado.
O aplicativo de exemplo usa AddPageRouteModelConvention para adicionar um modelo de rota {aboutTemplate?} à
página About:

options.Conventions.AddPageRouteModelConvention("/About", model =>


{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 1,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel.Template,
"{aboutTemplate?}"),
}
});
}
});

OBSERVAÇÃO
A propriedade Order do AttributeRouteModel é definida como 1 . Isso garante que o modelo para
{globalTemplate?} (definido anteriormente no tópico) tenha prioridade para a primeira posição de valor de dados de rota
quando um único valor de rota é fornecido. Se a página About é solicitada no /About/RouteDataValue , "RouteDataValue" é
carregado no RouteData.Values["globalTemplate"] ( Order = 0 ) e não RouteData.Values["aboutTemplate"] (
Order = 1 ) devido à configuração da propriedade Order .

Solicite a página About da amostra em localhost:5000/About/GlobalRouteValue/AboutRouteValue e inspecione o


resultado:

Configurar uma rota de página


Use AddPageRoute para configurar uma rota para uma página no caminho de página especificado. Os links
gerados para a página usam a rota especificada. AddPageRoute usa AddPageRouteModelConvention para estabelecer
a rota.
O aplicativo de exemplo cria uma rota para /TheContactPage para Contact.cshtml:

options.Conventions.AddPageRoute("/Contact", "TheContactPage/{text?}");

A página Contact também pode ser acessada em /Contact por meio de sua rota padrão.
A rota personalizada do aplicativo de exemplo para a página Contact permite um segmento de rota text
opcional ( {text?} ). A página também inclui esse segmento opcional em sua diretiva @page , caso o visitante
acesse a página em sua rota /Contact :

@page "{text?}"
@model ContactModel
@{
ViewData["Title"] = "Contact";
}

<h1>@ViewData["Title"]</h1>
<h2>@Model.Message</h2>

<address>
One Microsoft Way<br>
Redmond, WA 98052-6399<br>
<abbr title="Phone">P:</abbr>
425.555.0100
</address>

<address>
<strong>Support:</strong> <a href="mailto:Support@example.com">Support@example.com</a><br>
<strong>Marketing:</strong> <a href="mailto:Marketing@example.com">Marketing@example.com</a>
</address>

<p>@Model.RouteDataTextTemplateValue</p>

Observe que a URL gerada para o link Contato na página renderizada reflete a rota atualizada:

Visite a página Contact em sua rota comum, /Contact , ou na rota personalizada, /TheContactPage . Se você
fornecer um segmento de rota text adicional, a página mostrará o segmento codificado em HTML fornecido:

Convenções de ação do modelo de página


O provedor de modelo de página padrão que implementa IPageApplicationModelProvider invoca convenções que
foram projetadas para fornecer pontos de extensibilidade para configuração de modelos de página. Essas
convenções são úteis ao criar e modificar recursos de descoberta e processamento de página.
Para os exemplos desta seção, o aplicativo de exemplo usa uma classe AddHeaderAttribute , que é um
ResultFilterAttribute, aplicável a um cabeçalho de resposta:
public class AddHeaderAttribute : ResultFilterAttribute
{
private readonly string _name;
private readonly string[] _values;

public AddHeaderAttribute(string name, string[] values)


{
_name = name;
_values = values;
}

public override void OnResultExecuting(ResultExecutingContext context)


{
context.HttpContext.Response.Headers.Add(_name, _values);
base.OnResultExecuting(context);
}
}

Usando convenções, a amostra explica como aplicar o atributo a todas as páginas de uma pasta e a uma única
página.
Convenção de modelo de aplicativo de pasta
Use AddFolderApplicationModelConvention para criar e adicionar uma IPageApplicationModelConvention que
invoca uma ação em instâncias de PageApplicationModel em todas as páginas na pasta especificada.
A amostra explica o uso de AddFolderApplicationModelConvention adicionando um cabeçalho, OtherPagesHeader , às
páginas dentro da pasta OtherPages do aplicativo:

options.Conventions.AddFolderApplicationModelConvention("/OtherPages", model =>


{
model.Filters.Add(new AddHeaderAttribute(
"OtherPagesHeader", new string[] { "OtherPages Header Value" }));
});

Solicite a página Page1 da amostra em localhost:5000/OtherPages/Page1 e inspecione os cabeçalhos para exibir o


resultado:

Convenção de modelo de aplicativo de página


Use AddPageApplicationModelConvention para criar e adicionar uma IPageApplicationModelConvention que
invoca uma ação no PageApplicationModel para a página com o nome especificado.
A amostra explica o uso de AddPageApplicationModelConvention adicionando um cabeçalho, AboutHeader , à página
About:

options.Conventions.AddPageApplicationModelConvention("/About", model =>


{
model.Filters.Add(new AddHeaderAttribute(
"AboutHeader", new string[] { "About Header Value" }));
});

Solicite a página About da amostra em localhost:5000/About e inspecione os cabeçalhos para exibir o resultado:

Configurar um filtro
ConfigureFilter configura o filtro especificado a ser aplicado. É possível implementar uma classe de filtro, mas o
aplicativo de exemplo mostra como implementar um filtro em uma expressão lambda, que é implementado em
segundo plano como um alocador que retorna um filtro:

options.Conventions.ConfigureFilter(model =>
{
if (model.RelativePath.Contains("OtherPages/Page2"))
{
return new AddHeaderAttribute(
"OtherPagesPage2Header",
new string[] { "OtherPages/Page2 Header Value" });
}
return new EmptyFilter();
});

O modelo de aplicativo de página é usado para verificar o caminho relativo para segmentos que levam à página
Page2 na pasta OtherPages. Se a condição é aprovada, um cabeçalho é adicionado. Caso contrário, o EmptyFilter
é aplicado.
EmptyFilter é um filtro de Ação. Como os filtros de Ação são ignorados pelas Páginas do Razor, o EmptyFilter
não funciona conforme esperado se o caminho não contém OtherPages/Page2 .
Solicite a página Page2 da amostra em localhost:5000/OtherPages/Page2 e inspecione os cabeçalhos para exibir o
resultado:

Configurar um alocador de filtro


ConfigureFilter configura o alocador especificado para aplicar filtros a todas as Páginas do Razor.
O aplicativo de exemplo fornece um exemplo de como usar um alocador de filtro adicionando um cabeçalho,
FilterFactoryHeader , com dois valores para as páginas do aplicativo:

options.Conventions.ConfigureFilter(new AddHeaderWithFactory());

AddHeaderWithFactory.cs:
public class AddHeaderWithFactory : IFilterFactory
{
// Implement IFilterFactory
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return new AddHeaderFilter();
}

private class AddHeaderFilter : IResultFilter


{
public void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add(
"FilterFactoryHeader",
new string[]
{
"Filter Factory Header Value 1",
"Filter Factory Header Value 2"
});
}

public void OnResultExecuted(ResultExecutedContext context)


{
}
}

public bool IsReusable


{
get
{
return false;
}
}
}

Solicite a página About da amostra em localhost:5000/About e inspecione os cabeçalhos para exibir o resultado:

Substituir o provedor de modelo de aplicativo de página padrão


As Páginas do Razor usam a interface IPageApplicationModelProvider para criar um
DefaultPageApplicationModelProvider. Herde do provedor de modelo padrão para fornecer sua própria lógica de
implementação para descoberta e processamento do manipulador. A implementação padrão (fonte de referência)
estabelece convenções para a nomenclatura sem nome e nomeado do manipulador, que é descrita abaixo.
Métodos de manipulador sem nome padrão
Os métodos de manipulador para verbos HTTP (métodos de manipulador "sem nome") seguem uma convenção:
On<HTTP verb>[Async] (o acréscimo de Async é opcional, mas recomendado para métodos assíncronos).

MÉTODO DE MANIPULADOR SEM NOME OPERAÇÃO

OnGet / OnGetAsync Inicializar o estado da página.

OnPost / OnPostAsync Manipular solicitações POST.

OnDelete / OnDeleteAsync Manipular solicitações DELETE†.

OnPut / OnPutAsync Manipular solicitações PUT†.


MÉTODO DE MANIPULADOR SEM NOME OPERAÇÃO

OnPatch / OnPatchAsync Manipular solicitações PATCH8224;.

†Usado para fazer chamadas à API para a página.


Métodos de manipulador nomeado padrão
Os métodos de manipulador fornecidos pelo desenvolvedor (métodos de manipulador "nomeado") seguem uma
convenção semelhante. O nome do manipulador é exibido após o verbo HTTP ou entre o verbo HTTP e Async :
On<HTTP verb><handler name>[Async] (o acréscimo de Async é opcional, mas recomendado para métodos
assíncronos). Por exemplo, os métodos que processam mensagens podem usar a nomenclatura mostrada na
tabela abaixo.

MÉTODO DO MANIPULADOR NOMEADO DE EXEMPLO OPERAÇÃO DE EXEMPLO

OnGetMessage / OnGetMessageAsync Obter uma mensagem.

OnPostMessage / OnPostMessageAsync Executar POST em uma mensagem.

OnDeleteMessage / OnDeleteMessageAsync Executar DELETE em uma mensagem†.

OnPutMessage / OnPutMessageAsync Executar PUT em uma mensagem†.

OnPatchMessage / OnPatchMessageAsync Executar PATCH em uma mensagem†.

†Usado para fazer chamadas à API para a página.


Personalizar os nomes do método de manipulador
Suponha que você prefira alterar a maneira como os métodos de manipulador nomeado e não nomeado são
nomeados. Um esquema de nomenclatura alternativo é evitar que os nomes de método comecem com "On" e
usar o primeiro segmento de palavra para determinar o verbo HTTP. Faça outras alterações, como converter os
verbos DELETE, PUT e PATCH em POST. Um esquema desse tipo fornece os nomes de método mostrados na
tabela a seguir.

MÉTODO DO MANIPULADOR OPERAÇÃO

Get Inicializar o estado da página.

Post / PostAsync Manipular solicitações POST.

Delete / DeleteAsync Manipular solicitações DELETE†.

Put / PutAsync Manipular solicitações PUT†.

Patch / PatchAsync Manipular solicitações PATCH8224;.

GetMessage Obter uma mensagem.

PostMessage / PostMessageAsync Executar POST em uma mensagem.

DeleteMessage / DeleteMessageAsync Executar POST em uma mensagem a ser excluída.


MÉTODO DO MANIPULADOR OPERAÇÃO

PutMessage / PutMessageAsync Executar POST em uma mensagem a ser colocada.

PatchMessage / PatchMessageAsync Executar POST em uma mensagem a ser corrigida.

†Usado para fazer chamadas à API para a página.


Para estabelecer esse esquema, herde da classe DefaultPageApplicationModelProvider e substitua o método
CreateHandlerModel para fornecer uma lógica personalizada para resolver os nomes de manipulador de
PageModel. O aplicativo de exemplo mostra como isso é feito em sua classe CustomPageApplicationModelProvider :

public class CustomPageApplicationModelProvider :


DefaultPageApplicationModelProvider
{
protected override PageHandlerModel CreateHandlerModel(MethodInfo method)
{
if (method == null)
{
throw new ArgumentNullException(nameof(method));
}

if (!IsHandler(method))
{
return null;
}

if (!TryParseHandlerMethod(
method.Name, out var httpMethod, out var handlerName))
{
return null;
}

var handlerModel = new PageHandlerModel(


method,
method.GetCustomAttributes(inherit: true))
{
Name = method.Name,
HandlerName = handlerName,
HttpMethod = httpMethod,
};

var methodParameters = handlerModel.MethodInfo.GetParameters();

for (var i = 0; i < methodParameters.Length; i++)


{
var parameter = methodParameters[i];
var parameterModel = CreateParameterModel(parameter);
parameterModel.Handler = handlerModel;

handlerModel.Parameters.Add(parameterModel);
}

return handlerModel;
}

private static bool TryParseHandlerMethod(


string methodName, out string httpMethod, out string handler)
{
httpMethod = null;
handler = null;

// Parse the method name according to our conventions to


// determine the required HTTP verb and optional
// handler name.
// handler name.
//
// Valid names look like:
// - Get
// - Post
// - PostAsync
// - GetMessage
// - PostMessage
// - DeleteMessage
// - DeleteMessageAsync

var length = methodName.Length;


if (methodName.EndsWith("Async", StringComparison.Ordinal))
{
length -= "Async".Length;
}

if (length == 0)
{
// The method is named "Async". Exit processing.
return false;
}

// The HTTP verb is at the start of the method name. Use


// casing to determine where it ends.
var handlerNameStart = 1;
for (; handlerNameStart < length; handlerNameStart++)
{
if (char.IsUpper(methodName[handlerNameStart]))
{
break;
}
}

httpMethod = methodName.Substring(0, handlerNameStart);

// The handler name follows the HTTP verb and is optional.


// It includes everything up to the end excluding the
// "Async" suffix, if present.
handler = handlerNameStart == length ? null : methodName.Substring(0, length);

if (string.Equals(httpMethod, "GET", StringComparison.OrdinalIgnoreCase) ||


string.Equals(httpMethod, "POST", StringComparison.OrdinalIgnoreCase))
{
// Do nothing. The httpMethod is correct for GET and POST.
return true;
}
if (string.Equals(httpMethod, "DELETE", StringComparison.OrdinalIgnoreCase) ||
string.Equals(httpMethod, "PUT", StringComparison.OrdinalIgnoreCase) ||
string.Equals(httpMethod, "PATCH", StringComparison.OrdinalIgnoreCase))
{
// Convert HTTP verbs for DELETE, PUT, and PATCH to POST
// For example: DeleteMessage, PutMessage, PatchMessage -> POST
httpMethod = "POST";
return true;
}
else
{
return false;
}
}
}

Destaques da classe incluem:


A classe herda de DefaultPageApplicationModelProvider .
O TryParseHandlerMethod processa um manipulador para determinar o verbo HTTP ( httpMethod ) e o nome do
manipulador nomeado ( handlerName ) ao criar o PageHandlerModel .
Um Async pós-fixado é ignorado, se presente.
O uso de maiúsculas é adotado para analisar o verbo HTTP com base no nome do método.
Quando o nome do método (sem Async ) é igual ao nome do verbo HTTP, não há um manipulador
nomeado. O handlerName é definido como null e o nome do método é Get , Post , Delete , Put ou
Patch .
Quando o nome do método (sem Async ) é maior que o nome do verbo HTTP, há um manipulador
nomeado. O handlerName é definido como <method name (less 'Async', if present)> . Por exemplo,
"GetMessage" e "GetMessageAsync" geram um nome de manipulador "GetMessage".
Os verbos HTTP DELETE, PUT e PATCH são convertidos em POST.
Registre o CustomPageApplicationModelProvider na classe Startup :

services.AddSingleton<IPageApplicationModelProvider,
CustomPageApplicationModelProvider>();

O modelo de página em Index.cshtml.cs mostra como as convenções de nomenclatura comuns de método de


manipulador são alteradas para as páginas no aplicativo. A nomenclatura de prefixo comum "On" usada com as
Páginas do Razor é removida. O método que inicializa o estado da página é agora nomeado Get . Você poderá ver
essa convenção usada em todo o aplicativo se abrir qualquer modelo de página de uma das páginas.
Cada um dos outros métodos começam com o verbo HTTP que descreve seu processamento. Os dois métodos
que começam com Delete normalmente são tratados como verbos HTTP DELETE, mas a lógica em
TryParseHandlerMethod define de forma explícita o verbo como POST para ambos os manipuladores.

Observe que Async é opcional entre DeleteAllMessages e DeleteMessageAsync . Os dois são métodos assíncronos,
mas você pode optar por usar o Async pós-fixado ou não; recomendamos que você faça isso. DeleteAllMessages
é usado aqui para fins de demonstração, mas recomendamos que você nomeie um método desse tipo
DeleteAllMessagesAsync . Isso não afeta o processamento da implementação da amostra, mas o uso do Async pós-
fixado indica que se trata de um método assíncrono.
public async Task Get()
{
Messages = await _db.Messages.AsNoTracking().ToListAsync();
}

public async Task<IActionResult> PostMessageAsync()


{
_db.Messages.Add(Message);
await _db.SaveChangesAsync();

Result = $"{nameof(PostMessageAsync)} handler: Message '{Message.Text}' added.";

return RedirectToPage();
}

public async Task<IActionResult> DeleteAllMessages()


{
foreach (Message message in _db.Messages)
{
_db.Messages.Remove(message);
}
await _db.SaveChangesAsync();

Result = $"{nameof(DeleteAllMessages)} handler: All messages deleted.";

return RedirectToPage();
}

public async Task<IActionResult> DeleteMessageAsync(int id)


{
var message = await _db.Messages.FindAsync(id);

if (message != null)
{
_db.Messages.Remove(message);
await _db.SaveChangesAsync();
}

Result = $"{nameof(DeleteMessageAsync)} handler: Message with Id: {id} deleted.";

return RedirectToPage();
}

Observe que os nomes de manipulador fornecidos em Index.cshtml correspondem aos métodos de manipulador
DeleteAllMessages e DeleteMessageAsync :
<div class="row">
<div class="col-md-3">
<form method="post">
<h2>Clear all messages</h2>
<hr>
<div class="form-group">
<button type="submit" asp-page-handler="DeleteAllMessages"
class="btn btn-danger">Clear All</button>
</div>
</form>
</div>
</div>

<div class="row">
<div class="col-md-12">
<form method="post">
<h2>Messages</h2>
<hr>
<ol>
@foreach (var message in Model.Messages)
{
<li>
@message.Text
<button type="submit" asp-page-handler="DeleteMessage"
asp-route-id="@message.Id">delete</button>
</li>
}
</ol>
</form>
</div>
</div>

Async no nome do método de manipulador DeleteMessageAsync é excluído do TryParseHandlerMethod para a


correspondência de manipulador da solicitação POST com o método. É feita a correspondência do nome
asp-page-handler de DeleteMessage ao método de manipulador DeleteMessageAsync .

Filtros MVC e o filtro de Página (IPageFilter)


Os filtros de Ação MVC são ignorados pelas Páginas do Razor, pois elas usam métodos de manipulador. Outros
tipos de filtros MVC estão disponíveis para uso: Autorização, Exceção, Recurso e Resultado. Para obter mais
informações, consulte o tópico Filtros.
O filtro de Página (IPageFilter) é um filtro que se aplica às Páginas do Razor. Ele envolve a execução de um
método de manipulador de página. Permite que você processe um código personalizado em estágios da execução
do método de manipulador de página. Este é um exemplo do aplicativo de exemplo:
[AttributeUsage(AttributeTargets.Class)]
public class ReplaceRouteValueFilterAttribute : Attribute, IPageFilter
{
public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
{
// Called after the handler method executes before the result.
}

public void OnPageHandlerExecuting(PageHandlerExecutingContext context)


{
// Called before the handler method executes after model binding is complete.
}

public void OnPageHandlerSelected(PageHandlerSelectedContext context)


{
// Called after a handler method is selected but before model binding occurs.
context.RouteData.Values.TryGetValue("globalTemplate",
out var globalTemplateValue);
if (string.Equals((string)globalTemplateValue, "TriggerValue",
StringComparison.Ordinal))
{
context.RouteData.Values["globalTemplate"] = "ReplacementValue";
}
}
}

Esse filtro procura um valor de rota globalTemplate igual a "TriggerValue" e troca-o em "ReplacementValue".
O atributo ReplaceRouteValueFilter pode ser aplicado diretamente a um PageModel :

[ReplaceRouteValueFilter]
public class Page3Model : PageModel
{

Solicite a página Page3 do aplicativo de amostra com um localhost:5000/OtherPages/Page3/TriggerValue . Observe


como o filtro substitui o valor de rota:

Consulte também
Convenções de autorização de Páginas Razor
Visão geral do ASP.NET Core MVC
10/04/2018 • 19 min to read • Edit Online

Por Steve Smith


O ASP.NET Core MVC é uma estrutura avançada para a criação de aplicativos Web e APIs usando o padrão de
design Model-View -Controller.

O que é o padrão MVC?


O padrão de arquitetura MVC (Model-View -Controller) separa um aplicativo em três grupos de componentes
principais: Modelos, Exibições e Componentes. Esse padrão ajuda a obter a separação de interesses. Usando
esse padrão, as solicitações de usuário são encaminhadas para um Controlador, que é responsável por trabalhar
com o Modelo para executar as ações do usuário e/ou recuperar os resultados de consultas. O Controlador
escolhe a Exibição a ser exibida para o usuário e fornece-a com os dados do Modelo solicitados.
O seguinte diagrama mostra os três componentes principais e quais deles referenciam os outros:

Essa descrição das responsabilidades ajuda você a dimensionar o aplicativo em termos de complexidade,
porque é mais fácil de codificar, depurar e testar algo (modelo, exibição ou controlador) que tem um único
trabalho (e que segue o Princípio da Responsabilidade Única). É mais difícil atualizar, testar e depurar um
código que tem dependências distribuídas em duas ou mais dessas três áreas. Por exemplo, a lógica da
interface do usuário tende a ser alterada com mais frequência do que a lógica de negócios. Se o código de
apresentação e a lógica de negócios forem combinados em um único objeto, um objeto que contém a lógica de
negócios precisa ser modificado sempre que a interface do usuário é alterada. Isso costuma introduzir erros e
exige um novo teste da lógica de negócios após cada alteração mínima da interface do usuário.

OBSERVAÇÃO
A exibição e o controlador dependem do modelo. No entanto, o modelo não depende da exibição nem do controlador.
Esse é um dos principais benefícios da separação. Essa separação permite que o modelo seja criado e testado de forma
independente da apresentação visual.

Responsabilidades do Modelo
O Modelo em um aplicativo MVC representa o estado do aplicativo e qualquer lógica de negócios ou operação
que deve ser executada por ele. A lógica de negócios deve ser encapsulada no modelo, juntamente com
qualquer lógica de implementação, para persistir o estado do aplicativo. As exibições fortemente tipadas
normalmente usam tipos ViewModel criados para conter os dados a serem exibidos nessa exibição. O
controlador cria e popula essas instâncias de ViewModel com base no modelo.

OBSERVAÇÃO
Há várias maneiras de organizar o modelo em um aplicativo que usa o padrão de arquitetura MVC. Saiba mais sobre
alguns tipos diferentes de tipos de modelo.

Responsabilidades da Exibição
As exibições são responsáveis por apresentar o conteúdo por meio da interface do usuário. Elas usam o
mecanismo de exibição do Razor para inserir o código .NET em uma marcação HTML. Deve haver uma lógica
mínima nas exibições e qualquer lógica contida nelas deve se relacionar à apresentação do conteúdo. Se você
precisar executar uma grande quantidade de lógica em arquivos de exibição para exibir dados de um modelo
complexo, considere o uso de um Componente de Exibição, ViewModel ou um modelo de exibição para
simplificar a exibição.
Responsabilidades do Controlador
Os controladores são os componentes que cuidam da interação do usuário, trabalham com o modelo e, em
última análise, selecionam uma exibição a ser renderizada. Em um aplicativo MVC, a exibição mostra apenas
informações; o controlador manipula e responde à entrada e à interação do usuário. No padrão MVC, o
controlador é o ponto de entrada inicial e é responsável por selecionar quais tipos de modelo serão usados para
o trabalho e qual exibição será renderizada (daí seu nome – ele controla como o aplicativo responde a
determinada solicitação).

OBSERVAÇÃO
Os controladores não devem ser excessivamente complicados por muitas responsabilidades. Para evitar que a lógica do
controlador se torne excessivamente complexa, use o Princípio da Responsabilidade Única para empurrar a lógica de
negócios para fora do controlador e inseri-la no modelo de domínio.

DICA
Se você achar que as ações do controlador executam com frequência os mesmos tipos de ações, siga o Princípio Don't
Repeat Yourself movendo essas ações comuns para filtros.

O que é ASP.NET Core MVC


A estrutura do ASP.NET Core MVC é uma estrutura de apresentação leve, de software livre e altamente
testável, otimizada para uso com o ASP.NET Core.
ASP.NET Core MVC fornece uma maneira com base em padrões para criar sites dinâmicos que habilitam uma
separação limpa de preocupações. Ele lhe dá controle total sobre a marcação, dá suporte ao desenvolvimento
amigável a TDD e usa os padrões da web mais recentes.

Recursos
ASP.NET Core MVC inclui o seguinte:
Roteamento
Associação de modelos
Validação de modelo
Injeção de dependência
Filtros
Áreas
APIs Web
Capacidade de teste
Mecanismo de exibição do Razor
Exibições fortemente tipadas
Auxiliares de marcação
Componentes da exibição
Roteamento
O ASP.NET Core MVC baseia-se no roteamento do ASP.NET Core, um componente de mapeamento de URL
avançado que permite criar aplicativos que têm URLs compreensíveis e pesquisáveis. Isso permite que você
defina padrões de nomenclatura de URL do aplicativo que funcionam bem para SEO (otimização do
mecanismo de pesquisa) e para a geração de links, sem levar em consideração como os arquivos no servidor
Web estão organizados. Defina as rotas usando uma sintaxe de modelo de rota conveniente que dá suporte a
restrições de valor de rota, padrões e valores opcionais.
O roteamento baseado em convenção permite definir globalmente os formatos de URL aceitos pelo aplicativo
e como cada um desses formatos é mapeado para um método de ação específico em determinado controlador.
Quando uma solicitação de entrada é recebida, o mecanismo de roteamento analisa a URL e corresponde-a a
um dos formatos de URL definidos. Em seguida, ele chama o método de ação do controlador associado.

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

O roteamento de atributo permite que você especifique as informações de roteamento decorando os


controladores e as ações com atributos que definem as rotas do aplicativo. Isso significa que as definições de
rota são colocadas ao lado do controlador e da ação aos quais estão associadas.

[Route("api/[controller]")]
public class ProductsController : Controller
{
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
...
}
}

Associação de modelos
ASP.NET Core MVC associação de modelo converte dados de solicitação de cliente (valores de formulário, os
dados de rota, parâmetros de cadeia de caracteres de consulta, os cabeçalhos HTTP ) em objetos que o
controlador pode manipular. Como resultado, a lógica de controlador não precisa fazer o trabalho de descobrir
os dados de solicitação de entrada; ele simplesmente tem os dados como parâmetros para os métodos de ação.

public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null) { ... }

Validação de modelo
O ASP.NET Core MVC dá suporte à validação pela decoração do objeto de modelo com atributos de validação
de anotação de dados. Os atributos de validação são verificados no lado do cliente antes que os valores sejam
postados no servidor, bem como no servidor antes que a ação do controlador seja chamada.
using System.ComponentModel.DataAnnotations;
public class LoginViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; }

[Required]
[DataType(DataType.Password)]
public string Password { get; set; }

[Display(Name = "Remember me?")]


public bool RememberMe { get; set; }
}

Uma ação do controlador:

public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)


{
if (ModelState.IsValid)
{
// work with the model
}
// At this point, something failed, redisplay form
return View(model);
}

A estrutura manipula a validação dos dados de solicitação no cliente e no servidor. A lógica de validação
especificada em tipos de modelo é adicionada às exibições renderizados como anotações não invasivas e é
imposta no navegador com o jQuery Validation.
Injeção de dependência
O ASP.NET Core tem suporte interno para DI (injeção de dependência). No ASP.NET Core MVC, os
controladores podem solicitar serviços necessários por meio de seus construtores, possibilitando o
acompanhamento do princípio de dependências explícitas.
O aplicativo também pode usar a injeção de dependência em arquivos no exibição, usando a diretiva @inject :

@inject SomeService ServiceName


<!DOCTYPE html>
<html lang="en">
<head>
<title>@ServiceName.GetTitle</title>
</head>
<body>
<h1>@ServiceName.GetTitle</h1>
</body>
</html>

Filtros
Os filtros ajudam os desenvolvedores a encapsular interesses paralelos, como tratamento de exceção ou
autorização. Os filtros permitem a execução de uma lógica pré e pós-processamento personalizada para
métodos de ação e podem ser configurados para execução em determinados pontos no pipeline de execução de
uma solicitação específica. Os filtros podem ser aplicados a controladores ou ações como atributos (ou podem
ser executados globalmente). Vários filtros (como Authorize ) são incluídos na estrutura.
[Authorize]
public class AccountController : Controller
{

Áreas
As áreas fornecem uma maneira de particionar um aplicativo Web ASP.NET Core MVC grande em
agrupamentos funcionais menores. Uma área é uma estrutura MVC dentro de um aplicativo. Em um projeto
MVC, componentes lógicos como Modelo, Controlador e Exibição são mantidos em pastas diferentes e o MVC
usa convenções de nomenclatura para criar a relação entre esses componentes. Para um aplicativo grande,
pode ser vantajoso particionar o aplicativo em áreas de nível alto separadas de funcionalidade. Por exemplo,
um aplicativo de comércio eletrônico com várias unidades de negócios, como check-out, cobrança e pesquisa,
etc. Cada uma dessas unidades têm suas próprias exibições de componente lógico, controladores e modelos.
APIs da Web
Além de ser uma ótima plataforma para a criação de sites, o ASP.NET Core MVC tem um excelente suporte
para a criação de APIs Web. Crie serviços que alcançam uma ampla gama de clientes, incluindo navegadores e
dispositivos móveis.
A estrutura inclui suporte para a negociação de conteúdo HTTP com suporte interno para formatar dados
como JSON ou XML. Escreva formatadores personalizados para adicionar suporte para seus próprios
formatos.
Use a geração de links para habilitar o suporte para hipermídia. Habilite o suporte para o CORS
(Compartilhamento de Recursos Entre Origens) com facilidade, de modo que as APIs Web possam ser
compartilhadas entre vários aplicativos Web.
Capacidade de teste
Uso da estrutura de injeção de dependência e interfaces, é adequado para testes de unidade e a estrutura inclui
recursos (como um provedor TestHost e InMemory para Entity Framework) que tornam testes de integração
rápida e fácil também. Saiba mais sobre como testar a lógica do controlador.
Mecanismo de exibição do Razor
As exibições do ASP.NET Core MVC usam o mecanismo de exibição do Razor para renderizar exibições. Razor
é uma linguagem de marcação de modelo compacta, expressiva e fluida para definir exibições usando um
código C# inserido. O Razor é usado para gerar o conteúdo da Web no servidor de forma dinâmica. Você pode
combinar o código do servidor com o código e o conteúdo do lado cliente de maneira limpa.

<ul>
@for (int i = 0; i < 5; i++) {
<li>List item @i</li>
}
</ul>

Usando o mecanismo de exibição do Razor, você pode definir layouts, exibições parciais e seções substituíveis.
Exibições fortemente tipadas
As exibições do Razor no MVC podem ser fortemente tipadas com base no modelo. Os controladores podem
passar um modelo fortemente tipado para as exibições, permitindo que elas tenham a verificação de tipo e o
suporte do IntelliSense.
Por exemplo, a seguinte exibição renderiza um modelo do tipo IEnumerable<Product> :
@model IEnumerable<Product>
<ul>
@foreach (Product p in Model)
{
<li>@p.Name</li>
}
</ul>

Auxiliares de Marca
Os Auxiliares de Marca permitem que o código do servidor participe da criação e renderização de elementos
HTML em arquivos do Razor. Use auxiliares de marca para definir marcas personalizadas (por exemplo,
<environment> ) ou para modificar o comportamento de marcas existentes (por exemplo, <label> ). Os
Auxiliares de Marca associam a elementos específicos com base no nome do elemento e seus atributos. Eles
oferecem os benefícios da renderização do lado do servidor, enquanto preservam uma experiência de edição de
HTML.
Há muitos Auxiliares de Marca internos para tarefas comuns – como criação de formulários, links,
carregamento de ativos e muito mais – e ainda outros disponíveis em repositórios GitHub públicos e como
NuGet. Os Auxiliares de Marca são criados no C# e são direcionados a elementos HTML de acordo com o
nome do elemento, o nome do atributo ou a marca pai. Por exemplo, o LinkTagHelper interno pode ser usado
para criar um link para a ação Login do AccountsController :

<p>
Thank you for confirming your email.
Please <a asp-controller="Account" asp-action="Login">Click here to Log in</a>.
</p>

O EnvironmentTagHelper pode ser usado para incluir scripts diferentes nas exibições (por exemplo, bruto ou
minimizado) de acordo com o ambiente de tempo de execução, como Desenvolvimento, Preparo ou Produção:

<environment names="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
</environment>
<environment names="Staging,Production">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.1.4.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery">
</script>
</environment>

Os Auxiliares de Marca fornecem uma experiência de desenvolvimento amigável a HTML e um ambiente


avançado do IntelliSense para a criação de HTML e marcação do Razor. A maioria dos Auxiliares de Marca
internos é direcionada a elementos HTML existentes e fornece atributos do lado do servidor para o elemento.
Componentes da exibição
Os Componentes de Exibição permitem que você empacote a lógica de renderização e reutilize-a em todo o
aplicativo. São semelhantes às exibições parciais, mas com a lógica associada.
Associação de modelos
08/02/2018 • 15 min to read • Edit Online

Por Rachel Appel

Introdução à associação de modelos


A associação de modelos do ASP.NET Core MVC mapeia dados de solicitações HTTP para
parâmetros de método de ação. Os parâmetros podem ser tipos simples, como cadeias de
caracteres, inteiros ou floats, ou podem ser tipos complexos. Esse é um ótimo recurso do MVC,
pois o mapeamento dos dados de entrada para um equivalente é um cenário repetido com
frequência, independentemente do tamanho ou da complexidade dos dados. O MVC resolve esse
problema com a abstração da associação, de modo que os desenvolvedores não precisem
continuar reconfigurando uma versão ligeiramente diferente desse mesmo código em cada
aplicativo. A escrita de seu próprio texto para tipar o código de conversor é entediante e propensa
a erros.

Como funciona a associação de modelos


Quando o MVC recebe uma solicitação HTTP, ele encaminha-a a um método de ação específico
de um controlador. Ele determina qual método de ação será executado com base no que está nos
dados de rota e, em seguida, ele associa valores da solicitação HTTP aos parâmetros desse
método de ação. Por exemplo, considere a seguinte URL:
http://contoso.com/movies/edit/2

Como o modelo de rota é semelhante a isto, {controller=Home}/{action=Index}/{id?} ,


movies/edit/2 encaminha para o controlador Movies e seu método de ação Edit . Ele também
aceita um parâmetro opcional chamado id . O código para o método de ação deve ter esta
aparência:

public IActionResult Edit(int? id)

Observação: as cadeias de caracteres na rota de URL não diferenciam maiúsculas de minúsculas.


O MVC tentará associar os dados de solicitação aos parâmetros de ação por nome. O MVC
procurará valores para cada parâmetro usando o nome do parâmetro e os nomes de suas
propriedades configuráveis públicas. No exemplo acima, o único parâmetro de ação é chamado
id , que o MVC associa ao valor com o mesmo nome nos valores de rota. Além dos valores de
rota, o MVC associará dados de várias partes da solicitação e faz isso em uma ordem específica.
Veja abaixo uma lista das fontes de dados na ordem em que a associação de modelos examina-as:
1. : esses são valores de formulário que entram na solicitação HTTP usando o
Form values
método POST. (incluindo as solicitações POST jQuery).
2. Route values : o conjunto de valores de rota fornecido pelo Roteamento
3. Query strings : a parte da cadeia de caracteres de consulta do URI.

Observação: valores de formulário, dados de rota e cadeias de consulta são todos armazenados
como pares nome-valor.
Como a associação de modelos solicitou uma chave chamada id e não há nada chamado id
nos valores de formulário, ela passou para os valores de rota procurando essa chave. Em nosso
exemplo, isso é uma correspondência. A associação ocorre e o valor é convertido no inteiro 2. A
mesma solicitação que usa Edit(string id) converterá na cadeia de caracteres "2".
Até agora, o exemplo usa tipos simples. No MVC, os tipos simples são qualquer tipo primitivo do
.NET ou um tipo com um conversor de tipo de cadeia de caracteres. Se o parâmetro do método de
ação for uma classe, como o tipo Movie , que contém tipos simples e complexos como
propriedades, a associação de modelos do MVC ainda o manipulará perfeitamente. Ele usa a
reflexão e recursão para percorrer as propriedades de tipos complexos procurando
correspondências. A associação de modelos procura o padrão
nome_do_parâmetro.nome_da_propriedade para associar valores a propriedades. Se ela não
encontrar os valores correspondentes desse formulário, ela tentará associar usando apenas o
nome da propriedade. Para esses tipos como tipos Collection , a associação de modelos procura
correspondências para parameter_name [index] ou apenas [index]. A associação de modelos trata
tipos Dictionary da mesma forma, solicitando parameter_name[key ] ou apenas [key ], desde que
as chaves sejam tipos simples. As chaves compatíveis correspondem o HTML dos nomes de
campo e os auxiliares de marca gerados para o mesmo tipo de modelo. Isso permite a ida e vinda
dos valores, de modo que os campos de formulário permaneçam preenchidos com a entrada do
usuário para sua conveniência, por exemplo, quando os dados associados de uma criação ou
edição não são aprovados na validação.
Para que a associação ocorra, a classe precisa ter um construtor padrão público e o membro a ser
associado precisa ser propriedades graváveis públicas. Quando a associação de modelos ocorrer,
só será criada uma instância da classe com o construtor padrão público e, em seguida, as
propriedades poderão ser definidas.
Quando um parâmetro é associado, a associação de modelos para de procurar valores com esse
nome e ela passa para associar o próximo parâmetro. Caso contrário, o comportamento padrão
da associação de modelos define parâmetros com seus valores padrão, dependendo de seu tipo:
T[] : com a exceção de matrizes do tipo byte[] , a associação define parâmetros do tipo
T[] como Array.Empty<T>() . Matrizes do tipo byte[] são definidas como null .

Tipos de referência: a associação cria uma instância de uma classe com o construtor padrão
sem configurar propriedades. No entanto, a associação de modelos define parâmetros
string como null .

Tipos que permitem valor nulo: os tipos que permitem valor nulo são definidos como
null . No exemplo acima, a associação de modelos define id como null , pois ele é do
tipo int? .
Tipos de valor: os tipos de valor que não permitem valor nulo do tipo T são definidos
como default(T) . Por exemplo, a associação de modelos definirá um parâmetro int id
como 0. Considere o uso da validação de modelo ou de tipos que permitem valor nulo, em
vez de depender de valores padrão.
Se a associação falhar, o MVC não gerará um erro. Todas as ações que aceitam a entrada do
usuário devem verificar a propriedade ModelState.IsValid .
Observação: cada entrada na propriedade ModelState do controlador é uma ModelStateEntry
que contém uma propriedade Errors . Raramente é necessário consultar essa coleção por conta
própria. Use ModelState.IsValid em seu lugar.
Além disso, há alguns tipos de dados especiais que o MVC precisa considerar ao realizar a
associação de modelos:
IFormFile , IEnumerable<IFormFile> : um ou mais arquivos carregados que fazem parte da
solicitação HTTP.
CancellationToken : usado para cancelar a atividade em controladores assíncronos.

Esses tipos podem ser associados a parâmetros de ação ou a propriedades em um tipo de classe.
Após a conclusão da associação de modelos, a Validação ocorrerá. A associação de modelos
padrão funciona bem para a maioria dos cenários de desenvolvimento. Também é extensível e,
portanto, se você tiver necessidades exclusivas, poderá personalizar o comportamento interno.

Personalizar o comportamento da associação de modelos


com atributos
O MVC contém vários atributos que podem ser usados para direcionar seu comportamento de
associação de modelos padrão para outra fonte. Por exemplo, você pode especificar se a
associação é obrigatória para uma propriedade ou se ela nunca deve ocorrer usando os atributos
[BindRequired] ou [BindNever] . Como alternativa, você pode substituir a fonte de dados padrão
e especificar a fonte de dados do associador de modelos. Veja abaixo uma lista dos atributos de
associação de modelos:
[BindRequired] : esse atributo adiciona um erro de estado do modelo se a associação não
pode ocorrer.
[BindNever] : instrui o associador de modelos a nunca associar a esse parâmetro.
[FromHeader] , [FromQuery] , [FromRoute] , [FromForm] : use-os para especificar a origem da
associação exata que deseja aplicar.
[FromServices] : esse atributo usa a injeção de dependência para associar parâmetros de
serviços.
[FromBody]: use os formatadores configurados para associar dados do corpo da solicitação.
O formatador é selecionado de acordo com o tipo de conteúdo da solicitação.
[ModelBinder] : usado para substituir o associador de modelos padrão, a origem da
associação e o nome.
Os atributos são ferramentas muito úteis quando você precisa substituir o comportamento
padrão da associação de modelos.

Associar dados formatados do corpo da solicitação


Os dados de solicitação podem ser recebidos em uma variedade de formatos, incluindo JSON,
XML e muitos outros. Quando você usa o atributo [FromBody] para indicar que deseja associar
um parâmetro a dados no corpo da solicitação, o MVC usa um conjunto configurado de
formatadores para manipular os dados de solicitação de acordo com seu tipo de conteúdo. Por
padrão, o MVC inclui uma classe JsonInputFormatter para manipular dados JSON, mas você
pode adicionar outros formatadores para manipular XML e outros formatos personalizados.
OBSERVAÇÃO
Pode haver, no máximo, um parâmetro por ação decorado com [FromBody] . O tempo de execução do
ASP.NET Core MVC delega a responsabilidade de ler o fluxo da solicitação ao formatador. Depois que o
fluxo da solicitação é lido para um parâmetro, geralmente, não é possível ler o fluxo da solicitação
novamente para associar outros parâmetros [FromBody] .

OBSERVAÇÃO
O JsonInputFormatter é o formatador padrão e se baseia no Json.NET.

O ASP.NET seleciona formatadores de entrada de acordo com o cabeçalho Content-Type e o tipo


do parâmetro, a menos que haja um atributo aplicado a ele especificando o contrário. Se deseja
usar XML ou outro formato, configure-o no arquivo Startup.cs, mas talvez você precise obter
primeiro uma referência a Microsoft.AspNetCore.Mvc.Formatters.Xml usando o NuGet. O código de
inicialização deverá ter uma aparência semelhante a esta:

public void ConfigureServices(IServiceCollection services)


{
services.AddMvc()
.AddXmlSerializerFormatters();
}

O código do arquivo Startup.cs contém um método ConfigureServices com um argumento


services que você pode usar para criar serviços para o aplicativo ASP.NET. No exemplo, estamos
adicionando um formatador XML como um serviço que o MVC fornecerá para este aplicativo. O
argumento options passado para o método AddMvc permite que você adicione e gerencie filtros,
formatadores e outras opções do sistema no MVC após a inicialização do aplicativo. Em seguida,
aplique o atributo Consumes a classes do controlador ou métodos de ação para trabalhar com o
formato desejado.
Associação de modelos personalizada
Estenda a associação de modelos escrevendo seus próprios associadores de modelos
personalizados. Saiba mais sobre a associação de modelos personalizada.
Introdução à validação do modelo no ASP.NET
Core MVC
08/02/2018 • 29 min to read • Edit Online

Por Rachel Appel

Introdução à validação do modelo


Antes que um aplicativo armazene dados em um banco de dados, ele deve validá-los. Os dados precisam
ser verificados quanto a possíveis ameaças de segurança, se estão formatados corretamente por tipo e
tamanho e precisam estar em conformidade com as regras. A validação é necessária, embora possa ser
redundante e monótona de ser implementada. No MVC, a validação ocorre no cliente e no servidor.
Felizmente, o .NET abstraiu a validação para atributos de validação. Esses atributos contêm o código de
validação, reduzindo a quantidade de código que precisa ser escrita.
Exibir ou baixar amostra do GitHub.

Atributos de validação
Os atributos de validação são uma maneira de configurar a validação do modelo; portanto, é
conceitualmente semelhante à validação em campos de tabelas de banco de dados. Isso inclui restrições,
como atribuição de tipos de dados ou campos obrigatórios. Outros tipos de validação incluem a aplicação
de padrões a dados para impor regras de negócio, como um cartão de crédito, número de telefone ou
endereço de email. Os atributos de validação simplificam e facilitam o uso da imposição desses requisitos.
Veja abaixo um modelo Movie anotado de um aplicativo que armazena informações sobre filmes e
programas de TV. A maioria das propriedades é obrigatória e várias propriedades de cadeia de caracteres
têm requisitos de tamanho. Além disso, há uma restrição de intervalo numérico em vigor para a
propriedade Price de 0 a US$ 999,99, juntamente com um atributo de validação personalizado.
public class Movie
{
public int Id { get; set; }

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

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

[Required]
[StringLength(1000)]
public string Description { get; set; }

[Range(0, 999.99)]
public decimal Price { get; set; }

[Required]
public Genre Genre { get; set; }

public bool Preorder { get; set; }


}

A simples leitura do modelo revela as regras sobre os dados para esse aplicativo, facilitando a
manutenção do código. Veja abaixo vários atributos de validação internos populares:
[CreditCard] : valida se a propriedade tem um formato de cartão de crédito.
[Compare] : valida se duas propriedades em um modelo são correspondentes.
[EmailAddress] : valida se a propriedade tem um formato de email.
[Phone] : valida se a propriedade tem um formato de telefone.
[Range] : valida se o valor da propriedade está dentro do intervalo especificado.
[RegularExpression] : valida se os dados correspondem à expressão regular especificada.
[Required] : torna uma propriedade obrigatória.
[StringLength]: valida se uma propriedade de cadeia de caracteres tem, no máximo, o tamanho
máximo especificado.
[Url] : valida se a propriedade tem um formato de URL.

O MVC dá suporte a qualquer atributo derivado de ValidationAttribute para fins de validação. Muitos
atributos de validação úteis podem ser encontrados no namespace
System.ComponentModel.DataAnnotations.
Pode haver ocasiões em que você precisa de mais recursos do que os atributos internos fornecem. Para
essas ocasiões, é possível criar atributos de validação personalizados pela derivação de
ValidationAttribute ou alteração do modelo para implementar IValidatableObject .

Observações sobre o uso do atributo Required


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 aplicativo não executa nenhuma
verificação de validação do lado do servidor para tipos que não permitem valor nulo marcados como
Required .
A associação de modelos do MVC, que não está preocupada com a validação nem com atributos de
validação, rejeita o envio de um campo de formulário que contém um valor ausente ou espaço em branco
para um tipo que não permite valor nulo. Na ausência de um atributo BindRequired na propriedade de
destino, a associação de modelos ignora dados ausentes para tipos que não permitem valor nulo, nos
quais o campo de formulário está ausente nos dados de formulário de entrada.
O atributo BindRequired (consulte também Personalizar o comportamento da associação de modelos
com atributos) é útil para garantir que os dados de formulário estejam completos. Quando aplicado a
uma propriedade, o sistema de associação de modelos exige um valor para essa propriedade. Quando
aplicado a um tipo, o sistema de associação de modelos exige valores para todas as propriedades desse
tipo.
Quando você usa um tipo Nullable<T> (por exemplo, decimal? ou System.Nullable<decimal> ) e marca-o
como Required , é realizada uma verificação de validação do lado do servidor, como se a propriedade
fosse um tipo que permite valor nulo padrão (para exemplo, uma string ).
A validação do lado do cliente exige um valor para um campo de formulário que corresponde a uma
propriedade de modelo que foi marcada como Required e para uma propriedade de tipo que não
permite valor nulo que ainda não foi marcada como Required . Required pode ser usado para controlar a
mensagem de erro de validação do lado do cliente.

Estado do modelo
O estado do modelo representa erros de validação nos valores de formulário HTML enviados.
O MVC continuará validando os campos até atingir o número máximo de erros (200 por padrão).
Configure esse número inserindo o seguinte código no método ConfigureServices no arquivo Startup.cs:

services.AddMvc(options => options.MaxModelValidationErrors = 50);

Tratando erros do estado do modelo


A validação do modelo ocorre antes da invocação de cada ação do controlador e é responsabilidade do
método de ação inspecionar ModelState.IsValid e responder de forma adequada. Em muitos casos, a
resposta apropriada é retornar uma resposta de erro, de preferência, fornecendo detalhes sobre o motivo
pelo qual houve falha na validação do modelo.
Alguns aplicativos optarão por seguir uma convenção padrão para lidar com erros de validação do
modelo, caso em que um filtro pode ser um local adequado para implementar uma política como essa. É
necessário testar o comportamento das ações com estados de modelo válidos e inválidos.

Validação manual
Após a conclusão da associação e validação de modelos, recomendamos repetir partes dela. Por exemplo,
um usuário pode ter inserido um texto em um campo que espera um inteiro ou talvez você precise
calcular um valor da propriedade de um modelo.
Talvez seja necessário executar a validação manualmente. Para fazer isso, chame o método
TryValidateModel , conforme mostrado aqui:

TryValidateModel(movie);
Validação personalizada
Os atributos de validação funcionam para a maior parte das necessidades de validação. No entanto,
algumas regras de validação são específicas ao negócio. As regras podem não ser técnicas comuns de
validação de dados, como garantir que um campo seja obrigatório ou que ele esteja em conformidade
com um intervalo de valores. Para esses cenários, os atributos de validação personalizados são uma ótima
solução. É fácil criar seus próprios atributos de validação personalizados no MVC. Basta herdar do
ValidationAttribute e substituir o método IsValid . O método IsValid aceita dois parâmetros: o
primeiro é um objeto chamado value e o segundo é um objeto ValidationContext chamado
validationContext. Value refere-se ao valor real do campo que o validador personalizado está validando.
Na amostra a seguir, uma regra de negócios indica que os usuários não podem definir o gênero como
Clássico para um filme lançado após 1960. O atributo [ClassicMovie] verifica o gênero primeiro e, se ele
for um clássico, verificará a data de lançamento para ver se ela é posterior a 1960. Se ele tiver sido
lançado após 1960, a validação falhará. O atributo aceita um parâmetro de inteiro que representa o ano
que pode ser usado para validar os dados. Capture o valor do parâmetro no construtor do atributo,
conforme mostrado aqui:

public class ClassicMovieAttribute : ValidationAttribute, IClientModelValidator


{
private int _year;

public ClassicMovieAttribute(int Year)


{
_year = Year;
}

protected override ValidationResult IsValid(object value, ValidationContext validationContext)


{
Movie movie = (Movie)validationContext.ObjectInstance;

if (movie.Genre == Genre.Classic && movie.ReleaseDate.Year > _year)


{
return new ValidationResult(GetErrorMessage());
}

return ValidationResult.Success;
}

A variável movie acima representa um objeto Movie que contém os dados do envio do formulário a
serem validados. Nesse caso, o código de validação verifica a data e o gênero no método IsValid da
classe ClassicMovieAttribute de acordo com as regras. Após a validação bem-sucedida, IsValid retorna
um código ValidationResult.Success e, quando a validação falha, um ValidationResult com uma
mensagem de erro. Quando um usuário modificar o campo Genre e enviar o formulário, o método
IsValid da ClassicMovieAttribute verificará se o filme é um clássico. Como qualquer atributo interno,
aplique o atributo ClassicMovieAttribute a uma propriedade como ReleaseDate para garantir que a
validação ocorra, conforme mostrado no exemplo de código anterior. Como o exemplo funciona apenas
com tipos Movie , a melhor opção é usar IValidatableObject , conforme mostrado no parágrafo a seguir.
Como alternativa, esse mesmo código pode ser colocado no modelo implementando o método Validate
na interface IValidatableObject . Embora os atributos de validação personalizados funcionem bem para
validar propriedades individuais, a implementação de IValidatableObject pode ser usada para
implementar a validação no nível da classe como visto aqui.
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
{
yield return new ValidationResult(
$"Classic movies must have a release year earlier than {_classicYear}.",
new[] { "ReleaseDate" });
}
}

Validação do lado do cliente


A validação do lado do cliente é uma ótima conveniência para usuários. Ela economiza tempo que de
outra forma seria gasto aguardando uma viagem de ida e volta para o servidor. Em termos de negócios,
até mesmo algumas frações de segundos multiplicados por centenas de vezes por dia chega a ser muito
tempo, despesa e frustração. A validação imediata e simples permite que os usuários trabalhem com mais
eficiência e façam contribuições e produzam entradas e saídas de melhor qualidade.
É necessário ter uma exibição com as referências de script JavaScript corretas em vigor para a validação
do lado do cliente para que ela funcione como visto aqui.

<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.2.0.min.js"></script>

<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.16.0/jquery.validate.min.js"></script>
<script
src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.m
in.js"></script>

O script do jQuery Unobtrusive Validation é uma biblioteca de front-end personalizada da Microsoft que
se baseia no popular plug-in jQuery Validate. Sem o jQuery Unobtrusive Validation, você precisará
codificar a mesma lógica de validação em dois locais: uma vez nos atributos de validação do lado do
servidor nas propriedades do modelo e, em seguida, novamente nos scripts do lado do cliente (os
exemplos para o método validate() do jQuery Validate mostram a complexidade que isso pode gerar).
Em vez disso, os Auxiliares de Marca e os Auxiliares HTML do MVC podem usar os atributos de validação
e os metadados de tipo das propriedades do modelo para renderizar atributos de dados HTML 5 nos
elementos de formulário que precisam de validação. O MVC gera os atributos data- para atributos
internos e personalizados. Em seguida, o jQuery Unobtrusive Validation analisa os atributos data- e
passa a lógica para o jQuery Validate, "copiando" efetivamente a lógica de validação do lado do servidor
para o cliente. Exiba erros de validação no cliente usando os auxiliares de marca relevantes, conforme
mostrado aqui:

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

Os auxiliares de marca acima renderizam o HTML abaixo. Observe que os atributos data- na saída
HTML correspondem aos atributos de validação da propriedade ReleaseDate . O atributo
data-val-required abaixo contém uma mensagem de erro a ser exibida se o usuário não preencher o
campo de data de lançamento. O jQuery Unobtrusive Validation passa esse valor para o método
required()do jQuery Validate, que, por sua vez, exibe essa mensagem no elemento <span>
complementar.

<form action="/Movies/Create" method="post">


<div class="form-horizontal">
<h4>Movie</h4>
<div class="text-danger"></div>
<div class="form-group">
<label class="col-md-2 control-label" for="ReleaseDate">ReleaseDate</label>
<div class="col-md-10">
<input class="form-control" type="datetime"
data-val="true" data-val-required="The ReleaseDate field is required."
id="ReleaseDate" name="ReleaseDate" value="" />
<span class="text-danger field-validation-valid"
data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
</div>
</div>
</div>
</form>

A validação do lado do cliente impede o envio até que o formulário seja válido. O botão Enviar executa o
JavaScript que envia o formulário ou exibe mensagens de erro.
O MVC determina os valores de atributo de tipo com base no tipo de dados .NET de uma propriedade,
possivelmente, substituído com o uso de atributos [DataType] . O atributo [DataType] base não faz
nenhuma validação real do lado do servidor. Os navegadores escolhem suas próprias mensagens de erro
e exibem os erros da maneira desejada. No entanto, o pacote do jQuery Unobtrusive Validation pode
substituir as mensagens e exibi-las de forma consistente com outras. Isso ocorre de forma mais evidente
quando os usuários aplicam subclasses [DataType] , como [EmailAddress] .
Adicionar validação a formulários dinâmicos
Como o jQuery Unobtrusive Validation passa parâmetros e a lógica de validação para o jQuery Validate
quando a página é carregada pela primeira vez, os formulários gerados dinamicamente não exibem a
validação de forma automática. Em vez disso, é necessário instruir o jQuery Unobtrusive Validation a
analisar o formulário dinâmico imediatamente depois de criá-lo. Por exemplo, o código abaixo mostra
como você pode configurar a validação do lado do cliente em um formulário adicionado por meio do
AJAX.

$.get({
url: "https://url/that/returns/a/form",
dataType: "html",
error: function(jqXHR, textStatus, errorThrown) {
alert(textStatus + ": Couldn't add form. " + errorThrown);
},
success: function(newFormHTML) {
var container = document.getElementById("form-container");
container.insertAdjacentHTML("beforeend", newFormHTML);
var forms = container.getElementsByTagName("form");
var newForm = forms[forms.length - 1];
$.validator.unobtrusive.parse(newForm);
}
})

O método $.validator.unobtrusive.parse() aceita um seletor do jQuery para um argumento seu. Esse


método instrui o jQuery Unobtrusive Validation a analisar os atributos data- de formulários nesse
seletor. Os valores desses atributos são então passados para o plug-in jQuery Validate, de modo que o
formulário exiba as regras de validação do lado do cliente desejadas.
Adicionar validação a controles dinâmicos
Também atualize as regras de validação em um formulário quando controles individuais, como <input/> s
e <select/> s, são gerados dinamicamente. Não é possível passar seletores para esses elementos para o
método parse() diretamente, porque o formulário adjacente já foi analisado e não será atualizado. Em
vez disso, primeiro remova os dados de validação existentes e, em seguida, analise novamente todo o
formulário, conforme mostrado abaixo:

$.get({
url: "https://url/that/returns/a/control",
dataType: "html",
error: function(jqXHR, textStatus, errorThrown) {
alert(textStatus + ": Couldn't add form. " + errorThrown);
},
success: function(newInputHTML) {
var form = document.getElementById("my-form");
form.insertAdjacentHTML("beforeend", newInputHTML);
form.removeData("validator") // Added by the raw jQuery Validate
.removeData("unobtrusiveValidation"); // Added by jQuery Unobtrusive Validation
$.validator.unobtrusive.parse(form);
}
})

IClientModelValidator
Crie a lógica do lado do cliente para o atributo personalizado e a validação não invasiva a executará no
cliente para você automaticamente como parte da validação. A primeira etapa é controlar quais atributos
de dados são adicionados, pela implementação da interface IClientModelValidator , conforme mostrado
aqui:

public void AddValidation(ClientModelValidationContext context)


{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}

MergeAttribute(context.Attributes, "data-val", "true");


MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());

var year = _year.ToString(CultureInfo.InvariantCulture);


MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
}

Atributos que implementam essa interface podem adicionar atributos HTML a campos gerados. O exame
da saída do elemento ReleaseDate revela um HTML semelhante ao exemplo anterior, exceto que agora há
um atributo data-val-classicmovie que foi definido no método AddValidation de IClientModelValidator .

<input class="form-control" type="datetime"


data-val="true"
data-val-classicmovie="Classic movies must have a release year earlier than 1960."
data-val-classicmovie-year="1960"
data-val-required="The ReleaseDate field is required."
id="ReleaseDate" name="ReleaseDate" value="" />

A validação não invasiva usa os dados nos atributos data- para exibir mensagens de erro. No entanto, o
jQuery não reconhece regras ou mensagens até que você adicione-as ao objeto validator do jQuery.
Isso é mostrado no exemplo abaixo que adiciona um método chamado classicmovie , que contém um
código de validação do cliente personalizado para o objeto validator do jQuery.
$(function () {
$.validator.addMethod('classicmovie',
function (value, element, params) {
// Get element value. Classic genre has value '0'.
var genre = $(params[0]).val(),
year = params[1],
date = new Date(value);
if (genre && genre.length > 0 && genre[0] === '0') {
// Since this is a classic movie, invalid if release date is after given year.
return date.getFullYear() <= year;
}

return true;
});

$.validator.unobtrusive.adapters.add('classicmovie',
['year'],
function (options) {
var element = $(options.form).find('select#Genre')[0];
options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
options.messages['classicmovie'] = options.message;
});
});

Agora o jQuery tem as informações para executar a validação personalizada do JavaScript, bem como a
mensagem de erro a ser exibida se esse código de validação retornar falso.

Validação remota
A validação remota é um ótimo recurso a ser usado quando você precisa validar os dados no cliente em
relação aos dados no servidor. Por exemplo, o aplicativo pode precisar verificar se um email ou nome de
usuário já está em uso e precisa consultar uma grande quantidade de dados para fazer isso. O download
de conjuntos de dados grandes para validar um ou alguns campos consome muitos recursos. Isso
também pode expor informações confidenciais. Uma alternativa é fazer uma solicitação de ida e volta para
validar um campo.
Implemente a validação remota em um processo de duas etapas. Primeiro, é necessário anotar o modelo
com o atributo [Remote] . O atributo [Remote] aceita várias sobrecargas que podem ser usadas para
direcionar o JavaScript do lado do cliente para o código apropriado a ser chamado. O exemplo abaixo
aponta para o método de ação VerifyEmail do controlador Users .

[Remote(action: "VerifyEmail", controller: "Users")]


public string Email { get; set; }

A segunda etapa é colocar o código de validação no método de ação correspondente, conforme definido
no atributo [Remote] . De acordo com a documentação do método remote() do jQuery Validate:

A resposta do lado do servidor deve ser uma cadeia de caracteres JSON que deve ser "true" para
elementos válidos e pode ser "false" , undefined ou null para elementos inválidos, usando a
mensagem de erro padrão. Se a resposta do lado do servidor for uma cadeia de caracteres, por
exemplo, "That name is already taken, try peter123 instead" , essa cadeia de caracteres será exibida
como uma mensagem de erro personalizada, em vez do padrão.

A definição do método VerifyEmail() segue essas regras, conforme mostrado abaixo. Ela retorna uma
mensagem de erro de validação se o email já está sendo usado ou true se o email está livre e encapsula
o resultado em um objeto JsonResult . O lado do cliente pode então usar o valor retornado para
continuar ou exibir o erro, se necessário.

[AcceptVerbs("Get", "Post")]
public IActionResult VerifyEmail(string email)
{
if (!_userRepository.VerifyEmail(email))
{
return Json($"Email {email} is already in use.");
}

return Json(true);
}

Agora, quando os usuários inserem um email, o JavaScript na exibição faz uma chamada remota para ver
se o email já está sendo usado e, em caso afirmativo, exibe a mensagem de erro. Caso contrário, o usuário
pode enviar o formulário como de costume.
A propriedade AdditionalFields do atributo [Remote] é útil para validação de combinações de campos
em relação aos dados no servidor. Por exemplo, se o modelo User acima tiver duas propriedades
adicionais chamadas FirstName e LastName , é recomendável verificar se não há usuários que já têm esse
par de nomes. Defina as novas propriedades, conforme mostrado no seguinte código:

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]


public string FirstName { get; set; }
[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
public string LastName { get; set; }

AdditionalFields poderia ter sido definido de forma explícita com as cadeias de caracteres "FirstName" e
"LastName" , mas o uso do operador nameof dessa forma, simplifica a refatoração posterior. Em seguida, o
método de ação para executar a validação precisa aceitar dois argumentos, um para o valor de FirstName
e outro para o valor de LastName .

[AcceptVerbs("Get", "Post")]
public IActionResult VerifyName(string firstName, string lastName)
{
if (!_userRepository.VerifyName(firstName, lastName))
{
return Json(data: $"A user named {firstName} {lastName} already exists.");
}

return Json(data: true);


}

Agora, quando os usuários inserem um nome e sobrenome, o JavaScript:


Faz uma chamada remota para ver se esse par de nomes já está sendo usado.
Se o par já está sendo usado, uma mensagem de erro é exibida.
Caso contrário, o usuário pode enviar o formulário.
Se você precisa validar dois ou mais campos adicionais com o atributo [Remote] , forneça-os como uma
lista delimitada por vírgula. Por exemplo, para adicionar uma propriedade MiddleName ao modelo, defina
o atributo [Remote] , conforme mostrado no seguinte código:

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName) + "," +


nameof(LastName))]
public string MiddleName { get; set; }
AdditionalFields , como todos os argumentos de atributo, deve ser uma expressão de constante.
Portanto, você não deve usar uma cadeia de caracteres interpolada ou chamar string.Join() para
inicializar AdditionalFields . Para cada campo adicional adicionado ao atributo [Remote] , é necessário
adicionar outro argumento ao método de ação do controlador correspondente.
Exibições no ASP.NET Core MVC
10/04/2018 • 25 min to read • Edit Online

Por Steve Smith e Luke Latham


Este documento explica as exibições usadas em aplicativos do ASP.NET Core MVC. Para obter informações
sobre páginas do Razor, consulte Introdução a Páginas do Razor.
No padrão MVC (Model-View -Controller), a exibição trata da apresentação de dados do aplicativo e da
interação com o usuário. Uma exibição é um modelo HTML com marcação Razor inserida. A marcação Razor é
um código que interage com a marcação HTML para produzir uma página da Web que é enviada ao cliente.
No ASP.NET Core MVC, as exibições são arquivos .cshtml que usam a linguagem de programação C# na
marcação Razor. Geralmente, arquivos de exibição são agrupados em pastas nomeadas para cada um dos
controladores do aplicativo. As pastas são armazenadas em uma pasta chamada Views na raiz do aplicativo:

O controlador Home é representado por uma pasta Home dentro da pasta Views. A pasta Home contém as
exibições das páginas da Web About, Contact e Index (home page). Quando um usuário solicita uma dessas três
páginas da Web, ações do controlador Home determinam qual das três exibições é usada para compilar e
retornar uma página da Web para o usuário.
Use layouts para fornecer seções de páginas da Web consistentes e reduzir repetições de código. Layouts
geralmente contêm o cabeçalho, elementos de navegação e menu e o rodapé. O cabeçalho e o rodapé
geralmente contêm marcações repetitivas para muitos elementos de metadados, bem como links para ativos de
script e estilo. Layouts ajudam a evitar essa marcação repetitiva em suas exibições.
Exibições parciais reduzem a duplicação de código gerenciando as partes reutilizáveis das exibições. Por
exemplo, uma exibição parcial é útil para uma biografia do autor que aparece em várias exibições em um site de
blog. Uma biografia do autor é um conteúdo de exibição comum e não requer que um código seja executado
para produzi-lo para a página da Web. O conteúdo da biografia do autor é disponibilizado para a exibição
usando somente a associação de modelos, de modo que usar uma exibição parcial para esse tipo de conteúdo é
ideal.
Componentes de exibição são semelhantes a exibições parciais no sentido em que permitem reduzir códigos
repetitivos, mas são adequados para conteúdos de exibição que requerem que um código seja executado no
servidor para renderizar a página da Web. Componentes de exibição são úteis quando o conteúdo renderizado
requer uma interação com o banco de dados, como para o carrinho de compras de um site. Os componentes de
exibição não ficam limitados à associação de modelos para produzir a saída da página da Web.

Benefícios do uso de exibições


As exibições ajudam a estabelecer um design de SoC (Separation of Concerns) dentro de um aplicativo MVC
separando a marcação da interface do usuário de outras partes do aplicativo. Seguir um design de SoC faz com
que seu aplicativo seja modular, o que fornece vários benefícios:
A manutenção do aplicativo é mais fácil, porque ele é melhor organizado. Geralmente, as exibições são
agrupadas segundo os recursos do aplicativo. Isso facilita encontrar exibições relacionadas ao trabalhar em
um recurso.
As partes do aplicativo ficam acopladas de forma flexível. Você pode compilar e atualizar as exibições do
aplicativo separadamente da lógica de negócios e dos componentes de acesso a dados. É possível modificar
os modos de exibição do aplicativo sem precisar necessariamente atualizar outras partes do aplicativo.
É mais fácil testar as partes da interface do usuário do aplicativo porque as exibições são unidades separadas.
Devido à melhor organização, é menos provável que você repita acidentalmente seções da interface do
usuário.

Criando uma exibição


Exibições que são específicas de um controlador são criadas na pasta Views/ [NomeDoControlador ]. Exibições
que são compartilhadas entre controladores são colocadas na pasta Views/Shared. Para criar uma exibição,
adicione um novo arquivo e dê a ele o mesmo nome que o da ação de seu controlador associado, com a
extensão de arquivo .cshtml. Para criar uma exibição correspondente à ação About no controlador Home, crie um
arquivo About.cshtml na pasta Views/Home:

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

<p>Use this area to provide additional information.</p>

A marcação Razor começa com o símbolo @ . Execute instruções em C# colocando o código C# dentro de blocos
de código Razor entre chaves ( { ... } ). Por exemplo, consulte a atribuição de "About" para ViewData["Title"]
mostrado acima. É possível exibir valores em HTML simplesmente referenciando o valor com o símbolo @ . Veja
o conteúdo dos elementos <h2> e <h3> acima.
O conteúdo da exibição mostrado acima é apenas uma parte da página da Web inteira que é renderizada para o
usuário. O restante do layout da página e outros aspectos comuns da exibição são especificados em outros
arquivos de exibição. Para saber mais, consulte o tópico sobre Layout.

Como controladores especificam exibições


Normalmente, as exibições são retornadas de ações como um ViewResult, que é um tipo de ActionResult. O
método de ação pode criar e retornar um ViewResult diretamente, mas normalmente isso não é feito. Como a
maioria dos controladores herdam de Controller, basta usar o método auxiliar View para retornar o ViewResult :
HomeController.cs

public IActionResult About()


{
ViewData["Message"] = "Your application description page.";

return View();
}

Quando essa ação é retornada, a exibição About.cshtml mostrada na seção anterior é renderizada como a
seguinte página da Web:

O método auxiliar View tem várias sobrecargas. Opcionalmente, você pode especificar:
Uma exibição explícita a ser retornada:

return View("Orders");

Um modelo a ser passado para a exibição:

return View(Orders);

Um modo de exibição e um modelo:

return View("Orders", Orders);

Descoberta de exibição
Quando uma ação retorna uma exibição, um processo chamado descoberta de exibição ocorre. Esse processo
determina qual arquivo de exibição é usado com base no nome da exibição.
O comportamento padrão do método View ( return View(); ) é retornar uma exibição com o mesmo nome que
o método de ação do qual ela é chamada. Por exemplo, o nome do método ActionResult de About do
controlador é usado para pesquisar um arquivo de exibição chamado About.cshtml. Primeiro, o tempo de
execução pesquisa pela exibição na pasta Views/[NomeDoControlador ]. Se não encontrar uma exibição
correspondente nela, ele procura pela exibição na pasta Shared.
Não importa se você retornar implicitamente o ViewResult com return View(); ou se passar explicitamente o
nome de exibição para o método View com return View("<ViewName>"); . Nos dois casos, a descoberta de
exibição pesquisa por um arquivo de exibição correspondente nesta ordem:
1. Views/[ControllerName]/[ViewName].cshtml
2. Views/Shared/[NomeDaExibição ].cshtml
Um caminho de arquivo de exibição pode ser fornecido em vez de um nome de exibição. Se um caminho
absoluto que começa na raiz do aplicativo (ou é iniciado por "/" ou "~ /") estiver sendo usado, a extensão .cshtml
deverá ser especificada:
return View("Views/Home/About.cshtml");

Você também pode usar um caminho relativo para especificar exibições em diretórios diferentes sem a extensão
.cshtml. Dentro do HomeController , você pode retornar a exibição Index de suas exibições Manage com um
caminho relativo:

return View("../Manage/Index");

De forma semelhante, você pode indicar o atual diretório específico do controlador com o prefixo ". /":

return View("./About");

Exibições parciais e componentes de exibição usam mecanismos de descoberta semelhantes (mas não idênticos).
É possível personalizar a convenção padrão de como as exibições ficam localizadas dentro do aplicativo usando
um IViewLocationExpander personalizado.
A descoberta de exibição depende da localização de arquivos de exibição pelo nome do arquivo. Se o sistema de
arquivos subjacente diferenciar maiúsculas de minúsculas, os nomes de exibição provavelmente diferenciarão
maiúsculas de minúsculas. Para fins de compatibilidade de sistemas operacionais, padronize as maiúsculas e
minúsculas dos nomes de controladores e de ações e dos nomes de arquivos e pastas de exibição. Se encontrar
um erro indicando que não é possível encontrar um arquivo de exibição ao trabalhar com um sistema de
arquivos que diferencia maiúsculas de minúsculas, confirme que o uso de maiúsculas e minúsculas é
correspondente entre o arquivo de exibição solicitado e o nome do arquivo de exibição real.
Siga a melhor prática de organizar a estrutura de arquivos de suas exibições de forma a refletir as relações entre
controladores, ações e exibições para facilidade de manutenção e clareza.

Passando dados para exibições


Você pode passar dados para exibições adotando várias abordagens. A abordagem mais robusta é especificar
um tipo de modelo na exibição. Esse modelo é conhecido como viewmodel. Você passa uma instância do tipo
viewmodel para a exibição da ação.
Usar um viewmodel para passar dados para uma exibição permite que a exibição tire proveito da verificação de
tipo forte. Tipagem forte (ou fortemente tipado) significa que cada variável e constante tem um tipo definido
explicitamente (por exemplo, string , int ou DateTime ). A validade dos tipos usados em uma exibição é
verificada em tempo de compilação.
O Visual Studio e o Visual Studio Code listam membros de classe fortemente tipados usando um recurso
chamado IntelliSense. Quando quiser ver as propriedades de um viewmodel, digite o nome da variável para o
viewmodel, seguido por um ponto final ( . ). Isso ajuda você a escrever código mais rapidamente e com menos
erros.
Especifique um modelo usando a diretiva @model . Use o modelo com @Model :

@model WebApplication1.ViewModels.Address

<h2>Contact</h2>
<address>
@Model.Street<br>
@Model.City, @Model.State @Model.PostalCode<br>
<abbr title="Phone">P:</abbr> 425.555.0100
</address>
Para fornecer o modelo à exibição, o controlador o passa como um parâmetro:

public IActionResult Contact()


{
ViewData["Message"] = "Your contact page.";

var viewModel = new Address()


{
Name = "Microsoft",
Street = "One Microsoft Way",
City = "Redmond",
State = "WA",
PostalCode = "98052-6399"
};

return View(viewModel);
}

Não há restrições quanto aos tipos de modelo que você pode fornecer a uma exibição. Recomendamos usar
viewmodels do tipo POCO (Plain Old CLR Object – objeto CRL básico) com pouco ou nenhum comportamento
(métodos) definido. Geralmente, classes de viewmodel são armazenadas na pasta Models ou em uma pasta
ViewModels separada na raiz do aplicativo. O viewmodel Address usado no exemplo acima é um viewmodel
POCO armazenado em um arquivo chamado Address.cs:

namespace WebApplication1.ViewModels
{
public class Address
{
public string Name { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
}
}

OBSERVAÇÃO
Nada impede que você use as mesmas classes para seus tipos de viewmodel e seus tipos de modelo de negócios. No
entanto, o uso de modelos separados permite que suas exibições variem independentemente das partes de lógica de
negócios e de acesso a dados do aplicativo. A separação de modelos e viewmodels também oferece benefícios de
segurança quando os modelos usam associação de modelos e validação para dados enviados ao aplicativo pelo usuário.

Dados com tipagem fraca (ViewData e ViewBag)


Observação: ViewBag não está disponível em páginas do Razor.
Além de exibições fortemente tipadas, as exibições têm acesso a uma coleção de dados com tipagem fraca
(também chamada de tipagem flexível). Diferente dos tipos fortes, ter tipos fracos (ou tipos flexíveis) significa que
você não declara explicitamente o tipo dos dados que está usando. Você pode usar a coleção de dados com
tipagem fraca para passar pequenas quantidades de dados para dentro e para fora de controladores e exibições.

PASSAR DADOS ENTRE... EXEMPLO

Um controlador e uma exibição Preencher uma lista suspensa com os dados.

Uma exibição e uma exibição de layout Definir o conteúdo do elemento <title> na exibição de
layout de um arquivo de exibição.
PASSAR DADOS ENTRE... EXEMPLO

Uma exibição parcial e uma exibição Um widget que exibe dados com base na página da Web que
o usuário solicitou.

Essa coleção pode ser referenciada por meio das propriedades ViewData ou ViewBag em controladores e
exibições. A propriedade ViewData é um dicionário de objetos com tipagem fraca. A propriedade ViewBag é um
wrapper em torno de ViewData que fornece propriedades dinâmicas à coleção de ViewData subjacente.
ViewData e ViewBag são resolvidos dinamicamente em tempo de execução. Uma vez que não oferecem
verificação de tipo em tempo de compilação, geralmente ambos são mais propensos a erros do que quando um
viewmodel é usado. Por esse motivo, alguns desenvolvedores preferem nunca usar ViewData e ViewBag ou usá-
los o mínimo possível.
ViewData
ViewData é um objeto ViewDataDictionary acessado por meio de chaves string . Dados de cadeias de
caracteres podem ser armazenados e usados diretamente, sem a necessidade de conversão, mas você precisa
converter os valores de outros objetos ViewData em tipos específicos quando extraí-los. Você pode usar
ViewData para passar dados de controladores para exibições e dentro das exibições, incluindo exibições parciais
e layouts.
A seguir, temos um exemplo que define valores para uma saudação e um endereço usando ViewData em uma
ação:

public IActionResult SomeAction()


{
ViewData["Greeting"] = "Hello";
ViewData["Address"] = new Address()
{
Name = "Steve",
Street = "123 Main St",
City = "Hudson",
State = "OH",
PostalCode = "44236"
};

return View();
}

Trabalhar com os dados em uma exibição:

@{
// Since Address isn't a string, it requires a cast.
var address = ViewData["Address"] as Address;
}

@ViewData["Greeting"] World!

<address>
@address.Name<br>
@address.Street<br>
@address.City, @address.State @address.PostalCode
</address>

ViewBag
Observação: ViewBag não está disponível em páginas do Razor.
ViewBag é um objeto DynamicViewData que fornece acesso dinâmico aos objetos armazenados em ViewData .
Pode ser mais conveniente trabalhar com ViewBag , pois ele não requer uma conversão. O exemplo a seguir
mostra como usar ViewBag com o mesmo resultado que o uso de ViewData acima:

public IActionResult SomeAction()


{
ViewBag.Greeting = "Hello";
ViewBag.Address = new Address()
{
Name = "Steve",
Street = "123 Main St",
City = "Hudson",
State = "OH",
PostalCode = "44236"
};

return View();
}

@ViewBag.Greeting World!

<address>
@ViewBag.Address.Name<br>
@ViewBag.Address.Street<br>
@ViewBag.Address.City, @ViewBag.Address.State @ViewBag.Address.PostalCode
</address>

Usando ViewData e ViewBag simultaneamente


Observação: ViewBag não está disponível em páginas do Razor.
Como ViewDatae ViewBag fazem referência à mesma coleção ViewData subjacente, você pode usar ViewData e
ViewBag , além de misturá-los e combiná-los ao ler e gravar valores.

Defina o título usando ViewBag e a descrição usando ViewData na parte superior de uma exibição About.cshtml:

@{
Layout = "/Views/Shared/_Layout.cshtml";
ViewBag.Title = "About Contoso";
ViewData["Description"] = "Let us tell you about Contoso's philosophy and mission.";
}

Leia as propriedades, mas inverta o uso de ViewData e ViewBag . No arquivo _Layout.cshtml, obtenha o título
usando ViewData e a descrição usando ViewBag :

<!DOCTYPE html>
<html lang="en">
<head>
<title>@ViewData["Title"]</title>
<meta name="description" content="@ViewBag.Description">
...

Lembre-se de que cadeias de caracteres não exigem uma conversão para ViewData . Você pode usar
@ViewData["Title"] sem converter.

Usar ViewData e ViewBag ao mesmo tempo funciona, assim como misturar e combinar e leitura e a gravação
das propriedades. A seguinte marcação é renderizada:
<!DOCTYPE html>
<html lang="en">
<head>
<title>About Contoso</title>
<meta name="description" content="Let us tell you about Contoso's philosophy and mission.">
...

Resumo das diferenças entre ViewData e ViewBag


ViewBag não está disponível em páginas Razor.
ViewData
Deriva de ViewDataDictionary, de forma que tem propriedades de dicionário que podem ser úteis,
como ContainsKey , Add , Remove e Clear .
Chaves no dicionário são cadeias de caracteres, de forma que espaços em branco são permitidos.
Exemplo: ViewData["Some Key With Whitespace"]
Qualquer tipo diferente de string deve ser convertido na exibição para usar ViewData .
ViewBag
Deriva de DynamicViewData, de forma que permite a criação de propriedades dinâmicas usando a
notação de ponto ( @ViewBag.SomeKey = <value or object> ) e nenhuma conversão é necessária. A
sintaxe de ViewBag torna mais rápido adicioná-lo a controladores e exibições.
Mais simples de verificar quanto à presença de valores nulos. Exemplo: @ViewBag.Person?.Name

Quando usar ViewData ou ViewBag


ViewData e ViewBag são abordagens igualmente válidas para passar pequenas quantidades de dados entre
controladores e exibições. A escolha de qual delas usar é baseada na preferência. Você pode misturar e combinar
objetos ViewData e ViewBag , mas é mais fácil ler e manter o código quando uma abordagem é usada de
maneira consistente. Ambas as abordagens são resolvidas dinamicamente em tempo de execução e, portanto,
são propensas a causar erros de tempo de execução. Algumas equipes de desenvolvimento as evitam.
Exibições dinâmicas
Exibições que não declaram um tipo de modelo usando @model , mas que têm uma instância de modelo passada
a elas (por exemplo, return View(Address); ) podem referenciar as propriedades da instância dinamicamente:

<address>
@Model.Street<br>
@Model.City, @Model.State @Model.PostalCode<br>
<abbr title="Phone">P:</abbr> 425.555.0100
</address>

Esse recurso oferece flexibilidade, mas não oferece proteção de compilação ou IntelliSense. Se a propriedade não
existir, a geração da página da Web falhará em tempo de execução.

Mais recursos das exibições


Auxiliares de Marca facilitam a adição do comportamento do lado do servidor às marcações HTML existentes. O
uso de Auxiliares de marca evita a necessidade de escrever código personalizado ou auxiliares em suas exibições.
Auxiliares de marca são aplicados como atributos a elementos HTML e são ignorados por editores que não
podem processá-los. Isso permite editar e renderizar a marcação da exibição em várias ferramentas.
É possível gerar uma marcação HTML personalizada com muitos auxiliares HTML internos. Uma lógica de
interface do usuário mais complexa pode ser tratada por Componentes de exibição. Componentes de exibição
fornecem o mesmo SoC oferecido por controladores e exibições. Eles podem eliminar a necessidade de ações e
exibições que lidam com os dados usados por elementos comuns da interface do usuário.
Como muitos outros aspectos do ASP.NET Core, as exibições dão suporte à injeção de dependência, permitindo
que serviços sejam injetados em exibições.
Sintaxe Razor para ASP.NET Core
08/02/2018 • 22 min to read • Edit Online

Por Rick Anderson, Luke Latham, Taylor Mullen e Dan Vicarel


Razor é uma sintaxe de marcação para inserir código baseado em servidor em páginas da Web. A sintaxe Razor
é composta pela marcação Razor, por C# e por HTML. Arquivos que contêm Razor geralmente têm a extensão
de arquivo .cshtml.

Renderizando HTML
A linguagem padrão do Razor padrão é o HTML. Renderizar HTML da marcação Razor não é diferente de
renderizar HTML de um arquivo HTML. A marcação HTML em arquivos Razor .cshtml é renderizada pelo
servidor inalterado.

Sintaxe Razor
O Razor dá suporte a C# e usa o símbolo @ para fazer a transição de HTML para C#. O Razor avalia expressões
em C# e as renderiza na saída HTML.
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# simples.
Para fazer o escape de um símbolo @ na marcação Razor, use um segundo símbolo @ :

<p>@@Username</p>

O código é renderizado em HTML com um único símbolo @ :

<p>@Username</p>

Conteúdo e atributos HTML que contêm endereços de email não tratam o símbolo @ como um caractere de
transição. Os endereços de email no exemplo a seguir não são alterados pela análise do Razor:

<a href="mailto:Support@contoso.com">Support@contoso.com</a>

Expressões Razor implícitas


Expressões Razor implícitas são iniciadas por @ seguido do código C#:

<p>@DateTime.Now</p>
<p>@DateTime.IsLeapYear(2016)</p>

Com exceção da palavra-chave C# await , expressões implícitas não devem conter espaços. Se a instrução C#
tiver uma terminação clara, espaços podem ser misturados:

<p>@await DoSomething("hello", "world")</p>


Expressões implícitas não podem conter elementos genéricos de C#, pois caracteres dentro de colchetes ( <> )
são interpretados como uma marca HTML. O código a seguir é inválido:

<p>@GenericMethod<int>()</p>

O código anterior gera um erro de compilador semelhante a um dos seguintes:


O elemento "int" não foi fechado. Todos os elementos devem ter fechamento automático ou ter uma marca
de fim correspondente.
Não é possível converter o grupo de métodos "GenericMethod" em um "object" de tipo não delegado. Você
pretendia invocar o método?
Chamadas de método genérico devem ser encapsuladas em uma expressão Razor explícita ou em um bloco de
código Razor.

Expressões Razor explícitas


Expressões Razor explícitas consistem de um símbolo @ com parênteses equilibradas. Para renderizar a hora da
última semana, a seguinte marcação Razor é usada:

<p>Last week this time: @(DateTime.Now - TimeSpan.FromDays(7))</p>

Qualquer conteúdo dentro dos parênteses @() é avaliado e renderizado para a saída.
Expressões implícitas, descritas na seção anterior, geralmente não podem conter espaços. No código a seguir,
uma semana não é subtraída da hora atual:

<p>Last week: @DateTime.Now - TimeSpan.FromDays(7)</p>

O código renderiza o HTML a seguir:

<p>Last week: 7/7/2016 4:39:52 PM - TimeSpan.FromDays(7)</p>

Expressões explícitas podem ser usadas para concatenar texto com um resultado de expressão:

@{
var joe = new Person("Joe", 33);
}

<p>Age@(joe.Age)</p>

Sem a expressão explícita, <p>Age@joe.Age</p> é tratado como um endereço de email e <p>Age@joe.Age</p> é


renderizado. Quando escrito como uma expressão explícita, <p>Age33</p> é renderizado.
Expressões explícitas podem ser usadas para renderizar a saída de métodos genéricos em arquivos .cshtml. Em
uma expressão implícita, os caracteres dentro de colchetes ( <> ) são interpretados como uma marca HTML. A
seguinte marcação Razor é inválida:

<p>@GenericMethod<int>()</p>

O código anterior gera um erro de compilador semelhante a um dos seguintes:


O elemento "int" não foi fechado. Todos os elementos devem ter fechamento automático ou ter uma marca
de fim correspondente.
Não é possível converter o grupo de métodos "GenericMethod" em um "object" de tipo não delegado. Você
pretendia invocar o método?
A marcação a seguir mostra a maneira correta de escrever esse código. O código é escrito como uma expressão
explícita:

<p>@(GenericMethod<int>())</p>

Codificação de expressão
Expressões em C# que são avaliadas como uma cadeia de caracteres estão codificadas em HTML. Expressões
em C# que são avaliadas como IHtmlContent são renderizadas diretamente por meio IHtmlContent.WriteTo .
Expressões em C# que não são avaliadas como IHtmlContent são convertidas em uma cadeia de caracteres por
ToString e codificadas antes que sejam renderizadas.

@("<span>Hello World</span>")

O código renderiza o HTML a seguir:

&lt;span&gt;Hello World&lt;/span&gt;

O HTML é mostrado no navegador como:

<span>Hello World</span>

A saída HtmlHelper.Raw não é codificada, mas renderizada como marcação HTML.

AVISO
Usar HtmlHelper.Raw em uma entrada do usuário que não está limpa é um risco de segurança. A entrada do usuário
pode conter JavaScript mal-intencionado ou outras formas de exploração. Limpar a entrada do usuário é difícil. Evite usar
HtmlHelper.Raw com a entrada do usuário.

@Html.Raw("<span>Hello World</span>")

O código renderiza o HTML a seguir:

<span>Hello World</span>

Blocos de código Razor


Blocos de código Razor são iniciados por @ e delimitados por {} . Diferente das expressões, o código C#
dentro de blocos de código não é renderizado. Blocos de código e expressões em uma exibição compartilham o
mesmo escopo e são definidos em ordem:
@{
var quote = "The future depends on what you do today. - Mahatma Gandhi";
}

<p>@quote</p>

@{
quote = "Hate cannot drive out hate, only love can do that. - Martin Luther King, Jr.";
}

<p>@quote</p>

O código renderiza o HTML a seguir:

<p>The future depends on what you do today. - Mahatma Gandhi</p>


<p>Hate cannot drive out hate, only love can do that. - Martin Luther King, Jr.</p>

Transições implícitas
A linguagem padrão em um bloco de código é C#, mas a página Razor pode fazer a transição de volta para
HTML:

@{
var inCSharp = true;
<p>Now in HTML, was in C# @inCSharp</p>
}

Transição delimitada explícita


Para definir uma subseção de um bloco de código que deve renderizar HTML, circunde os caracteres para
renderização com as marca Razor <text>:

@for (var i = 0; i < people.Length; i++)


{
var person = people[i];
<text>Name: @person.Name</text>
}

Use essa abordagem para renderizar HTML que não está circundado por uma marca HTML. Sem uma marca
HTML ou Razor, ocorrerá um erro de tempo de execução do Razor.
A marca <text> é útil para controlar o espaço em branco ao renderizar conteúdo:
Somente o conteúdo entre a marca <text> é renderizado.
Não aparece nenhum espaço em branco antes ou depois da marca <text> na saída HTML.
Transição de linha explícita com @:
Para renderizar o restante de uma linha inteira como HTML dentro de um bloco de código, use a sintaxe @: :

@for (var i = 0; i < people.Length; i++)


{
var person = people[i];
@:Name: @person.Name
}

Sem o @: no código, será gerado um erro de tempo de execução do Razor.


Aviso: caracteres @ extra em um arquivo Razor podem causar erros do compilador em instruções mais adiante
no bloco. Esses erros do compilador podem ser difíceis de entender porque o erro real ocorre antes do erro
relatado. Esse erro é comum após combinar várias expressões implícitas/explícitas em um bloco de código
único.

Estruturas de controle
Estruturas de controle são uma extensão dos blocos de código. Todos os aspectos dos blocos de código
(transição para marcação, C# embutido) também se aplicam às seguintes estruturas:
Condicionais @if, else if, else e @switch
@if controla quando o código é executado:

@if (value % 2 == 0)
{
<p>The value was even.</p>
}

else e else if não exigem o símbolo @ :

@if (value % 2 == 0)
{
<p>The value was even.</p>
}
else if (value >= 1337)
{
<p>The value is large.</p>
}
else
{
<p>The value is odd and small.</p>
}

A marcação a seguir mostra como usar uma instrução switch:

@switch (value)
{
case 1:
<p>The value is 1!</p>
break;
case 1337:
<p>Your number is 1337!</p>
break;
default:
<p>Your number wasn't 1 or 1337.</p>
break;
}

Loop de @for, @foreach, @while e @do while


O HTML no modelo pode ser renderizado com instruções de controle em loop. Para renderizar uma lista de
pessoas:
@{
var people = new Person[]
{
new Person("Weston", 33),
new Person("Johnathon", 41),
...
};
}

Há suporte para as seguintes instruções em loop:


@for

@for (var i = 0; i < people.Length; i++)


{
var person = people[i];
<p>Name: @person.Name</p>
<p>Age: @person.Age</p>
}

@foreach

@foreach (var person in people)


{
<p>Name: @person.Name</p>
<p>Age: @person.Age</p>
}

@while

@{ var i = 0; }
@while (i < people.Length)
{
var person = people[i];
<p>Name: @person.Name</p>
<p>Age: @person.Age</p>

i++;
}

@do while

@{ var i = 0; }
@do
{
var person = people[i];
<p>Name: @person.Name</p>
<p>Age: @person.Age</p>

i++;
} while (i < people.Length);

@using composto
Em C#, uma instrução using é usada para garantir que um objeto seja descartado. No Razor, o mesmo
mecanismo é usado para criar Auxiliares HTML que têm conteúdo adicional. No código a seguir, os Auxiliares
HTML renderizam uma marca de formulário com a instrução @using :
@using (Html.BeginForm())
{
<div>
email:
<input type="email" id="Email" value="">
<button>Register</button>
</div>
}

Ações no nível de escopo podem ser executadas com Auxiliares de marca.


@try, catch, finally
O tratamento de exceções é semelhante ao de C#:

@try
{
throw new InvalidOperationException("You did something invalid.");
}
catch (Exception ex)
{
<p>The exception message: @ex.Message</p>
}
finally
{
<p>The finally statement.</p>
}

@lock
O Razor tem a capacidade de proteger seções críticas com instruções de bloqueio:

@lock (SomeLock)
{
// Do critical section work
}

Comentários
O Razor dá suporte a comentários em C# e HTML:

@{
/* C# comment */
// Another C# comment
}
<!-- HTML comment -->

O código renderiza o HTML a seguir:

<!-- HTML comment -->

Comentários em Razor são removidos pelo servidor antes que a página da Web seja renderizada. O Razor usa
@* *@ para delimitar comentários. O código a seguir é comentado, de modo que o servidor não renderiza
nenhuma marcação:
@*
@{
/* C# comment */
// Another C# comment
}
<!-- HTML comment -->
*@

Diretivas
Diretivas de Razor são representadas por expressões implícitas com palavras-chave reservadas após o símbolo
@ . Uma diretiva geralmente altera o modo como uma exibição é analisada ou habilita uma funcionalidade
diferente.
Compreender como o Razor gera código para uma exibição torna mais fácil entender como as diretivas
funcionam.

@{
var quote = "Getting old ain't for wimps! - Anonymous";
}

<div>Quote of the Day: @quote</div>

O código gera uma classe semelhante à seguinte:

public class _Views_Something_cshtml : RazorPage<dynamic>


{
public override async Task ExecuteAsync()
{
var output = "Getting old ain't for wimps! - Anonymous";

WriteLiteral("/r/n<div>Quote of the Day: ");


Write(output);
WriteLiteral("</div>");
}
}

Posteriormente neste artigo, a seção Exibindo a classe C# de Razor gerada para uma exibição explica como
exibir essa classe gerada.
@using
A diretiva @using adiciona a diretiva using de C# à exibição gerada:

@using System.IO
@{
var dir = Directory.GetCurrentDirectory();
}
<p>@dir</p>

@model
A diretiva @model especifica o tipo do modelo passado para uma exibição:

@model TypeNameOfModel

Em um aplicativo do ASP.NET Core MCV criado com contas de usuário individuais, a exibição
Views/Account/Login.cshtml contém a declaração de modelo a seguir:

@model LoginViewModel

A classe gerada herda de RazorPage<dynamic> :

public class _Views_Account_Login_cshtml : RazorPage<LoginViewModel>

O Razor expõe uma propriedade Model para acessar o modelo passado para a exibição:

<div>The Login Email: @Model.Email</div>

A diretiva @model especifica o tipo dessa propriedade. A diretiva especifica o T em RazorPage<T> da classe
gerada da qual a exibição deriva. Se a diretiva @model não for especificada, a propriedade Model será do tipo
dynamic . O valor do modelo é passado do controlador para a exibição. Para obter mais informações, consulte
[Modelos fortemente tipados e a palavra-chave @model.
@inherits
A diretiva @inherits fornece controle total da classe que a exibição herda:

@inherits TypeNameOfClassToInheritFrom

O código a seguir é um tipo de página Razor personalizado:

using Microsoft.AspNetCore.Mvc.Razor;

public abstract class CustomRazorPage<TModel> : RazorPage<TModel>


{
public string CustomText { get; } = "Gardyloo! - A Scottish warning yelled from a window before dumping
a slop bucket on the street below.";
}

O CustomText é exibido em uma exibição:

@inherits CustomRazorPage<TModel>

<div>Custom text: @CustomText</div>

O código renderiza o HTML a seguir:

<div>Custom text: Gardyloo! - A Scottish warning yelled from a window before dumping a slop bucket on the
street below.</div>

@model e @inherits podem ser usados na mesma exibição. @inherits pode estar em um arquivo
_ViewImports.cshtml que a exibição importa:

@inherits CustomRazorPage<TModel>

O código a seguir é um exemplo de exibição fortemente tipada:


@inherits CustomRazorPage<TModel>

<div>The Login Email: @Model.Email</div>


<div>Custom text: @CustomText</div>

Se "rick@contoso.com" for passado no modelo, a exibição gerará a seguinte marcação HTML:

<div>The Login Email: rick@contoso.com</div>


<div>Custom text: Gardyloo! - A Scottish warning yelled from a window before dumping a slop bucket on the
street below.</div>

@inject
A diretiva @inject permite que a página do Razor injete um serviço do contêiner de serviço em uma exibição.
Para obter mais informações, consulte Injeção de dependência em exibições.
@functions
A diretiva @functions permite que uma página do Razor adicione conteúdo no nível da função a uma exibição:

@functions { // C# Code }

Por exemplo:

@functions {
public string GetHello()
{
return "Hello";
}
}

<div>From method: @GetHello()</div>

O código gera a seguinte marcação HTML:

<div>From method: Hello</div>

O código a seguir é a classe C# do Razor gerada:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Razor;

public class _Views_Home_Test_cshtml : RazorPage<dynamic>


{
// Functions placed between here
public string GetHello()
{
return "Hello";
}
// And here.
#pragma warning disable 1998
public override async Task ExecuteAsync()
{
WriteLiteral("\r\n<div>From method: ");
Write(GetHello());
WriteLiteral("</div>\r\n");
}
#pragma warning restore 1998
@section
A diretiva @section é usada em conjunto com o layout para permitir que as exibições renderizem conteúdo em
partes diferentes da página HTML. Para obter mais informações, consulte Seções.

Auxiliares de Marca
Há três diretivas que relacionadas aos Auxiliares de marca.

DIRETIVA FUNÇÃO

Disponibiliza os Auxiliares de marca para uma exibição.

Remove os Auxiliares de marca adicionados anteriormente de


uma exibição.

Especifica um prefixo de marca para habilitar o suporte do


Auxiliar de marca e tornar explícito o uso do Auxiliar de
marca.

Palavras-chave reservadas ao Razor


Palavras-chave do Razor
page (requer o ASP.NET Core 2.0 e posteriores)
funções
herda
modelo
section
helper (atualmente sem suporte do ASP.NET Core)
Palavras-chave do Razor têm o escape feito com @(Razor Keyword) (por exemplo, @(functions) ).
Palavras-chave do Razor em C#
case
do
default
for
foreach
if
else
bloqueio
switch
try
catch
finally
using
while
Palavras-chave do Razor em C# precisam ter o escape duplo com @(@C# Razor Keyword) (por exemplo,
@(@case) ). O primeiro @ faz o escape do analisador Razor. O segundo @ faz o escape do analisador C#.

Palavras-chave reservadas não usadas pelo Razor


namespace
classe

Exibindo a classe C# do Razor gerada para uma exibição


Adicione a seguinte classe ao projeto do ASP.NET Core MVC:

using Microsoft.AspNetCore.Mvc.Razor.Extensions;
using Microsoft.AspNetCore.Razor.Language;

public class CustomTemplateEngine : MvcRazorTemplateEngine


{
public CustomTemplateEngine(RazorEngine engine, RazorProject project)
: base(engine, project)
{
}

public override RazorCSharpDocument GenerateCode(RazorCodeDocument codeDocument)


{
var csharpDocument = base.GenerateCode(codeDocument);
var generatedCode = csharpDocument.GeneratedCode;

// Look at generatedCode

return csharpDocument;
}
}

Substitua o RazorTemplateEngine adicionado pelo MVC pela classe CustomTemplateEngine :

public void ConfigureServices(IServiceCollection services)


{
services.AddMvc();
services.AddSingleton<RazorTemplateEngine, CustomTemplateEngine>();
}

Defina um ponto de interrupção na instrução return csharpDocument de CustomTemplateEngine . Quando a


execução do programa parar no ponto de interrupção, exiba o valor de generatedCode .
Pesquisas de exibição e diferenciação de maiúsculas e minúsculas
O mecanismo de exibição do Razor executa pesquisas que diferenciam maiúsculas de minúsculas para as
exibições. No entanto, a pesquisa real é determinada pelo sistema de arquivos subjacente:
Origem baseada em arquivo:
Em sistemas operacionais com sistemas de arquivos que não diferenciam maiúsculas e minúsculas
(por exemplo, Windows), pesquisas no provedor de arquivos físico não diferenciam maiúsculas de
minúsculas. Por exemplo, return View("Test") resulta em correspondências para
/Views/Home/Test.cshtml, /Views/home/test.cshtml e qualquer outra variação de maiúsculas e
minúsculas.
Em sistemas de arquivos que diferenciam maiúsculas de minúsculas (por exemplo, Linux, OSX e com
EmbeddedFileProvider ), as pesquisas diferenciam maiúsculas de minúsculas. Por exemplo,
return View("Test") corresponde especificamente a /Views/Home/Test.cshtml.
Exibições pré-compiladas: com o ASP.NET Core 2.0 e posteriores, pesquisar em exibições pré-compiladas
não diferencia maiúsculas de minúsculas em nenhum sistema operacional. O comportamento é idêntico ao
comportamento do provedor de arquivos físico no Windows. Se duas exibições pré-compiladas diferirem
apenas quanto ao padrão de maiúsculas e minúsculas, o resultado da pesquisa não será determinístico.
Os desenvolvedores são incentivados a fazer a correspondência entre as maiúsculas e minúsculas dos nomes
dos arquivos e de diretórios com o uso de maiúsculas e minúsculas em:

* <span data-ttu-id="94c0f-297">Nomes de área, controlador e ação.</span><span class="sxs-lookup"><span


data-stu-id="94c0f-297">Area, controller, and action names.</span></span>
* <span data-ttu-id="94c0f-298">Páginas do Razor.</span><span class="sxs-lookup"><span data-stu-id="94c0f-
298">Razor Pages.</span></span>

Fazer essa correspondência garante que as implantações encontrem suas exibições, independentemente do
sistema de arquivos subjacente.
Compilação e pré-compilação da exibição do Razor
no ASP.NET Core
07/05/2018 • 3 min to read • Edit Online

Por Rick Anderson


As exibições do Razor são compiladas em tempo de execução quando a exibição é invocada. O ASP.NET Core
1.1.0 e superior pode opcionalmente compilar exibições do Razor e implantá-las com o aplicativo – um processo
conhecido como pré-compilação. Os modelos de projeto do ASP.NET Core 2.x habilitam a pré-compilação por
padrão.

IMPORTANTE
A pré-compilação da exibição do Razor não está disponível no momento durante a execução de uma SCD (implantação
autossuficiente) no ASP.NET Core 2.0. O recurso estará disponível para SCDs na versão 2.1. Para obter mais informações,
consulte A compilação de exibição falha durante a compilação cruzada para Linux no Windows.

Considerações sobre a pré-compilação:


As exibições de pré-compilação resultam em um menor pacote publicado e um tempo de inicialização mais
rápido.
Não é possível editar arquivos do Razor depois que de pré-compilar exibições. As exibições editadas não
estarão presentes no pacote publicado.
Para implantar exibições pré-compiladas:
ASP.NET Core 2.x
ASP.NET Core 1.x
Se o projeto for direcionado ao .NET Framework, inclua uma referência de pacote a
Microsoft.AspNetCore.Mvc.Razor.ViewCompilation:

<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.ViewCompilation" Version="2.0.0"


PrivateAssets="All" />

Se o projeto for direcionado ao .NET Core, nenhuma alteração será necessária.


Os modelos de projeto do ASP.NET Core 2.x definem MvcRazorCompileOnPublish de forma implícita como true
por padrão, o que significa que este nó pode ser removido com segurança do arquivo .csproj. Se você preferir que
isso seja explícito, não há nenhum problema em configurar a propriedade MvcRazorCompileOnPublish como true .
A seguinte amostra .csproj realça essa configuração:
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<MvcRazorCompileOnPublish>true</MvcRazorCompileOnPublish>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>

</Project>
Layout
08/02/2018 • 10 min to read • Edit Online

Por Steve Smith


Com frequência, as exibições compartilham elementos visuais e programáticos. Neste artigo, você
aprenderá a usar layouts comuns, compartilhar diretivas e executar o código comum antes de
renderizar exibições no aplicativo ASP.NET.

O que é um layout
A maioria dos aplicativos Web tem um layout comum que fornece aos usuários uma experiência
consistente durante sua navegação de uma página a outra. O layout normalmente inclui elementos
comuns de interface do usuário, como o cabeçalho do aplicativo, elementos de menu ou de
navegação e rodapé.

Estruturas HTML comuns, como scripts e folhas de estilo, também são usadas com frequência por
muitas páginas em um aplicativo. Todos esses elementos compartilhados podem ser definidos em um
arquivo de layout, que pode então ser referenciado por qualquer exibição usada no aplicativo. Os
layouts reduzem o código duplicado em exibições, ajudando-os a seguir o princípio DRY (Don't
Repeat Yourself ).
Por convenção, o layout padrão de um aplicativo ASP.NET é chamado _Layout.cshtml . O modelo de
projeto do ASP.NET Core MVC no Visual Studio inclui o arquivo de layout na pasta Views/Shared :
Esse layout define um modelo de nível superior para exibições no aplicativo. Os aplicativos não
exigem um layout e podem definir mais de um layout, com diferentes exibições especificando
diferentes layouts.
Um _Layout.cshtml de exemplo:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - WebApplication1</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.6/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>
<div 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">WebApplication1</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>
@await Html.PartialAsync("_LoginPartial")
</div>
</div>
</div>
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>&copy; 2016 - WebApplication1</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">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/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">
</script>
<script src="~/js/site.min.js" asp-append-version="true"></script>
</environment>

@RenderSection("scripts", required: false)


</body>
</html>

Especificando um layout
As exibições do Razor têm uma propriedade Layout . As exibições individuais especificam um layout
com a configuração dessa propriedade:

@{
Layout = "_Layout";
}

O layout especificado pode usar um caminho completo (exemplo: /Views/Shared/_Layout.cshtml ) ou


um nome parcial (exemplo: _Layout ). Quando um nome parcial for fornecido, o mecanismo de
exibição do Razor pesquisará o arquivo de layout usando seu processo de descoberta padrão. A pasta
associada ao controlador é pesquisada primeiro, seguida da pasta Shared . Esse processo de
descoberta é idêntico àquele usado para descobrir exibições parciais.
Por padrão, todo layout precisa chamar RenderBody . Sempre que a chamada a RenderBody for feita, o
conteúdo da exibição será renderizado.
Seções
Um layout, opcionalmente, pode referenciar uma ou mais seções, chamando RenderSection . As
seções fornecem uma maneira de organizar o local em que determinados elementos da página
devem ser colocados. Cada chamada a RenderSection pode especificar se essa seção é obrigatória ou
opcional. Se uma seção obrigatória não for encontrada, uma exceção será gerada. As exibições
individuais especificam o conteúdo a ser renderizado em uma seção usando a sintaxe Razor
@section . Se uma exibição definir uma seção, ela precisará ser renderizada (ou ocorrerá um erro).

Uma definição @section de exemplo em uma exibição:

@section Scripts {
<script type="text/javascript" src="/scripts/main.js"></script>
}

No código acima, os scripts de validação são adicionados à seção scripts em uma exibição que
inclui um formulário. Outras exibições no mesmo aplicativo podem não exigir scripts adicionais e,
portanto, não precisam definir uma seção de scripts.
As seções definidas em uma exibição estão disponíveis apenas em sua página de layout imediata. Elas
não podem ser referenciadas em parciais, componentes de exibição ou outras partes do sistema de
exibição.
Ignorando seções
Por padrão, o corpo e todas as seções de uma página de conteúdo precisam ser renderizados pela
página de layout. O mecanismo de exibição do Razor impõe isso acompanhando se o corpo e cada
seção foram renderizados.
Para instruir o mecanismo de exibição a ignorar o corpo ou as seções, chame os métodos IgnoreBody
e IgnoreSection .
O corpo e cada seção em uma página do Razor precisam ser renderizados ou ignorados.

Importando diretivas compartilhadas


As exibições podem usar diretivas do Razor para fazer muitas coisas, como importar namespaces ou
executar a injeção de dependência. Diretivas compartilhadas por muitas exibições podem ser
especificadas em um arquivo _ViewImports.cshtml comum. O arquivo _ViewImports dá suporte às
seguintes diretivas:
@addTagHelper

@removeTagHelper

@tagHelperPrefix

@using

@model

@inherits

@inject

O arquivo não dá suporte a outros recursos do Razor, como funções e definições de seção.
Um arquivo _ViewImports.cshtml de exemplo:

@using WebApplication1
@using WebApplication1.Models
@using WebApplication1.Models.AccountViewModels
@using WebApplication1.Models.ManageViewModels
@using Microsoft.AspNetCore.Identity
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

O arquivo _ViewImports.cshtml para um aplicativo ASP.NET Core MVC normalmente é colocado na


pasta Views . Um arquivo _ViewImports.cshtml pode ser colocado em qualquer pasta, caso em que só
será aplicado a exibições nessa pasta e em suas subpastas. Os arquivos _ViewImports são
processados, começando no nível raiz e, em seguida, para cada pasta até o local da exibição em si.
Portanto, as configurações especificadas no nível raiz podem ser substituídas no nível da pasta.
Por exemplo, se um arquivo _ViewImports.cshtml no nível raiz especificar @model e @addTagHelper e
outro arquivo _ViewImports.cshtml na pasta associada ao controlador da exibição especificar um
@model diferente e adicionar outro @addTagHelper , a exibição terá acesso aos dois auxiliares de marca
e usará o último @model .
Se vários arquivos _ViewImports.cshtml forem executados para uma exibição, o comportamento
combinado das diretivas incluídas nos arquivos ViewImports.cshtml será o seguinte:
@addTagHelper , @removeTagHelper : todos são executados, em ordem
@tagHelperPrefix : o mais próximo à exibição substitui todos os outros
@model : o mais próximo à exibição substitui todos os outros
@inherits : o mais próximo à exibição substitui todos os outros
@using : todos são incluídos; duplicatas são ignoradas
: para cada propriedade, o mais próximo à exibição substitui todos os outros com o
@inject
mesmo nome de propriedade

Executando o código antes de cada exibição


Se você tiver o código necessário a ser executado antes de cada exibição, isso deverá ser colocado no
arquivo _ViewStart.cshtml . Por convenção, o arquivo _ViewStart.cshtml está localizado na pasta
Views . As instruções listadas em _ViewStart.cshtml são executadas antes de cada exibição completa
(não layouts nem exibições parciais). Como ViewImports.cshtml, _ViewStart.cshtml é hierárquico. Se
um arquivo _ViewStart.cshtml for definido na pasta de exibição associada ao controlador, ele será
executado depois daquele definido na raiz da pasta Views (se houver).
Um arquivo _ViewStart.cshtml de exemplo:

@{
Layout = "_Layout";
}

O arquivo acima especifica que todas as exibições usarão o layout _Layout.cshtml .

OBSERVAÇÃO
_ViewStart.cshtml nem _ViewImports.cshtml geralmente são colocados na pasta /Views/Shared . As
versões no nível do aplicativo desses arquivos devem ser colocadas diretamente na pasta /Views .
Introdução aos Auxiliares de Marca no
ASP.NET Core
08/02/2018 • 24 min to read • Edit Online

Por Rick Anderson

E que são Auxiliares de Marca?


Os Auxiliares de Marca permitem que o código do lado do servidor participe da criação e
renderização de elementos HTML em arquivos do Razor. Por exemplo, o ImageTagHelper
interno pode acrescentar um número de versão ao nome da imagem. Sempre que a imagem é
alterada, o servidor gera uma nova versão exclusiva para a imagem, de modo que os clientes
tenham a garantia de obter a imagem atual (em vez de uma imagem obsoleta armazenada em
cache). Há muitos Auxiliares de Marca internos para tarefas comuns – como criação de
formulários, links, carregamento de ativos e muito mais – e ainda outros disponíveis em
repositórios GitHub públicos e como NuGet. Os Auxiliares de Marca são criados no C# e são
direcionados a elementos HTML de acordo com o nome do elemento, o nome do atributo ou a
marca pai. Por exemplo, o LabelTagHelper interno pode ser direcionado ao elemento <label>
HTML quando os atributos LabelTagHelper são aplicados. Se você está familiarizado com
Auxiliares HTML, os Auxiliares de Marca reduzem as transições explícitas entre HTML e C# em
exibições do Razor. Em muitos casos, os Auxiliares HTML fornecem uma abordagem
alternativa a um Auxiliar de Marca específico, mas é importante reconhecer que os Auxiliares
de Marca não substituem os Auxiliares HTML e que não há um Auxiliar de Marca para cada
Auxiliar HTML. Comparação entre Auxiliares de Marca e Auxiliares HTML explica as diferenças
mais detalhadamente.

O que os Auxiliares de Marca fornecem


Uma experiência de desenvolvimento amigável a HTML Na maioria dos casos, a
marcação do Razor com Auxiliares de Marca parece um HTML padrão. Designers de front-end
familiarizados com HTML/CSS/JavaScript podem editar o Razor sem aprender a sintaxe Razor
do C#.
Um ambiente avançado do IntelliSense para criação do HTML e da marcação do Razor
Isso é um nítido contraste com Auxiliares HTML, a abordagem anterior para a criação do lado
do servidor de marcação nas exibições do Razor. Comparação entre Auxiliares de Marca e
Auxiliares HTML explica as diferenças mais detalhadamente. Suporte do IntelliSense para
Auxiliares de Marca explica o ambiente do IntelliSense. Até mesmo desenvolvedores
experientes com a sintaxe Razor do C# são mais produtivos usando Auxiliares de Marca do que
escrevendo a marcação do Razor do C#.
Uma maneira de fazer com que você fique mais produtivo e possa produzir um código
mais robusto, confiável e possível de ser mantido usando as informações apenas
disponíveis no servidor Por exemplo, historicamente, o mantra da atualização de imagens era
alterar o nome da imagem quando a imagem era alterada. As imagens devem ser armazenadas
em cache de forma agressiva por motivos de desempenho e, a menos que você altere o nome
de uma imagem, você corre o risco de os clientes obterem uma cópia obsoleta. Historicamente,
depois que uma imagem era editada, o nome precisava ser alterado e cada referência à
imagem no aplicativo Web precisava ser atualizada. Não apenas isso exige muito trabalho, mas
também é propenso a erros (você pode perder uma referência, inserir a cadeia de caracteres
incorreta acidentalmente, etc.) O ImageTagHelper interno pode fazer isso para você
automaticamente. O ImageTagHelper pode acrescentar um número de versão ao nome da
imagem, de modo que sempre que a imagem é alterada, o servidor gera automaticamente uma
nova versão exclusiva para a imagem. Os clientes têm a garantia de obter a imagem atual.
Basicamente, essa economia na robustez e no trabalho é obtida gratuitamente com o
ImageTagHelper .

A maioria dos Auxiliares de Marca internos é direcionada a elementos HTML existentes e


fornece atributos do lado do servidor para o elemento. Por exemplo, o elemento <input>
usado em muitas das exibições na pasta Views/Account contém o atributo asp-for , que extrai
o nome da propriedade do modelo especificado no HTML renderizado. A seguinte marcação
do Razor:

<label asp-for="Email"></label>

Gera o seguinte HTML:

<label for="Email">Email</label>

O atributo asp-for é disponibilizado pela propriedade For no LabelTagHelper . Consulte


Criando Auxiliares de Marca para obter mais informações.

Gerenciando o escopo do Auxiliar de Marca


O escopo dos Auxiliares de Marca é controlado por uma combinação de @addTagHelper ,
@removeTagHelper e o caractere de recusa "!".

@addTagHelper disponibiliza os Auxiliares de Marca


Se você criar um novo aplicativo Web ASP.NET Core chamado AuthoringTagHelpers (sem
nenhuma autenticação), o seguinte arquivo Views/_ViewImports.cshtml será adicionado ao
projeto:

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, AuthoringTagHelpers

A diretiva @addTagHelper disponibiliza os Auxiliares de Marca para a exibição. Nesse caso, o


arquivo de exibição é Views/_ViewImports.cshtml, que por padrão é herdado por todos os
arquivos de exibição na pasta Views e seus subdiretórios, disponibilizando os Auxiliares de
Marca. O código acima usa a sintaxe de curinga ("*") para especificar que todos os Auxiliares de
Marca no assembly especificado (Microsoft.AspNetCore.Mvc.TagHelpers) estarão disponíveis
para todos os arquivos de exibição no diretório Views ou subdiretório. O primeiro parâmetro
após @addTagHelper especifica os Auxiliares de Marca a serem carregados (estamos usando "*"
para todos os Auxiliares de Marca) e o segundo parâmetro
"Microsoft.AspNetCore.Mvc.TagHelpers" especifica o assembly que contém os Auxiliares de
Marca. Microsoft.AspNetCore.Mvc.TagHelpers é o assembly para os Auxiliares de Marca
internos do ASP.NET Core.
Para expor todos os Auxiliares de Marca neste projeto (que cria um assembly chamado
AuthoringTagHelpers), você usará o seguinte:
@using AuthoringTagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, AuthoringTagHelpers

Se o projeto contém um EmailTagHelper com o namespace padrão (


AuthoringTagHelpers.TagHelpers.EmailTagHelper ), forneça o FQN ( nome totalmente qualificado)
do Auxiliar de Marca:

@using AuthoringTagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper AuthoringTagHelpers.TagHelpers.EmailTagHelper, AuthoringTagHelpers

Para adicionar um Auxiliar de Marca a uma exibição usando um FQN, primeiro adicione o FQN
( AuthoringTagHelpers.TagHelpers.EmailTagHelper ) e, em seguida, o nome do assembly
(AuthoringTagHelpers). A maioria dos desenvolvedores prefere usar a sintaxe de curinga "*". A
sintaxe de curinga permite que você insira o caractere curinga "*" como o sufixo de um FQN.
Por exemplo, uma das seguintes diretivas exibirá o EmailTagHelper :

@addTagHelper AuthoringTagHelpers.TagHelpers.E*, AuthoringTagHelpers


@addTagHelper AuthoringTagHelpers.TagHelpers.Email*, AuthoringTagHelpers

Conforme mencionado anteriormente, a adição da diretiva @addTagHelper ao arquivo


Views/_ViewImports.cshtml disponibiliza o Auxiliar de Marca para todos os arquivos de
exibição no diretório Views e subdiretórios. Use a diretiva @addTagHelper nos arquivos de
exibição específicos se você deseja aceitar a exposição do Auxiliar de Marca a apenas essas
exibições.
@removeTagHelper remove os Auxiliares de Marca
O @removeTagHelper tem os mesmos dois parâmetros @addTagHelper e remove um Auxiliar de
Marca adicionado anteriormente. Por exemplo, @removeTagHelper aplicado a uma exibição
específica remove o Auxiliar de Marca especificado da exibição. O uso de @removeTagHelper em
um arquivo Views/Folder/_ViewImports.cshtml remove o Auxiliar de Marca especificado de
todas as exibições em Folder.
Controlando o escopo do Auxiliar de Marca com o arquivo _ViewImports.cshtml
Adicione um _ViewImports.cshtml a qualquer pasta de exibição e o mecanismo de exibição
aplicará as diretivas desse arquivo e do arquivo Views/_ViewImports.cshtml. Se você adicionou
um arquivo Views/Home/_ViewImports.cshtml vazio às exibições Home, não haverá nenhuma
alteração porque o arquivo _ViewImports.cshtml é aditivo. As diretivas @addTagHelper que você
adicionar ao arquivo Views/Home/_ViewImports.cshtml (que não estão no arquivo
Views/_ViewImports.cshtml padrão) exporão os Auxiliares de Marca às exibições somente na
pasta Home.
Recusando elementos individuais
Desabilite um Auxiliar de Marca no nível do elemento com o caractere de recusa do Auxiliar de
Marca ("!"). Por exemplo, a validação Email está desabilitada no <span> com o caractere de
recusa do Auxiliar de Marca:

<!span asp-validation-for="Email" class="text-danger"></!span>

É necessário aplicar o caractere de recusa do Auxiliar de Marca à marca de abertura e


fechamento. (O editor do Visual Studio adiciona automaticamente o caractere de recusa à
marca de fechamento quando um é adicionado à marca de abertura). Depois de adicionar o
caractere de recusa, o elemento e os atributos do Auxiliar de Marca deixam de ser exibidos em
uma fonte diferenciada.
Usando @tagHelperPrefix para tornar explícito o uso do Auxiliar de Marca
A diretiva @tagHelperPrefix permite que você especifique uma cadeia de caracteres de prefixo
de marca para habilitar o suporte do Auxiliar de Marca e tornar explícito o uso do Auxiliar de
Marca. Por exemplo, você pode adicionar a seguinte marcação ao arquivo
Views/_ViewImports.cshtml:

@tagHelperPrefix th:

Na imagem do código abaixo, o prefixo do Auxiliar de Marca é definido como th: ; portanto,
somente esses elementos que usam o prefixo th: dão suporte a Auxiliares de Marca
(elementos habilitados para Auxiliar de Marca têm uma fonte diferenciada). Os elementos
<label> e <input> têm o prefixo do Auxiliar de Marca e são habilitados para Auxiliar de
Marca, ao contrário do elemento <span> .

As mesmas regras de hierarquia que se aplicam a @addTagHelper também se aplicam a


@tagHelperPrefix .

Suporte do IntelliSense para Auxiliares de Marca


Quando você cria um novo aplicativo Web ASP.NET no Visual Studio, ele adiciona o pacote
NuGet "Microsoft.AspNetCore.Razor.Tools". Esse é o pacote que adiciona ferramentas do
Auxiliar de Marca.
Considere a escrita de um elemento <label> HTML. Assim que você insere <l no editor do
Visual Studio, o IntelliSense exibe elementos correspondentes:

Não só você obtém a ajuda do HTML, mas também o ícone (o "@" symbol with "<>" abaixo
dele).
identifica o elemento como direcionado a Auxiliares de Marca. Elementos HTML puros (como o
fieldset ) exibem o ícone "<>".

Uma marca <label> HTML pura exibe a marca HTML (com o tema de cores padrão do Visual
Studio) em uma fonte marrom, os atributos em vermelho e os valores de atributo em azul.

Depois de inserir <label , o IntelliSense lista os atributos HTML/CSS disponíveis e os atributos


direcionados ao Auxiliar de Marca:

O preenchimento de declaração do IntelliSense permite que você pressione a tecla TAB para
preencher a declaração com o valor selecionado:

Assim que um atributo do Auxiliar de Marca é inserido, as fontes da marca e do atributo são
alteradas. Usando o tema de cores padrão "Azul" ou "Claro" do Visual Studio, a fonte é roxo em
negrito. Se estiver usando o tema "Escuro", a fonte será azul-petróleo em negrito. As imagens
deste documento foram obtidas usando o tema padrão.

Insira o atalho CompleteWord do Visual Studio – Ctrl + barra de espaços é o padrão dentro
das aspas duplas ("") e você está agora no C#, exatamente como estaria em uma classe do C#.
O IntelliSense exibe todos os métodos e propriedades no modelo de página. Os métodos e as
propriedades estão disponíveis porque o tipo de propriedade é ModelExpression . Na imagem
abaixo, estou editando a exibição Register e, portanto, o RegisterViewModel está disponível.

O IntelliSense lista as propriedades e os métodos disponíveis para o modelo na página. O


ambiente avançado de IntelliSense ajuda você a selecionar a classe CSS:
Comparação entre Auxiliares de Marca e Auxiliares HTML
Os Auxiliares de Marca são anexados a elementos HTML em exibições do Razor, enquanto os
Auxiliares HTML são invocados como métodos intercalados com HTML nas exibições do Razor.
Considere a seguinte marcação do Razor, que cria um rótulo HTML com a classe CSS "caption":

@Html.Label("FirstName", "First Name:", new {@class="caption"})

O símbolo de arroba ( @ ) informa o Razor de que este é o início do código. Os dois próximos
parâmetros ("FirstName" e "First Name:") são cadeias de caracteres; portanto, o IntelliSense
não pode ajudar. O último argumento:

new {@class="caption"}

É um objeto anônimo usado para representar atributos. Como a classe é uma palavra-chave
reservada no C#, use o símbolo @ para forçar o C# a interpretar "@class=" como um símbolo
(nome da propriedade). Para um designer de front-end (alguém familiarizado com
HTML/CSS/JavaScript e outras tecnologias de cliente, mas não familiarizado com o C# e
Razor), a maior parte da linha é estranha. Toda a linha precisa ser criada sem nenhuma ajuda
do IntelliSense.
Usando o LabelTagHelper , a mesma marcação pode ser escrita como:

Com a versão do Auxiliar de Marca, assim que você insere <l no editor do Visual Studio, o
IntelliSense exibe elementos correspondentes:

O IntelliSense ajuda você a escrever a linha inteira. O LabelTagHelper também usa como
padrão a definição do conteúdo do valor de atributo asp-for ("FirstName") como "First
Name"; ele converte propriedades concatenadas em uma frase composta do nome da
propriedade com um espaço em que ocorre cada nova letra maiúscula. Na seguinte marcação:
gera:

<label class="caption" for="FirstName">First Name</label>

O conteúdo concatenado para maiúsculas e minúsculas não é usado se você adiciona o


conteúdo ao <label> . Por exemplo:

gera:

<label class="caption" for="FirstName">Name First</label>

A imagem de código a seguir mostra a parte do Formulário da exibição do Razor


Views/Account/Register.cshtml gerada com base no modelo herdado do ASP.NET 4.5 MVC
incluído com o Visual Studio 2015.

O editor do Visual Studio exibe o código C# com uma tela de fundo cinza. Por exemplo, o
Auxiliar HTML AntiForgeryToken :

@Html.AntiForgeryToken()

é exibido com uma tela de fundo cinza. A maior parte da marcação na exibição Register é C#.
Compare isso com a abordagem equivalente ao uso de Auxiliares de Marca:
A marcação é muito mias limpa e fácil de ler, editar e manter que a abordagem dos Auxiliares
HTML. O código C# é reduzido ao mínimo que o servidor precisa conhecer. O editor do Visual
Studio exibe a marcação direcionada por um Auxiliar de Marca em uma fonte diferenciada.
Considere o grupo Email:

<div class="form-group">
<label asp-for="Email" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>

Cada um dos atributos "asp-" tem um valor "Email", mas "Email" não é uma cadeia de
caracteres. Nesse contexto, "Email" é a propriedade da expressão do modelo C# para o
RegisterViewModel .

O editor do Visual Studio ajuda você a escrever toda a marcação na abordagem do Auxiliar de
Marca de formulário de registro, enquanto o Visual Studio não fornece nenhuma ajuda para a
maioria do código na abordagem de Auxiliares HTML. Suporte do IntelliSense para Auxiliares
de Marca apresenta detalhes sobre como trabalhar com Auxiliares de Marca no editor do
Visual Studio.

Comparação entre Auxiliares de Marca e Controles de


Servidor Web
Os Auxiliares de Marca não têm o elemento ao qual estão associados; simplesmente
participam da renderização do elemento e do conteúdo. Os controles de Servidor Web
do ASP.NET são declarados e invocados em uma página.
Os controles de Servidor Web têm um ciclo de vida não trivial que pode dificultar o
desenvolvimento e a depuração.
Os controles de Servidor Web permitem que você adicione a funcionalidade aos
elementos DOM (Modelo de Objeto do Documento) do cliente usando um controle de
cliente. Os Auxiliares de Marca não tem nenhum DOM.
Os controles de Servidor Web incluem a detecção automática do navegador. Os
Auxiliares de Marca não têm nenhum conhecimento sobre o navegador.
Vários Auxiliares de Marca podem atuar no mesmo elemento (consulte Evitando
conflitos do Auxiliar de Marca), embora normalmente não seja possível compor
controles de Servidor Web.
Os Auxiliares de Marca podem modificar a marca e o conteúdo de elementos HTML no
escopo com o qual foram definidos, mas não modificam diretamente todo o resto em
uma página. Os controles de Servidor Web têm um escopo menos específico e podem
executar ações que afetam outras partes da página, permitindo efeitos colaterais não
intencionais.
Os controles de Servidor Web usam conversores de tipo para converter cadeias de
caracteres em objetos. Com os Auxiliares de Marca, você trabalha nativamente no C# e,
portanto, não precisa fazer a conversão de tipo.
Os controles de Servidor Web usam System.ComponentModel para implementar o
comportamento de componentes e controles em tempo de execução e em tempo de
design. System.ComponentModel inclui as interfaces e as classes base para implementar
atributos e conversores de tipo, associar a fontes de dados e licenciar componentes.
Compare isso com os Auxiliares de Marca, que normalmente são derivados de
TagHelper , e a classe base TagHelper expõe apenas dois métodos, Process e
ProcessAsync .

Personalizando a fonte de elemento do Auxiliar de Marca


Personalize a fonte e a colorização em Ferramentas > Opções > Ambiente > Fontes e
Cores:
Recursos adicionais
Criando auxiliares de marcação
Trabalhando com formulários
TagHelperSamples no GitHub contém amostras de Auxiliar de Marca para trabalhar com o
Bootstrap.
Trabalhando com formulários
Criação de auxiliares de marcação no ASP.NET Core,
um passo a passo com exemplos
17/03/2018 • 28 min to read • Edit Online

Por Rick Anderson


Exibir ou baixar código de exemplo (como baixar)

Introdução aos Auxiliares de Marca


Este tutorial fornece uma introdução à programação de auxiliares de marcação. Introdução aos auxiliares de
marcação descreve os benefícios que os auxiliares de marcação fornecem.
Um auxiliar de marca é qualquer classe que implementa a interface ITagHelper . No entanto, quando cria um
auxiliar de marca, você geralmente deriva de TagHelper , o que fornece acesso ao método Process .
1. Crie um novo projeto do ASP.NET Core chamado AuthoringTagHelpers. Você não precisará de
autenticação para esse projeto.
2. Criar uma pasta para armazenar os auxiliares de marca chamados TagHelpers. A pasta TagHelpers pasta
não é necessária, mas é uma convenção comum. Agora vamos começar a escrever alguns auxiliares de
marca simples.

Um auxiliar de marca mínimo


Nesta seção, você escreve um auxiliar de marca que atualiza uma marca de email. Por exemplo:

<email>Support</email>

O servidor usará nosso auxiliar de marca de email para converter essa marcação como a seguir:

<a href="mailto:Support@contoso.com">Support@contoso.com</a>

Ou seja, uma marca de âncora que torna isso um link de email. Talvez você deseje fazer isso se estiver
escrevendo um mecanismo de blog e precisar que ele envie emails para o marketing, suporte e outros contatos,
todos para o mesmo domínio.
1. Adicione a classe EmailTagHelper a seguir à pasta TagHelpers.
using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Threading.Tasks;

namespace AuthoringTagHelpers.TagHelpers
{
public class EmailTagHelper : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "a"; // Replaces <email> with <a> tag
}
}
}

Observações:
Auxiliares de marcação usam uma convenção de nomenclatura que tem como alvo os elementos
do nome da classe raiz (menos o TagHelper parte do nome de classe). Neste exemplo, o nome da
raiz EmailTagHelper é email, portanto, a marca <email> será direcionada. Essa convenção de
nomenclatura deve funcionar para a maioria dos auxiliares de marcação, posteriormente, mostrarei
como substituí-la.
O EmailTagHelper classe deriva de TagHelper . O TagHelper classe fornece métodos e
propriedades para gravar tag helpers.
O método Process substituído controla o que o auxiliar de marca faz quando é executado. A
classe TagHelper também fornece uma versão assíncrona ( ProcessAsync ) com os mesmos
parâmetros.
O parâmetro de contexto para Process (e ProcessAsync ) contém informações associadas à
execução da marca HTML atual.
O parâmetro de saída para Process (e ProcessAsync ) contém um elemento HTML com estado
que representa a fonte original usada para gerar uma marca HTML e o conteúdo.
Nosso nome de classe tem um sufixo TagHelper, que não é necessário, mas que é considerado
uma convenção de melhor prática. Você pode declarar a classe como:

public class Email : TagHelper

2. Para disponibilizar a classe EmailTagHelper para todas as nossas exibições do Razor, adicione a diretiva
addTagHelper ao arquivo Views/_ViewImports.cshtml: [!code-htmlMain]

O código acima usa a sintaxe de curinga para especificar que todos os auxiliares de marca em nosso
assembly estarão disponíveis. A primeira cadeia de caracteres após @addTagHelper especifica o auxiliar de
marca a ser carregado (use "*" para todos os auxiliares de marca) e a segunda cadeia de caracteres
"AuthoringTagHelpers" especifica o assembly no qual o auxiliar de marca se encontra. Além disso,
observe que a segunda linha insere os auxiliares de marca do ASP.NET Core MVC usando a sintaxe de
curinga (esses auxiliares são abordados em Introdução ao auxiliares de marcação). É a diretiva
@addTagHelper que disponibiliza o auxiliar de marca para a exibição do Razor. Como alternativa, você
pode fornecer o FQN (nome totalmente qualificado) de um auxiliar de marca, conforme mostrado abaixo:

@using AuthoringTagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper AuthoringTagHelpers.TagHelpers.EmailTagHelper, AuthoringTagHelpers
Para adicionar um tag helper para uma exibição usando um FQN, primeiro adicione o FQN (
AuthoringTagHelpers.TagHelpers.EmailTagHelper ) e, em seguida, o nome do assembly ( AuthoringTagHelpers). A
maioria dos desenvolvedores vão preferir usar a sintaxe de curinga. Introdução ao tag helpers apresenta
detalhes sobre a sintaxe de adição, remoção, hierarquia e curinga do tag helper.
3. Atualize a marcação no arquivo Views/Home/Contact.cshtml com essas alterações:

@{
ViewData["Title"] = "Contact";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<address>
One Microsoft Way<br />
Redmond, WA 98052<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>

<address>
<strong>Support:</strong><email>Support</email><br />
<strong>Marketing:</strong><email>Marketing</email>
</address>

4. Execute o aplicativo e use seu navegador favorito para exibir o código-fonte HTML para verificar se as
marcas de email são substituídas pela marcação de âncora (por exemplo, <a>Support</a> ). Suporte e
Marketing são renderizados como links, mas não têm um atributo href para torná-los funcionais.
Corrigiremos isso na próxima seção.
Observação: assim como atributos e marcas HTML, as marcações, os nomes de classes e os atributos no Razor e
no C# não diferenciam maiúsculas de minúsculas.

SetAttribute e SetContent
Nesta seção, atualizaremos o EmailTagHelper para que ele crie uma marca de âncora válida para email. Vamos
atualizá-lo para obter informações de uma exibição do Razor (na forma de um atributo mail-to ) e usar isso na
geração da âncora.
Atualize a classe EmailTagHelper com o seguinte:

public class EmailTagHelper : TagHelper


{
private const string EmailDomain = "contoso.com";

// Can be passed via <email mail-to="..." />.


// Pascal case gets translated into lower-kebab-case.
public string MailTo { get; set; }

public override void Process(TagHelperContext context, TagHelperOutput output)


{
output.TagName = "a"; // Replaces <email> with <a> tag

var address = MailTo + "@" + EmailDomain;


output.Attributes.SetAttribute("href", "mailto:" + address);
output.Content.SetContent(address);
}
}

Observações:
Nomes de classe e de propriedade na formatação Pascal Case para auxiliares de marca são convertidos
em seu kebab case em minúsculas. Portanto, para usar o atributo MailTo , você usará o equivalente de
<email mail-to="value"/> .

A última linha define o conteúdo concluído para nosso tag helper minimamente funcional.
A linha realçada mostra a sintaxe para adicionar atributos:

public override void Process(TagHelperContext context, TagHelperOutput output)


{
output.TagName = "a"; // Replaces <email> with <a> tag

var address = MailTo + "@" + EmailDomain;


output.Attributes.SetAttribute("href", "mailto:" + address);
output.Content.SetContent(address);
}

Essa abordagem funciona para o atributo "href" como no momento, ele não existe na coleção de atributos. Você
também pode usar o output.Attributes.Add para adicionar um atributo do tag helper ao final da coleção de
atributos de marca.
1. Atualize a marcação no arquivo Views/Home/Contact.cshtml com essas alterações: [!code-htmlMain]
2. Execute o aplicativo e verifique se ele gera os links corretos.

OBSERVAÇÃO
Se você pretende escrever o autofechamento da marca de email ( <email mail-to="Rick" /> ), a saída final
também é o autofechamento. Para permitir a capacidade de gravar a marca com uma marca de início (
<email mail-to="Rick"> ), é necessário decorar a classe com o seguinte:

[HtmlTargetElement("email", TagStructure = TagStructure.WithoutEndTag)]


public class EmailVoidTagHelper : TagHelper
{
private const string EmailDomain = "contoso.com";
// Code removed for brevity

Com um auxiliar de marca da marca de email com autofechamento, a saída será


<a href="mailto:Rick@contoso.com" /> . As marcas de âncora com autofechamento são um HTML inválido.
Portanto, não é recomendável criá-las, mas talvez criar um auxiliar de marca com autofechamento.
Auxiliares de marca definem o tipo da propriedade TagMode após a leitura de uma marca.
ProcessAsync
Nesta seção, escreveremos um auxiliar de email assíncrono.
1. Substitua a classe EmailTagHelper pelo seguinte código:
public class EmailTagHelper : TagHelper
{
private const string EmailDomain = "contoso.com";
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "a"; // Replaces <email> with <a> tag
var content = await output.GetChildContentAsync();
var target = content.GetContent() + "@" + EmailDomain;
output.Attributes.SetAttribute("href", "mailto:" + target);
output.Content.SetContent(target);
}
}

Observações:
Essa versão usa o método ProcessAsync assíncrono. O GetChildContentAsync assíncrono retorna
uma Task que contém o TagHelperContent .
Use o parâmetro output para obter o conteúdo do elemento HTML.
2. Faça a alteração a seguir no arquivo Views/Home/Contact.cshtml para que o auxiliar de marca possa
obter o email de destino.

@{
ViewData["Title"] = "Contact";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<address>
One Microsoft Way<br />
Redmond, WA 98052<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>

<address>
<strong>Support:</strong><email>Support</email><br />
<strong>Marketing:</strong><email>Marketing</email>
</address>

3. Execute o aplicativo e verifique se ele gera links de email válidos.


RemoveAll, PreContent.SetHtmlContent e PostContent.SetHtmlContent
1. Adicione a classe BoldTagHelper a seguir à pasta TagHelpers.

using Microsoft.AspNetCore.Razor.TagHelpers;

namespace AuthoringTagHelpers.TagHelpers
{
[HtmlTargetElement(Attributes = "bold")]
public class BoldTagHelper : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.Attributes.RemoveAll("bold");
output.PreContent.SetHtmlContent("<strong>");
output.PostContent.SetHtmlContent("</strong>");
}
}
}
Observações:
O atributo [HtmlTargetElement] passa um parâmetro de atributo que especifica que qualquer
elemento HTML que contenha um atributo HTML nomeado "bold" terá uma correspondência e
que o método de substituição Process na classe será executado. Em nossa amostra, o método
Process remove o atributo "bold" e envolve a marcação contida com <strong></strong> .

Como você não deseja substituir a conteúdo de marca existente, você precisa escrever a marca
<strong> de abertura com o método PreContent.SetHtmlContent e a marca </strong> de
fechamento com o método PostContent.SetHtmlContent .
2. Modifique a exibição About.cshtml para que ela contenha um valor de atributo bold . O código completo
é mostrado abaixo.

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

<p bold>Use this area to provide additional information.</p>

<bold> Is this bold?</bold>

3. Execute o aplicativo. Use seu navegador favorito para inspecionar a origem e verificar a marcação.
O [HtmlTargetElement] atributo acima se destina somente a marcação HTML que fornece um nome de
atributo de "bold". O <bold> elemento não foi modificado pelo tag helper.
4. Comente a linha de atributo [HtmlTargetElement] e ela usará como padrão as marcações <bold> de
direcionamento, ou seja, a marcação HTML do formato <bold> . Lembre-se de que a convenção de
nomenclatura padrão fará a correspondência do nome da classe BoldTagHelper com as marcações
<bold> .

5. Execute o aplicativo e verifique se a marca <bold> é processada pelo auxiliar de marca.

A decoração de uma classe com vários atributos [HtmlTargetElement] resulta em um OR lógico dos destinos.
Por exemplo, o uso do código a seguir, uma marca de negrito ou um atributo de negrito terá uma
correspondência.

[HtmlTargetElement("bold")]
[HtmlTargetElement(Attributes = "bold")]
public class BoldTagHelper : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.Attributes.RemoveAll("bold");
output.PreContent.SetHtmlContent("<strong>");
output.PostContent.SetHtmlContent("</strong>");
}
}

Quando vários atributos são adicionados à mesma instrução, o tempo de execução trata-os como um AND
lógico. Por exemplo, no código abaixo, um elemento HTML precisa ser nomeado "bold" com um atributo
nomeado "bold" ( <bold bold /> ) para que haja a correspondência.

[HtmlTargetElement("bold", Attributes = "bold")]


Também use o [HtmlTargetElement] para alterar o nome do elemento de destino. Por exemplo, se você deseja
que o BoldTagHelper seja direcionado a marcações <MyBold> , use o seguinte atributo:

[HtmlTargetElement("MyBold")]

Passe um model para um tag helper


1. Adicionar uma pasta models.
2. Adicione a seguinte classe WebsiteContext à pasta Models:

using System;

namespace AuthoringTagHelpers.Models
{
public class WebsiteContext
{
public Version Version { get; set; }
public int CopyrightYear { get; set; }
public bool Approved { get; set; }
public int TagsToShow { get; set; }
}
}

3. Adicione a classe WebsiteInformationTagHelper a seguir à pasta TagHelpers.

using System;
using AuthoringTagHelpers.Models;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace AuthoringTagHelpers.TagHelpers
{
public class WebsiteInformationTagHelper : TagHelper
{
public WebsiteContext Info { get; set; }

public override void Process(TagHelperContext context, TagHelperOutput output)


{
output.TagName = "section";
output.Content.SetHtmlContent(
$@"<ul><li><strong>Version:</strong> {Info.Version}</li>
<li><strong>Copyright Year:</strong> {Info.CopyrightYear}</li>
<li><strong>Approved:</strong> {Info.Approved}</li>
<li><strong>Number of tags to show:</strong> {Info.TagsToShow}</li></ul>");
output.TagMode = TagMode.StartTagAndEndTag;
}
}
}

Observações:
Conforme mencionado anteriormente, os auxiliares de marca convertem nomes de classes e
propriedades dos auxiliares de marca do C# na formatação Pascal Case em kebab case em
minúsculas. Portanto, para usar o WebsiteInformationTagHelper no Razor, você escreverá
<website-information /> .

Você não identifica de forma explícita o elemento de destino com o atributo [HtmlTargetElement] .
Portanto, o padrão de website-information será o destino. Se você aplicou o seguinte atributo
(observe que não está em kebab case, mas corresponde ao nome da classe):
[HtmlTargetElement("WebsiteInformation")]

A marca <website-information /> em kebab case em minúsculas não terá uma correspondência. Caso
deseje usar o atributo [HtmlTargetElement] , use o kebab case, conforme mostrado abaixo:

[HtmlTargetElement("Website-Information")]

Os elementos com autofechamento não têm nenhum conteúdo. Para este exemplo, a marcação do
Razor usará uma marca com autofechamento, mas o auxiliar de marca criará um elemento section
(que não tem autofechamento e você escreve o conteúdo dentro do elemento section ). Portanto,
você precisa definir TagMode como StartTagAndEndTag para escrever a saída. Como alternativa,
você pode comentar a linha definindo TagMode e escrever a marcação com uma marca de
fechamento. (A marcação de exemplo é fornecida mais adiante neste tutorial.)
O $ (cifrão) na seguinte linha usa uma cadeia de caracteres interpolada:

$@"<ul><li><strong>Version:</strong> {Info.Version}</li>

4. Adicione a marcação a seguir à exibição About.cshtml. A marcação realçada exibe as informações do site.

@using AuthoringTagHelpers.Models
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<p bold>Use this area to provide additional information.</p>

<bold> Is this bold?</bold>

<h3> web site info </h3>


<website-information info="new WebsiteContext {
Version = new Version(1, 3),
CopyrightYear = 1638,
Approved = true,
TagsToShow = 131 }" />

OBSERVAÇÃO
Na marcação do Razor mostrada abaixo:

<website-information info="new WebsiteContext {


Version = new Version(1, 3),
CopyrightYear = 1638,
Approved = true,
TagsToShow = 131 }" />

O Razor reconhece que o atributo info é uma classe, e não uma cadeia de caracteres, bem como que você
deseja escrever o código C#. Qualquer atributo do auxiliar de marca que não seja uma cadeia de caracteres deve
ser escrito sem o caractere @ .

5. Execute o aplicativo e navegue para a exibição About sobre para ver as informações do site.
OBSERVAÇÃO
Você pode usar a seguinte marcação com uma marca de fechamento e remova a linha com
TagMode.StartTagAndEndTag no tag helper:

<website-information info="new WebsiteContext {


Version = new Version(1, 3),
CopyrightYear = 1638,
Approved = true,
TagsToShow = 131 }" >
</website-information>

Tag helper condicional


O auxiliar de marca de condição renderiza a saída quando recebe um valor true.
1. Adicione a classe ConditionTagHelper a seguir à pasta TagHelpers.

using Microsoft.AspNetCore.Razor.TagHelpers;

namespace AuthoringTagHelpers.TagHelpers
{
[HtmlTargetElement(Attributes = nameof(Condition))]
public class ConditionTagHelper : TagHelper
{
public bool Condition { get; set; }

public override void Process(TagHelperContext context, TagHelperOutput output)


{
if (!Condition)
{
output.SuppressOutput();
}
}
}
}

2. Substitua o conteúdo do arquivo Views/Home/Index.cshtml pela seguinte marcação:

@using AuthoringTagHelpers.Models
@model WebsiteContext

@{
ViewData["Title"] = "Home Page";
}

<div>
<h3>Information about our website (outdated):</h3>
<website-information info=@Model />
<div condition="@Model.Approved">
<p>
This website has <strong surround="em"> @Model.Approved </strong> been approved yet.
Visit www.contoso.com for more information.
</p>
</div>
</div>

3. Substitua o método Index no controlador Home pelo seguinte código:


public IActionResult Index(bool approved = false)
{
return View(new WebsiteContext
{
Approved = approved,
CopyrightYear = 2015,
Version = new Version(1, 3, 3, 7),
TagsToShow = 20
});
}

4. Execute o aplicativo e navegue para a home page. A marcação no div condicional não será renderizada.
Acrescente a cadeia de caracteres de consulta ?approved=true à URL (por exemplo,
http://localhost:1235/Home/Index?approved=true ). approved é definido como verdadeiro e a marcação
condicional será exibida.

OBSERVAÇÃO
Use o nameof operador para especificar o atributo de destino em vez de especificar uma cadeia de caracteres como você
fez com o tag helper em negrito:

[HtmlTargetElement(Attributes = nameof(Condition))]
// [HtmlTargetElement(Attributes = "condition")]
public class ConditionTagHelper : TagHelper
{
public bool Condition { get; set; }

public override void Process(TagHelperContext context, TagHelperOutput output)


{
if (!Condition)
{
output.SuppressOutput();
}
}
}

O operador nameof protegerá o código, caso ele seja refatorado (recomendamos alterar o nome para RedCondition ).

Evitar conflitos de tag helper


Nesta seção, você escreve um par de tag helpers de vinculação automática. O primeiro substituirá a marcação
que contém uma URL iniciada por HTTP para um HTML âncora marca que contém a mesma URL (e, portanto,
resultando em um link de URL ). O segundo fará o mesmo para uma URL começando com WWW.
Como esses dois auxiliares estão intimamente relacionados e você poderá refatorá-los no futuro, vamos mantê-
los no mesmo arquivo.
1. Adicione a classe AutoLinkerHttpTagHelper a seguir à pasta TagHelpers.
[HtmlTargetElement("p")]
public class AutoLinkerHttpTagHelper : TagHelper
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var childContent = await output.GetChildContentAsync();
// Find Urls in the content and replace them with their anchor tag equivalent.
output.Content.SetHtmlContent(Regex.Replace(
childContent.GetContent(),
@"\b(?:https?://)(\S+)\b",
"<a target=\"_blank\" href=\"$0\">$0</a>")); // http link version}
}
}

OBSERVAÇÃO
A classe AutoLinkerHttpTagHelper é direcionada a elementos p e usa o Regex para criar a âncora.

2. Adicione a seguinte marcação ao final do arquivo Views/Home/Contact.cshtml:

@{
ViewData["Title"] = "Contact";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<address>
One Microsoft Way<br />
Redmond, WA 98052<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>

<address>
<strong>Support:</strong><email>Support</email><br />
<strong>Marketing:</strong><email>Marketing</email>
</address>

<p>Visit us at http://docs.asp.net or at www.microsoft.com</p>

3. Execute o aplicativo e verifique se o tag helper renderiza a âncora corretamente.


4. Atualize a classe AutoLinker para incluir o AutoLinkerWwwTagHelper que converterá o texto www em uma
marca de âncora que também contém o texto www original. O código atualizado é realçado abaixo:
[HtmlTargetElement("p")]
public class AutoLinkerHttpTagHelper : TagHelper
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var childContent = await output.GetChildContentAsync();
// Find Urls in the content and replace them with their anchor tag equivalent.
output.Content.SetHtmlContent(Regex.Replace(
childContent.GetContent(),
@"\b(?:https?://)(\S+)\b",
"<a target=\"_blank\" href=\"$0\">$0</a>")); // http link version}
}
}

[HtmlTargetElement("p")]
public class AutoLinkerWwwTagHelper : TagHelper
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var childContent = await output.GetChildContentAsync();
// Find Urls in the content and replace them with their anchor tag equivalent.
output.Content.SetHtmlContent(Regex.Replace(
childContent.GetContent(),
@"\b(www\.)(\S+)\b",
"<a target=\"_blank\" href=\"http://$0\">$0</a>")); // www version
}
}
}

5. Execute o aplicativo. Observe que o texto www é renderizado como um link, ao contrário do texto HTTP.
Se você colocar um ponto de interrupção em ambas as classes, poderá ver que a classe do auxiliar de
marca HTTP é executada primeiro. O problema é que a saída do auxiliar de marca é armazenada em
cache e quando o auxiliar de marca WWW é executado, ele substitui a saída armazenada em cache do
auxiliar de marca HTTP. Mais adiante no tutorial, veremos como controlar a ordem na qual os auxiliares
de marca são executados. Corrigiremos o código com o seguinte:
public class AutoLinkerHttpTagHelper : TagHelper
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var childContent = output.Content.IsModified ? output.Content.GetContent() :
(await output.GetChildContentAsync()).GetContent();

// Find Urls in the content and replace them with their anchor tag equivalent.
output.Content.SetHtmlContent(Regex.Replace(
childContent,
@"\b(?:https?://)(\S+)\b",
"<a target=\"_blank\" href=\"$0\">$0</a>")); // http link version}
}
}

[HtmlTargetElement("p")]
public class AutoLinkerWwwTagHelper : TagHelper
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var childContent = output.Content.IsModified ? output.Content.GetContent() :
(await output.GetChildContentAsync()).GetContent();

// Find Urls in the content and replace them with their anchor tag equivalent.
output.Content.SetHtmlContent(Regex.Replace(
childContent,
@"\b(www\.)(\S+)\b",
"<a target=\"_blank\" href=\"http://$0\">$0</a>")); // www version
}
}

OBSERVAÇÃO
Na primeira edição os tag helpers de vinculação automática, você obteve o conteúdo do destino com o código a
seguir:

var childContent = await output.GetChildContentAsync();

Ou seja, você chama GetChildContentAsync usando a TagHelperOutput passada para o método


ProcessAsync . Conforme mencionado anteriormente, como a saída é armazenada em cache, o último auxiliar de
marca a ser executado vence. Você corrigiu o problema com o seguinte código:

var childContent = output.Content.IsModified ? output.Content.GetContent() :


(await output.GetChildContentAsync()).GetContent();

O código acima verifica se o conteúdo foi modificado e, em caso afirmativo, ele obtém o conteúdo do buffer de
saída.

6. Execute o aplicativo e verifique se os dois links funcionam conforme esperado. Embora possa parecer que
nosso auxiliar de marca de vinculador automático está correto e completo, ele tem um problema sutil. Se
o auxiliar de marca WWW for executado primeiro, os links www não estarão corretos. Atualize o código
adicionando a sobrecarga Order para controlar a ordem em que a marca é executada. A propriedade
Order determina a ordem de execução em relação aos outros auxiliares de marca direcionados ao
mesmo elemento. O valor de ordem padrão é zero e as instâncias com valores mais baixos são
executadas primeiro.
public class AutoLinkerHttpTagHelper : TagHelper
{
// This filter must run before the AutoLinkerWwwTagHelper as it searches and replaces http and
// the AutoLinkerWwwTagHelper adds http to the markup.
public override int Order
{
get { return int.MinValue; }
}

O código acima assegurará que o tag helper HTTP seja executado antes do tag helper WWW. Altere
Order para MaxValue e verifique se a marcação gerada para a marcação WWW está incorreta.

Inspecionar e recuperar o conteúdo filho


Os tag helpers fornecem várias propriedades para recuperar o conteúdo.
O resultado de GetChildContentAsync pode ser acrescentado ao output.Content .
Inspecione o resultado de GetChildContentAsync com GetContent .
Se você modificar output.Content , o corpo da TagHelper não será executado ou renderizado, a menos que
você chame GetChildContentAsync como em nossa amostra de vinculador automático:

public class AutoLinkerHttpTagHelper : TagHelper


{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var childContent = output.Content.IsModified ? output.Content.GetContent() :
(await output.GetChildContentAsync()).GetContent();

// Find Urls in the content and replace them with their anchor tag equivalent.
output.Content.SetHtmlContent(Regex.Replace(
childContent,
@"\b(?:https?://)(\S+)\b",
"<a target=\"_blank\" href=\"$0\">$0</a>")); // http link version}
}
}

Várias chamadas a GetChildContentAsync retornam o mesmo valor e não executam o corpo TagHelper
novamente, a menos que você passe um parâmetro falso indicando para não usar o resultado armazenado
em cache.
Introdução uso de auxiliares de marca
em formulários no ASP.NET Core
08/02/2018 • 28 min to read • Edit Online

Por Rick Anderson, Dave Paquette e Jerrie Pelser


Este documento demonstra como é o trabalho com Formulários e os elementos HTML
usados comumente em um Formulário. O elemento HTML Formulário fornece o
mecanismo primário que os aplicativos Web usam para postar dados para o servidor. A
maior parte deste documento descreve os Auxiliares de marca e como eles podem ajudar
você a criar formulários HTML robustos de forma produtiva. É recomendável que você
leia Introdução ao auxiliares de marca antes de ler este documento.
Em muitos casos, os Auxiliares HTML fornecem uma abordagem alternativa a um
Auxiliar de Marca específico, mas é importante reconhecer que os Auxiliares de Marca
não substituem os Auxiliares HTML e que não há um Auxiliar de Marca para cada Auxiliar
HTML. Quando existe um Auxiliar HTML alternativo, ele é mencionado.

O Auxiliar de marca de formulário


O Auxiliar de marca de formulário:
Gera o valor do atributo HTML <FORM> action para uma ação do controlador
MVC ou uma rota nomeada
Gera um Token de verificação de solicitação oculto para evitar a falsificação de
solicitações entre sites (quando usado com o atributo [ValidateAntiForgeryToken]
no método de ação HTTP Post)
Fornece o atributo asp-route-<Parameter Name> , em que <Parameter Name> é
adicionado aos valores de rota. Os parâmetros routeValues para Html.BeginForm
e Html.BeginRouteForm fornecem funcionalidade semelhante.
Tem uma alternativa de Auxiliar HTML Html.BeginForm e Html.BeginRouteForm

Amostra:

<form asp-controller="Demo" asp-action="Register" method="post">


<!-- Input and Submit elements -->
</form>

O Auxiliar de marca de formulário acima gera o HTML a seguir:

<form method="post" action="/Demo/Register">


<!-- Input and Submit elements -->
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>" />
</form>

O tempo de execução do MVC gera o valor do atributo action dos atributos


asp-controller e asp-action do Auxiliar de marca de formulário. O Auxiliar de marca de
formulário também gera um Token de verificação de solicitação oculto para evitar a
falsificação de solicitações entre sites (quando usado com o atributo
[ValidateAntiForgeryToken] no método de ação HTTP Post). É difícil proteger um
Formulário HTML puro contra falsificação de solicitações entre sites e o Auxiliar de marca
de formulário fornece este serviço para você.
Usando uma rota nomeada
O atributo do Auxiliar de Marca asp-route também pode gerar a marcação para o
atributo HTML action . Um aplicativo com uma rota chamada register poderia usar a
seguinte marcação para a página de registro:

<form asp-route="register" method="post">


<!-- Input and Submit elements -->
</form>

Muitas das exibições na pasta Modos de Exibição/Conta (gerada quando você cria um
novo aplicativo Web com Contas de usuário individuais) contêm o atributo asp-route-
returnurl:

<form asp-controller="Account" asp-action="Login"


asp-route-returnurl="@ViewData["ReturnUrl"]"
method="post" class="form-horizontal" role="form">

OBSERVAÇÃO
Com os modelos internos, returnUrl só é preenchido automaticamente quando você tenta
acessar um recurso autorizado, mas não está autenticado ou autorizado. Quando você tenta
fazer um acesso não autorizado, o middleware de segurança o redireciona para a página de
logon com o returnUrl definido.

O auxiliar de marca de entrada


O Auxiliar de marca de entrada associa um elemento HTML <input> a uma expressão de
modelo em sua exibição do Razor.
Sintaxe:

<input asp-for="<Expression Name>" />

O auxiliar de marca de entrada:


Gera os atributos HTML id e name para o nome da expressão especificada no
atributo asp-for . asp-for="Property1.Property2" equivale a
m => m.Property1.Property2 . O nome da expressão é o que é usado para o valor do
atributo asp-for . Consulte a seção Nomes de expressão para obter informações
adicionais.
Define o valor do atributo HTML type com base nos atributos de tipo de modelo
e anotação de dados aplicados à propriedade de modelo
O valor do atributo HTML type não será substituído quando um for especificado
Gera atributos de validação HTML5 de atributos de anotação de dados aplicados a
propriedades de modelo
Tem uma sobreposição de recursos de Auxiliar HTML com Html.TextBoxFor e
Html.EditorFor . Consulte a seção Alternativas de Auxiliar HTML ao Auxiliar
de marca de entrada para obter detalhes.
Fornece tipagem forte. Se o nome da propriedade for alterado e você não atualizar
o Auxiliar de marca, você verá um erro semelhante ao seguinte:

An error occurred during the compilation of a resource required to process


this request. Please review the following specific error details and modify
your source code appropriately.

Type expected
'RegisterViewModel' does not contain a definition for 'Email' and no
extension method 'Email' accepting a first argument of type 'RegisterViewModel'
could be found (are you missing a using directive or an assembly reference?)

O Auxiliar de marca Input define o atributo HTML type com base no tipo .NET. A
tabela a seguir lista alguns tipos .NET comuns e o tipo HTML gerado (não estão listados
todos os tipos .NET).

TIPO .NET TIPO DE ENTRADA

Bool type=”checkbox”

Cadeia de Caracteres type=”text”

DateTime type=”datetime”

Byte type=”number”

int type=”number”

Single e Double type=”number”

A tabela a seguir mostra alguns atributos de anotações de dados comuns que o auxiliar
de marca de entrada mapeará para tipos de entrada específicos (não são listados todos os
atributos de validação):

ATRIBUTO TIPO DE ENTRADA

[EmailAddress] type=”email”

[Url] type=”url”

[HiddenInput] type=”hidden”

[Phone] type=”tel”

[DataType(DataType.Password)] type=”password”

[DataType(DataType.Date)] type=”date”
ATRIBUTO TIPO DE ENTRADA

[DataType(DataType.Time)] type=”time”

Amostra:

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }

[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterInput" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
<button type="submit">Register</button>
</form>

O código acima gera o seguinte HTML:

<form method="post" action="/Demo/RegisterInput">


Email:
<input type="email" data-val="true"
data-val-email="The Email Address field is not a valid email address."
data-val-required="The Email Address field is required."
id="Email" name="Email" value="" /> <br>
Password:
<input type="password" data-val="true"
data-val-required="The Password field is required."
id="Password" name="Password" /><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>" />
</form>

As anotações de dados aplicadas às propriedades Email e Password geram metadados


no modelo. O Auxiliar de marca de entrada consome os metadados do modelo e produz
atributos HTML5 data-val-* (consulte Validação de modelo). Esses atributos descrevem
os validadores a serem anexados aos campos de entrada. Isso fornece validação de
jQuery e HTML5 discreto. Os atributos discretos têm o formato
data-val-rule="Error Message" , em que a regra é o nome da regra de validação (como
data-val-required , data-val-email , data-val-maxlength etc.) Se uma mensagem de erro
for fornecida no atributo, ela será exibida como o valor para do atributo data-val-rule .
Também há atributos do formulário data-val-ruleName-argumentName="argumentValue" que
fornecem detalhes adicionais sobre a regra, por exemplo, data-val-maxlength-max="1024" .
Alternativas de Auxiliar HTML ao Auxiliar de marca de entrada
Html.TextBox , Html.TextBoxFor , Html.Editor e Html.EditorFor têm recursos que se
sobrepõem aos di Auxiliar de marca de entrada. O Auxiliar de marca de entrada define
automaticamente o atributo type ; Html.TextBox e Html.TextBoxFor não o fazem.
Html.Editor e Html.EditorFor manipulam coleções, objetos complexos e modelos; o
Auxiliar de marca de entrada não o faz. O Auxiliar de marca de entrada, Html.EditorFor e
Html.TextBoxFor são fortemente tipados (eles usam expressões lambda); Html.TextBox e
Html.Editor não usam (eles usam nomes de expressão).

HtmlAttributes
@Html.Editor() e usam uma entrada ViewDataDictionary especial
@Html.EditorFor()
chamada htmlAttributes ao executar seus modelos padrão. Esse comportamento pode
ser aumentado usando parâmetros additionalViewData . A chave "htmlAttributes"
diferencia maiúsculas de minúsculas. A chave "htmlAttributes" é tratada de forma
semelhante ao objeto htmlAttributes passado para auxiliares de entrada como
@Html.TextBox() .

@Html.EditorFor(model => model.YourProperty,


new { htmlAttributes = new { @class="myCssClass", style="Width:100px" } })

Nomes de expressão
O valor do atributo asp-for é um ModelExpression e o lado direito de uma expressão
lambda. Portanto, asp-for="Property1" se torna m => m.Property1 no código gerado e é
por isso você não precisa colocar o prefixo Model . Você pode usar o caractere "@" para
iniciar uma expressão embutida e mover para antes de `m.`:</span><span class="sxs-
lookup">You can use the "@" character to start an inline expression and move before the
m. :

@{
var joe = "Joe";
}
<input asp-for="@joe" />

Gera o seguinte:

<input type="text" id="joe" name="joe" value="Joe" />

Com propriedades de coleção, asp-for="CollectionProperty[23].Member" gera o mesmo


nome que asp-for="CollectionProperty[i].Member" quando i tem o valor 23 .
Navegando para propriedades filho
Você também pode navegar para propriedades filho usando o caminho da propriedade
do modelo de exibição. Considere uma classe de modelo mais complexa que contém uma
propriedade Address filho.

public class AddressViewModel


{
public string AddressLine1 { get; set; }
}
public class RegisterAddressViewModel
{
public string Email { get; set; }

[DataType(DataType.Password)]
public string Password { get; set; }

public AddressViewModel Address { get; set; }


}

Na exibição, associamos a Address.AddressLine1 :

@model RegisterAddressViewModel

<form asp-controller="Demo" asp-action="RegisterAddress" method="post">


Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
Address: <input asp-for="Address.AddressLine1" /><br />
<button type="submit">Register</button>
</form>

O HTML a seguir é gerado para Address.AddressLine1 :

<input type="text" id="Address_AddressLine1" name="Address.AddressLine1" value="" />

Nomes de expressão e coleções


Exemplo, um modelo que contém uma matriz de Colors :

public class Person


{
public List<string> Colors { get; set; }

public int Age { get; set; }


}

O método de ação:

public IActionResult Edit(int id, int colorIndex)


{
ViewData["Index"] = colorIndex;
return View(GetPerson(id));
}

O Razor a seguir mostra como você acessa um elemento Color específico:

@model Person
@{
var index = (int)ViewData["index"];
}

<form asp-controller="ToDo" asp-action="Edit" method="post">


@Html.EditorFor(m => m.Colors[index])
<label asp-for="Age"></label>
<input asp-for="Age" /><br />
<button type="submit">Post</button>
</form>
O modelo Views/Shared/EditorTemplates/String.cshtml:

@model string

<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />

Exemplo usando List<T> :

public class ToDoItem


{
public string Name { get; set; }

public bool IsDone { get; set; }


}

O Razor a seguir mostra como iterar em uma coleção:

@model List<ToDoItem>

<form asp-controller="ToDo" asp-action="Edit" method="post">


<table>
<tr> <th>Name</th> <th>Is Done</th> </tr>

@for (int i = 0; i < Model.Count; i++)


{
<tr>
@Html.EditorFor(model => model[i])
</tr>
}

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

O modelo Views/Shared/EditorTemplates/ToDoItem.cshtml:

@model ToDoItem

<td>
<label asp-for="@Model.Name"></label>
@Html.DisplayFor(model => model.Name)
</td>
<td>
<input asp-for="@Model.IsDone" />
</td>

@*
This template replaces the following Razor which evaluates the indexer three
times.
<td>
<label asp-for="@Model[i].Name"></label>
@Html.DisplayFor(model => model[i].Name)
</td>
<td>
<input asp-for="@Model[i].IsDone" />
</td>
*@
OBSERVAÇÃO
Sempre use for (e não foreach ) para iterar em uma lista. Avaliar um indexador em uma
expressão LINQ pode ser caro e deve feito o mínimo possível.

OBSERVAÇÃO
O código de exemplo comentado acima mostra como você substituiria a expressão lambda pelo
operador @ para acessar cada ToDoItem na lista.

Auxiliar de marca de área de texto


O auxiliar de marca Textarea Tag Helper é semelhante ao Auxiliar de marca de entrada.
Gera os atributos id e name , bem como os atributos de validação de dados do
modelo para um elemento <textarea>.
Fornece tipagem forte.
Alternativa de Auxiliar HTML: Html.TextAreaFor

Amostra:

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}

@model DescriptionViewModel

<form asp-controller="Demo" asp-action="RegisterTextArea" method="post">


<textarea asp-for="Description"></textarea>
<button type="submit">Test</button>
</form>

O HTML a seguir é gerado:


<form method="post" action="/Demo/RegisterTextArea">
<textarea data-val="true"
data-val-maxlength="The field Description must be a string or array type with a
maximum length of &#x27;1024&#x27;."
data-val-maxlength-max="1024"
data-val-minlength="The field Description must be a string or array type with a
minimum length of &#x27;5&#x27;."
data-val-minlength-min="5"
id="Description" name="Description">
</textarea>
<button type="submit">Test</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>"
/>
</form>

O auxiliar de marca de rótulo


Gera a legenda do rótulo e o atributo for em um elemento para um nome de
expressão
Alternativa de Auxiliar HTML: Html.LabelFor .

O Label Tag Helper fornece os seguintes benefícios em comparação com um elemento


de rótulo HTML puro:
Você obtém automaticamente o valor do rótulo descritivo do atributo Display . O
nome de exibição desejado pode mudar com o tempo e a combinação do atributo
Display e do Auxiliar de Marca de Rótulo aplicará Display em qualquer lugar em
que for usado.
Menos marcação no código-fonte
Tipagem forte com a propriedade de modelo.
Amostra:

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}

@model SimpleViewModel

<form asp-controller="Demo" asp-action="RegisterLabel" method="post">


<label asp-for="Email"></label>
<input asp-for="Email" /> <br />
</form>

O HTML a seguir é gerado para o elemento <label> :


<label for="Email">Email Address</label>

O Auxiliar de marca de rótulo gerou o valor do atributo for de "Email", que é a ID


associada ao elemento <input> . Auxiliares de marca geram elementos id e for
consistentes para que eles possam ser associados corretamente. A legenda neste exemplo
é proveniente do atributo Display . Se o modelo não contivesse um atributo Display , a
legenda seria o nome da propriedade da expressão.

Os auxiliares de marca de validação


Há dois auxiliares de marca de validação. O Validation Message Tag Helper (que exibe
uma mensagem de validação para uma única propriedade em seu modelo) e o
Validation Summary Tag Helper (que exibe um resumo dos erros de validação). O
Input Tag Helper adiciona atributos de validação do lado do cliente HTML5 para
elementos de entrada baseados em atributos de anotação de dados em suas classes de
modelo. A validação também é executada no servidor. O Auxiliar de marca de validação
exibe essas mensagens de erro quando ocorre um erro de validação.
O Auxiliar de marca de mensagem de validação
Adiciona o atributo data-valmsg-for="property" HTML5 ao elemento span, que
anexa as mensagens de erro de validação no campo de entrada da propriedade do
modelo especificado. Quando ocorre um erro de validação do lado do cliente,
jQuery exibe a mensagem de erro no elemento <span> .
A validação também é feita no servidor. Os clientes poderão ter o JavaScript
desabilitado e parte da validação só pode ser feita no lado do servidor.
Alternativa de Auxiliar HTML: Html.ValidationMessageFor

O Validation Message Tag Helper é usado com o atributo asp-validation-for em um


elemento HTML span.

<span asp-validation-for="Email"></span>

O Auxiliar de marca de mensagem de validação gerará o HTML a seguir:

<span class="field-validation-valid"
data-valmsg-for="Email"
data-valmsg-replace="true"></span>

Geralmente, você usa o Validation Message Tag Helper após um Auxiliar de marca Input
para a mesma propriedade. Fazer isso exibe as mensagens de erro de validação próximo à
entrada que causou o erro.

OBSERVAÇÃO
É necessário ter uma exibição com as referências de script jQuery e JavaScript corretas em vigor
para a validação do lado do cliente. Consulte Validação de Modelo para obter mais informações.

Quando ocorre um erro de validação do lado do servidor (por exemplo, quando você tem
validação do lado do servidor personalizada ou a validação do lado do cliente está
desabilitada), o MVC coloca essa mensagem de erro como o corpo do elemento <span> .
<span class="field-validation-error" data-valmsg-for="Email"
data-valmsg-replace="true">
The Email Address field is required.
</span>

Auxiliar de marca de resumo de validação


Tem como alvo elementos <div> com o atributo asp-validation-summary

Alternativa de Auxiliar HTML: @Html.ValidationSummary

O Validation Summary Tag Helper é usado para exibir um resumo das mensagens de
validação. O valor do atributo asp-validation-summary pode ser qualquer um dos
seguintes:

ASP-VALIDATION-SUMMARY MENSAGENS DE VALIDAÇÃO EXIBIDAS

ValidationSummary.All Nível da propriedade e do modelo

ValidationSummary.ModelOnly Modelo

ValidationSummary.None Nenhum

Amostra
No exemplo a seguir, o modelo de dados é decorado com atributos DataAnnotation , o
que gera mensagens de erro de validação no elemento <input> . Quando ocorre um erro
de validação, o Auxiliar de marca de validação exibe a mensagem de erro:

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }

[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}

@model RegisterViewModel

<form asp-controller="Demo" asp-action="RegisterValidation" method="post">


<div asp-validation-summary="ModelOnly"></div>
Email: <input asp-for="Email" /> <br />
<span asp-validation-for="Email"></span><br />
Password: <input asp-for="Password" /><br />
<span asp-validation-for="Password"></span><br />
<button type="submit">Register</button>
</form>

O código HTML gerado (quando o modelo é válido):


<form action="/DemoReg/Register" method="post">
<div class="validation-summary-valid" data-valmsg-summary="true">
<ul><li style="display:none"></li></ul></div>
Email: <input name="Email" id="Email" type="email" value=""
data-val-required="The Email field is required."
data-val-email="The Email field is not a valid email address."
data-val="true"> <br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Email"></span><br>
Password: <input name="Password" id="Password" type="password"
data-val-required="The Password field is required." data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Password"></span><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>"
/>
</form>

Auxiliar de Marca de Seleção


Gera select e os elementos option associados para as propriedades do modelo.
Tem uma alternativa de Auxiliar HTML Html.DropDownListFor e Html.ListBoxFor

O Select Tag Helper asp-for especifica o nome da propriedade do modelo para o


elemento select e asp-items especifica os elementos option. Por exemplo:

<select asp-for="Country" asp-items="Model.Countries"></select>

Amostra:

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }

public List<SelectListItem> Countries { get; } = new List<SelectListItem>


{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
};
}
}

O método Index inicializa o CountryViewModel , define o país selecionado e o transmite


para a exibição Index .

public IActionResult IndexOption(int id)


{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}
O método Index HTTP POST exibe a seleção:

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(CountryViewModel model)
{
if (ModelState.IsValid)
{
var msg = model.Country + " selected";
return RedirectToAction("IndexSuccess", new { message = msg});
}

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


return View(model);
}

A exibição Index :

@model CountryViewModel

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


<select asp-for="Country" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Que gera o seguinte HTML (com "CA" selecionado):

<form method="post" action="/">


<select id="Country" name="Country">
<option value="MX">Mexico</option>
<option selected="selected" value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>" />
</form>

OBSERVAÇÃO
Não é recomendável usar ViewBag ou ViewData com o Auxiliar de Marca de Seleção. Um
modelo de exibição é mais robusto para fornecer metadados MVC e, geralmente, menos
problemático.

O valor do atributo asp-for é um caso especial e não requer um prefixo Model , os


outros atributos do Auxiliar de marca requerem (como asp-items )

<select asp-for="Country" asp-items="Model.Countries"></select>

Associação de enumeração
Geralmente, é conveniente usar <select> com uma propriedade enum e gerar os
elementos SelectListItem dos valores enum .
Amostra:
public class CountryEnumViewModel
{
public CountryEnum EnumCountry { get; set; }
}
}

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O método GetEnumSelectList gera um objeto SelectList para uma enumeração.

@model CountryEnumViewModel

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


<select asp-for="EnumCountry"
asp-items="Html.GetEnumSelectList<CountryEnum>()"> >
</select>
<br /><button type="submit">Register</button>
</form>

É possível decorar sua lista de enumeradores com o atributo Display para obter uma
interface do usuário mais rica:

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}

O HTML a seguir é gerado:


<form method="post" action="/Home/IndexEnum">
<select data-val="true" data-val-required="The EnumCountry field is
required."
id="EnumCountry" name="EnumCountry">
<option value="0">United Mexican States</option>
<option value="1">United States of America</option>
<option value="2">Canada</option>
<option value="3">France</option>
<option value="4">Germany</option>
<option selected="selected" value="5">Spain</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>" />
</form>

Grupo de opções
O elemento HTML <optgroup> é gerado quando o modelo de exibição contém um ou
mais objetos SelectListGroup .
O CountryViewModelGroup agrupa os elementos SelectListItem nos grupos "América do
Norte" e "Europa":
public class CountryViewModelGroup
{
public CountryViewModelGroup()
{
var NorthAmericaGroup = new SelectListGroup { Name = "North America" };
var EuropeGroup = new SelectListGroup { Name = "Europe" };

Countries = new List<SelectListItem>


{
new SelectListItem
{
Value = "MEX",
Text = "Mexico",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "CAN",
Text = "Canada",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "US",
Text = "USA",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "FR",
Text = "France",
Group = EuropeGroup
},
new SelectListItem
{
Value = "ES",
Text = "Spain",
Group = EuropeGroup
},
new SelectListItem
{
Value = "DE",
Text = "Germany",
Group = EuropeGroup
}
};
}

public string Country { get; set; }

public List<SelectListItem> Countries { get; }

Os dois grupos são mostrados abaixo:


O HTML gerado:

<form method="post" action="/Home/IndexGroup">


<select id="Country" name="Country">
<optgroup label="North America">
<option value="MEX">Mexico</option>
<option value="CAN">Canada</option>
<option value="US">USA</option>
</optgroup>
<optgroup label="Europe">
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</optgroup>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>" />
</form>

Seleção múltipla
O Auxiliar de Marca de Seleção gerará automaticamente o atributo multiple = "multiple"
se a propriedade especificada no atributo asp-for for um IEnumerable . Por exemplo,
considerando o seguinte modelo:

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
public class CountryViewModelIEnumerable
{
public IEnumerable<string> CountryCodes { get; set; }

public List<SelectListItem> Countries { get; } = new List<SelectListItem>


{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
new SelectListItem { Value = "FR", Text = "France" },
new SelectListItem { Value = "ES", Text = "Spain" },
new SelectListItem { Value = "DE", Text = "Germany"}
};
}
}
Com a seguinte exibição:

@model CountryViewModelIEnumerable

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


<select asp-for="CountryCodes" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>

Gera o seguinte HTML:

<form method="post" action="/Home/IndexMultiSelect">


<select id="CountryCodes"
multiple="multiple"
name="CountryCodes"><option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>"
/>
</form>

Nenhuma seleção
Se acabar usando a opção "não especificado" em várias páginas, você poderá criar um
modelo para eliminar o HTML de repetição:

@model CountryViewModel

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


@Html.EditorForModel()
<br /><button type="submit">Register</button>
</form>

O modelo Views/Shared/EditorTemplates/CountryViewModel.cshtml:

@model CountryViewModel

<select asp-for="Country" asp-items="Model.Countries">


<option value="">--none--</option>
</select>

O acréscimo de elementos HTML <option> não está limitado ao caso de Nenhuma


seleção. Por exemplo, o seguinte método de ação e exibição gerarão HTML semelhante
ao código acima:

public IActionResult IndexOption(int id)


{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}
@model CountryViewModel

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


<select asp-for="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
</form>

O elemento <option> correto será selecionado (contém o atributo selected="selected" )


dependendo do valor atual de Country .

<form method="post" action="/Home/IndexEmpty">


<select id="Country" name="Country">
<option value="">&lt;none&gt;</option>
<option value="MX">Mexico</option>
<option value="CA" selected="selected">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for
brevity>" />
</form>

Recursos adicionais
Auxiliares de marcação
Elemento de formulário HTML
Token de verificação de solicitação
Associação de modelos
Validação de modelo
Interface IAttributeAdapter
Trechos de código para este documento
Auxiliares de marcação internos do ASP.NET Core
19/03/2018 • 1 min to read • Edit Online

Por Peter Kellner


O ASP.NET Core inclui diversos auxiliares de marcação internos para aumentar sua produtividade. Esta seção
fornece uma visão geral dos auxiliares de marcação internos.

OBSERVAÇÃO
Há auxiliares de marcação internos que não são abordados, pois eles são usados internamente pelo mecanismo de exibição
do Razor. Isso inclui um auxiliar de marcação para o caractere ~, que se expande para o caminho raiz do site.

Auxiliares de marcação internos do ASP.NET Core


Auxiliar de marcação de âncora
Auxiliar de marcação de cache
Auxiliar de marcação de cache distribuído
Auxiliar de marcação de ambiente
Auxiliar de marcação de formulário
Auxiliar de marcação de imagem
Auxiliar de marcação de entrada
Auxiliar de marcação de rótulo
Auxiliar de marcação parcial
Selecionar o auxiliar de marcação
Auxiliar de marcação de área de texto
Auxiliar de marcação de mensagem de validação
Resumo de validação de auxiliar de marcação

Recursos adicionais
Desenvolvimento do lado do cliente
Auxiliares de marcação
Auxiliar de Marca de Âncora no ASP.NET Core
08/05/2018 • 11 min to read • Edit Online

De Peter Kellner e Scott Addie


Exibir ou baixar código de exemplo (como baixar)
O Auxiliar de Marca de Âncora aprimora a marca de âncora HTML padrão ( <a ... ></a> ) adicionando
novos atributos. Por convenção, os nomes de atributos são prefixados com asp- . O valor do atributo href
do elemento de âncora renderizado é determinado pelos valores dos atributos asp- .
SpeakerController é usado em exemplos ao longo de todo este documento:

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

public class SpeakerController : Controller


{
private List<Speaker> Speakers =
new List<Speaker>
{
new Speaker {SpeakerId = 10},
new Speaker {SpeakerId = 11},
new Speaker {SpeakerId = 12}
};

[Route("Speaker/{id:int}")]
public IActionResult Detail(int id) =>
View(Speakers.FirstOrDefault(a => a.SpeakerId == id));

[Route("/Speaker/Evaluations",
Name = "speakerevals")]
public IActionResult Evaluations() => View();

[Route("/Speaker/EvaluationsCurrent",
Name = "speakerevalscurrent")]
public IActionResult Evaluations(
int speakerId,
bool currentYear) => View();

public IActionResult Index() => View(Speakers);


}

public class Speaker


{
public int SpeakerId { get; set; }
}

Segue um inventário dos atributos asp- .

asp-controller
O atributo asp-controller designa o controlador usado para gerar a URL. A marcação a seguir lista todos os
palestrantes:
<a asp-controller="Speaker"
asp-action="Index">All Speakers</a>

O HTML gerado:

<a href="/Speaker">All Speakers</a>

Se o atributo asp-controller for especificado e asp-action não for, o valor asp-action padrão é a ação do
controlador associada ao modo de exibição em execução no momento. Se asp-action for omitido da
marcação anterior e o Auxiliar de Marca de Âncora for usado no modo de exibição Índice (/home) do
HomeController, o HTML gerado será:

<a href="/Home">All Speakers</a>

asp-action
O valor do atributo asp-action representa o nome da ação do controlador incluído no atributo href gerado.
A seguinte marcação define o valor do atributo href gerado para a página de avaliações do palestrante:

<a asp-controller="Speaker"
asp-action="Evaluations">Speaker Evaluations</a>

O HTML gerado:

<a href="/Speaker/Evaluations">Speaker Evaluations</a>

Se nenhum atributo asp-controller for especificado, o controlador padrão que está chamando a exibição
que executa a exibição atual é usado.
Se o valor do atributo asp-action for Index , nenhuma ação será anexada à URL, causando a invocação da
ação Index padrão. A ação especificada (ou padrão), deve existir no controlador referenciado em
asp-controller .

asp-route-{value}
O atributo asp-route-{value} permite um prefixo de roteamento de caractere curinga. Qualquer valor que
esteja ocupando o espaço reservado {value} é interpretado como um possível parâmetro de roteamento. Se
uma rota padrão não for encontrada, esse prefixo de rota será anexado ao atributo href gerado como um
valor e parâmetro de solicitação. Caso contrário, ele será substituído no modelo de rota.
Considere a seguinte ação do controlador:

public IActionResult AnchorTagHelper(int id)


{
var speaker = new Speaker
{
SpeakerId = id
};

return View(speaker);
}
Com um modelo de rota padrão definido em Startup.Configure:

app.UseMvc(routes =>
{
// need route and attribute on controller: [Area("Blogs")]
routes.MapRoute(name: "areaRoute",
template: "{area:exists}/{controller=Home}/{action=Index}");

// default route for non-areas


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

O modo de exibição MVC usa o modelo fornecido pela ação, da seguinte forma:

@model Speaker
<!DOCTYPE html>
<html>
<body>
<a asp-controller="Speaker"
asp-action="Detail"
asp-route-id="@Model.SpeakerId">SpeakerId: @Model.SpeakerId</a>
</body>
</html>

O espaço reservado {id?} da rota padrão foi correspondido. O HTML gerado:

<a href="/Speaker/Detail/12">SpeakerId: 12</a>

Suponha que o prefixo de roteamento não faça parte do modelo de roteamento de correspondência, como
com o seguinte modo de exibição de MVC:

@model Speaker
<!DOCTYPE html>
<html>
<body>
<a asp-controller="Speaker"
asp-action="Detail"
asp-route-speakerid="@Model.SpeakerId">SpeakerId: @Model.SpeakerId</a>
<body>
</html>

O seguinte HTML é gerado porque o speakerid não foi encontrado na rota correspondente:

<a href="/Speaker/Detail?speakerid=12">SpeakerId: 12</a>

Se asp-controller ou asp-action não forem especificados, o mesmo processamento padrão será seguido,
como no atributo asp-route .

asp-route
O atributo asp-route é usado para criar um link de URL diretamente para uma rota nomeada. Usando
atributos de roteamento, uma rota pode ser nomeada como mostrado em SpeakerController e usada em sua
ação Evaluations :
[Route("/Speaker/Evaluations",
Name = "speakerevals")]
public IActionResult Evaluations() => View();

Na seguinte marcação, o atributo asp-route faz referência à rota nomeada:

<a asp-route="speakerevals">Speaker Evaluations</a>

O Auxiliar de Marca de Âncora gera uma rota diretamente para essa ação de controlador usando a URL
/Speaker/Evaluations. O HTML gerado:

<a href="/Speaker/Evaluations">Speaker Evaluations</a>

Se asp-controller ou asp-action for especificado além de asp-route , a rota gerada poderá não ser a
esperada. Para evitar um conflito de rota, asp-route não deve ser usado com os atributos asp-controller ou
asp-action .

asp-all-route-data
O atributo asp-all-route-data oferece suporte à criação de um dicionário ou par chave-valor. A chave é o
nome do parâmetro e o valor é o valor do parâmetro.
No exemplo a seguir, um dicionário é inicializado e passado para um modo de exibição Razor. Como
alternativa, os dados podem ser passado com seu modelo.

@{
var parms = new Dictionary<string, string>
{
{ "speakerId", "11" },
{ "currentYear", "true" }
};
}

<a asp-route="speakerevalscurrent"
asp-all-route-data="parms">Speaker Evaluations</a>

O código anterior gera o seguinte HTML:

<a href="/Speaker/EvaluationsCurrent?speakerId=11&currentYear=true">Speaker Evaluations</a>

O dicionário asp-all-route-data é simplificado para produzir um querystring que atenda aos requisitos da
ação Evaluations sobrecarregada:

[Route("/Speaker/EvaluationsCurrent",
Name = "speakerevalscurrent")]
public IActionResult Evaluations(
int speakerId,
bool currentYear) => View();

Se todas as chaves no dicionário corresponderem aos parâmetros, esses valores serão substituídos na rota
conforme apropriado. Os outros valores não correspondentes são gerados como parâmetros de solicitação.

asp-fragment
O atributo asp-fragment define um fragmento de URL para anexar à URL. O Auxiliar de Marca de Âncora
adiciona o caractere de hash (#). Considere a seguinte marcação:

<a asp-controller="Speaker"
asp-action="Evaluations"
asp-fragment="SpeakerEvaluations">Speaker Evaluations</a>

O HTML gerado:

<a href="/Speaker/Evaluations#SpeakerEvaluations">Speaker Evaluations</a>

As marcas de hash são úteis ao criar aplicativos do lado do cliente. Elas podem ser usadas para marcar e
pesquisar com facilidade em JavaScript, por exemplo.

asp-area
O atributo asp-area define o nome de área usado para definir a rota apropriada. O exemplo a seguir ilustra
como o atributo de área causa um remapeamento das rotas. Definir asp-area como "Blogs" faz com que o
diretório Áreas/Blogs seja prefixado nas rotas dos controladores e exibições associados a essa marca de
âncora.
<Nome do projeto>
wwwroot
Áreas
Blogs
Controladores
HomeController.cs
Exibições
Início
AboutBlog.cshtml
Index.cshtml
_ViewStart.cshtml
Controladores
Dada a hierarquia do diretório anterior, a marcação para referenciar o arquivo AboutBlog.cshtml é:

<a asp-area="Blogs"
asp-controller="Home"
asp-action="AboutBlog">About Blog</a>

O HTML gerado:

<a href="/Blogs/Home/AboutBlog">About Blog</a>

DICA
Para que as áreas funcionem em um aplicativo MVC, o modelo de rota deve incluir uma referência à área, se ela existir.
Esse modelo é representado pelo segundo parâmetro da chamada do método routes.MapRoute em
Startup.Configure:[!code-csharp]
asp-protocol
O atributo asp-protocol é destinado a especificar um protocolo (como https ) em sua URL. Por exemplo:

<a asp-protocol="https"
asp-controller="Home"
asp-action="About">About</a>

O HTML gerado:

<a href="https://localhost/Home/About">About</a>

O nome do host no exemplo é localhost, mas o Auxiliar de Marca de Âncora usará o domínio público do site
ao gerar a URL.

asp-host
O atributo asp-host é para especificar um nome do host na sua URL. Por exemplo:

<a asp-protocol="https"
asp-host="microsoft.com"
asp-controller="Home"
asp-action="About">About</a>

O HTML gerado:

<a href="https://microsoft.com/Home/About">About</a>

asp-page
O atributo asp-page é usado com as Páginas Razor. Use-o para definir um valor de atributo href da marca
de âncora para uma página específica. Prefixar o nome de página com uma barra ("/") cria a URL.
O exemplo a seguir aponta para o participante da Página Razor:

<a asp-page="/Attendee">All Attendees</a>

O HTML gerado:

<a href="/Attendee">All Attendees</a>

O atributo asp-page é mutuamente exclusivo com os atributos asp-route , asp-controller e asp-action .


No entanto, o asp-page pode ser usado com o asp-route-{value} para controlar o roteamento, como mostra
a marcação a seguir:

<a asp-page="/Attendee"
asp-route-attendeeid="10">View Attendee</a>

O HTML gerado:
<a href="/Attendee?attendeeid=10">View Attendee</a>

asp-page-handler
O atributo asp-page-handler é usado com as Páginas Razor. Ele se destina à vinculação a manipuladores de
página específicos.
Considere o seguinte manipulador de página:

public void OnGetProfile(int attendeeId)


{
ViewData["AttendeeId"] = attendeeId;

// code omitted for brevity


}

A marcação associada do modelo de página vincula ao manipulador de página OnGetProfile . Observe que o
prefixo On<Verb> do nome do método do manipulador de página é omitido no valor do atributo
asp-page-handler . Se esse fosse um método assíncrono, o sufixo Async também seria omitido.

<a asp-page="/Attendee"
asp-page-handler="Profile"
asp-route-attendeeid="12">Attendee Profile</a>

O HTML gerado:

<a href="/Attendee?attendeeid=12&handler=Profile">Attendee Profile</a>

Recursos adicionais
Áreas
Introdução às Páginas Razor
Auxiliar de Marca de Cache no ASP.NET Core MVC
08/05/2018 • 8 min to read • Edit Online

Por Peter Kellner


O Auxiliar de Marca de Cache possibilita melhorar consideravelmente o desempenho de seu aplicativo ASP.NET
Core armazenando seu conteúdo em cache no provedor de cache interno do ASP.NET Core.
O Mecanismo de Exibição do Razor define o expires-after padrão como vinte minutos.
A seguinte marcação Razor armazena em cache a data/hora:

<cache>@DateTime.Now</cache>

A primeira solicitação para a página que contém CacheTagHelper exibirá a data/hora atual. Solicitações adicionais
mostrarão o valor armazenado em cache até que o cache expire (padrão de 20 minutos) ou seja removido por
demanda de memória.
Você pode definir a duração do cache com os seguintes atributos:

Atributos do Auxiliar de Marca de Cache


habilitado
TIPO DE ATRIBUTO VALORES VÁLIDOS

boolean "true" (padrão)

"false"

Determina se o conteúdo circundado pelo Auxiliar de Marca de Cache é armazenado em cache. O padrão é true
. Se for definido como false , o Auxiliar de Marca de Cache não terá efeito de armazenamento em cache na saída
renderizada.
Exemplo:

<cache enabled="true">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

expires-on
TIPO DE ATRIBUTO VALOR DE EXEMPLO

DateTimeOffset "@new DateTime(2025,1,29,17,02,0)"

Define uma data de expiração absoluta. O exemplo a seguir armazenará em cache o conteúdo do Auxiliar de
Marca de Cache até 17:02 de 29 de janeiro de 2025.
Exemplo:
<cache expires-on="@new DateTime(2025,1,29,17,02,0)">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

expires-after
TIPO DE ATRIBUTO VALOR DE EXEMPLO

TimeSpan "@TimeSpan.FromSeconds(120)"

Define o tempo decorrido desde a primeira solicitação para armazenar o conteúdo em cache.
Exemplo:

<cache expires-after="@TimeSpan.FromSeconds(120)">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

expires-sliding
TIPO DE ATRIBUTO VALOR DE EXEMPLO

TimeSpan "@TimeSpan.FromSeconds(60)"

Define a hora em que uma entrada de cache deve ser removida se não tiver sido acessada.
Exemplo:

<cache expires-sliding="@TimeSpan.FromSeconds(60)">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

vary-by-header
TIPO DE ATRIBUTO VALORES DE EXEMPLO

Cadeia de Caracteres "User-Agent"

"User-Agent,content-encoding"

Aceita um único valor de cabeçalho ou uma lista separada por vírgulas de valores de cabeçalho que disparam
uma atualização do cache quando são alterados. O exemplo a seguir monitora o valor do cabeçalho User-Agent .
O exemplo armazenará em cache o conteúdo para todos os User-Agent diferentes apresentado ao servidor Web.
Exemplo:

<cache vary-by-header="User-Agent">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

vary-by-query
TIPO DE ATRIBUTO VALORES DE EXEMPLO

Cadeia de Caracteres "Make"

"Make,Model"

Aceita um único valor de cabeçalho ou uma lista separada por vírgulas de valores de cabeçalho que disparam
uma atualização do cache quando o valor do cabeçalho é alterado. O exemplo a seguir examina os valores de
Make e Model .

Exemplo:

<cache vary-by-query="Make,Model">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

vary-by-route
TIPO DE ATRIBUTO VALORES DE EXEMPLO

Cadeia de Caracteres "Make"

"Make,Model"

Aceita um único valor de cabeçalho ou uma lista separada por vírgulas de valores de cabeçalho que disparam
uma atualização do cache quando os valores do parâmetro de dados de rota do cabeçalho são alterados.
Exemplo:
Startup.cs

routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{Make?}/{Model?}");

Index.cshtml

<cache vary-by-route="Make,Model">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

vary-by-cookie
TIPO DE ATRIBUTO VALORES DE EXEMPLO

Cadeia de Caracteres ".AspNetCore.Identity.Application"

".AspNetCore.Identity.Application,HairColor"

Aceita um único valor de cabeçalho ou uma lista separada por vírgulas de valores de cabeçalho que disparam
uma atualização do cache quando os valores do cabeçalho são alterados. O exemplo a seguir examina o cookie
associado à identidade do ASP.NET. Quando um usuário é autenticado, o cookie de solicitação é definido, o que
dispara uma atualização do cache.
Exemplo:

<cache vary-by-cookie=".AspNetCore.Identity.Application">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

vary-by-user
TIPO DE ATRIBUTO VALORES DE EXEMPLO

Boolean "true"

"false" (padrão)

Especifica se o cache deve ou não ser redefinido quando o usuário conectado (ou a entidade de contexto) é
alterado. O usuário atual também é conhecido como a Entidade do contexto de solicitação e pode ser exibido em
um modo de exibição do Razor referenciando @User.Identity.Name .
O exemplo a seguir examina o usuário conectado no momento.
Exemplo:

<cache vary-by-user="true">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

Usar esse atributo mantém o conteúdo no cache durante um ciclo de logon e logoff. Ao usar vary-by-user="true" ,
uma ação de logon e logoff invalida o cache para o usuário autenticado. O cache é invalidado porque um novo
valor de cookie exclusivo é gerado no logon. O cache é mantido para o estado anônimo quando nenhum cookie
está presente ou expirou. Isso significa que se nenhum usuário estiver conectado, o cache será mantido.

vary-by
TIPO DE ATRIBUTO VALORES DE EXEMPLO

Cadeia de Caracteres "@Model"

Permite a personalização de quais dados são armazenados no cache. Quando o objeto referenciado pelo valor de
cadeia de caracteres do atributo é alterado, o conteúdo do Auxiliar de Marca de Cache é atualizado.
Frequentemente, uma concatenação de cadeia de caracteres de valores do modelo é atribuída a este atributo. Na
verdade, isso significa que uma atualização de qualquer um dos valores concatenados invalida o cache.
O exemplo a seguir supõe que o método do controlador que renderiza a exibição somas o valor inteiro dos dois
parâmetros de rota, myParam1 e myParam2 , e os retorna como a propriedade de modelo única. Quando essa soma
é alterada, o conteúdo do Auxiliar de Marca de Cache é renderizado e armazenado em cache novamente.
Exemplo:
Ação:
public IActionResult Index(string myParam1,string myParam2,string myParam3)
{
int num1;
int num2;
int.TryParse(myParam1, out num1);
int.TryParse(myParam2, out num2);
return View(viewName, num1 + num2);
}

Index.cshtml

<cache vary-by="@Model"">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

priority
TIPO DE ATRIBUTO VALORES DE EXEMPLO

CacheItemPriority "High"

"Low"

"NeverRemove"

"Normal"

Fornece diretrizes de remoção do cache para o provedor de cache interno. O servidor Web removerá entradas de
cache Low primeiro quando estiver sob demanda de memória.
Exemplo:

<cache priority="High">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

O atributo priority não assegura um nível específico de retenção de cache. CacheItemPriority é apenas uma
sugestão. Configurar esse atributo como NeverRemove não assegura que o cache sempre será retido. Consulte
Recursos adicionais para obter mais informações.
O Auxiliar de Marca de Cache é dependente do serviço de cache de memória. O Auxiliar de Marca de Cache
adiciona o serviço se ele não tiver sido adicionado.

Recursos adicionais
Cache na memória
Introdução ao Identity
Auxiliar de Marca de Cache Distribuído no ASP.NET
Core
08/05/2018 • 3 min to read • Edit Online

Por Peter Kellner


O Auxiliar de Marca de Cache Distribuído fornece a capacidade de melhorar consideravelmente o desempenho
do aplicativo ASP.NET Core armazenando seu conteúdo em cache em uma fonte de cache distribuído.
O Auxiliar de Marca de Cache Distribuído herda da mesma classe base do Auxiliar de Marca de Cache. Todos os
atributos associados ao Auxiliar de Marca de Cache também funcionarão no Auxiliar de Marca Distribuído.
O Auxiliar de Marca de Cache Distribuído segue o Princípio de Dependências Explícitas, conhecido como
Injeção de Construtor. Especificamente, o contêiner de interface IDistributedCache é passado para o construtor
do Auxiliar de Marca de Cache Distribuído. Se nenhuma implementação concreta específica de IDistributedCache
tiver sido criada em ConfigureServices , geralmente encontrada em startup.cs, o Auxiliar de Marca de Cache
Distribuído usará o mesmo provedor em memória para armazenar os dados em cache que o Auxiliar de Marca
de Cache básico.

Atributos do auxiliar de marca de cache distribuído


prioridade expires-on expires-after expires-sliding vary-by-header vary-by-query vary-by-route vary-by-cookie
vary-by-user vary-by habilitada
Consulte Auxiliar de Marca de Cache para obter as definições. O Auxiliar de Marca de Cache Distribuído herda da
mesma classe do Auxiliar de Marca de Cache. Portanto, todos esses atributos são comuns ao Auxiliar de Marca
de Cache.

nome (obrigatório )
TIPO DE ATRIBUTO VALOR DE EXEMPLO

cadeia de caracteres "my-distributed-cache-unique-key-101"

O atributo name obrigatório é usado como uma chave para esse cache armazenado de cada instância de um
Auxiliar de Marca de Cache Distribuído. Ao contrário do Auxiliar de Marca de Cache básico que atribui uma chave
a cada instância do Auxiliar de Marca de Cache com base no nome da página do Razor e na localização do
auxiliar de marca na página do Razor, o Auxiliar de Marca de Cache Distribuído somente localiza suas chaves no
atributo name
Exemplo de uso:

<distributed-cache name="my-distributed-cache-unique-key-101">
Time Inside Cache Tag Helper: @DateTime.Now
</distributed-cache>

Implementações de IDistributedCache do Auxiliar de Marca de Cache


Distribuído
Há duas implementações de IDistributedCache internas do ASP.NET Core. Uma é baseada no SQL Server e a
outra, no Redis. Os detalhes dessas implementações podem ser encontrados no recurso referenciado abaixo
intitulado "Trabalhando com um cache distribuído". Ambas as implementações envolvem a definição de uma
instância de IDistributedCache no startup.cs do ASP.NET Core.
Não há nenhum atributo de marca especificamente associado ao uso de uma implementação específica de
IDistributedCache .

Recursos adicionais
Auxiliar de Marca de Cache no ASP.NET Core MVC
Injeção de dependência no ASP.NET Core
Trabalhar com um cache distribuído no núcleo do ASP.NET
Cache de memória no núcleo do ASP.NET
Introdução à identidade do ASP.NET Core
Auxiliar de Marca de Ambiente no ASP.NET Core
08/02/2018 • 2 min to read • Edit Online

Por Peter Kellner e Hisham Bin Ateya


O Auxiliar de Marca de Ambiente renderiza condicionalmente seu conteúdo contido com base no ambiente de
hospedagem atual. Seu único atributo names é uma lista separada por vírgula de nomes de ambiente. Se houver
uma correspondência de um nome ao ambiente atual, ele disparará o conteúdo contido a ser renderizado.

Atributos do Auxiliar de Marca de Ambiente


nomes
Aceita um único nome de ambiente de hospedagem ou uma lista separada por vírgula de nomes de ambiente de
hospedagem que disparam a renderização do conteúdo contido.
Esses valores são comparados com o valor atual retornado da propriedade estática
HostingEnvironment.EnvironmentName do ASP.NET Core. Esse valor é um dos seguintes: Preparo,
Desenvolvimento ou Produção. A comparação ignora o uso de maiúsculas.
Um exemplo de um auxiliar de marca environment válido é:

<environment names="Staging,Production">
<strong>HostingEnvironment.EnvironmentName is Staging or Production</strong>
</environment>

incluir e excluir atributos


O ASP.NET Core 2.x adiciona os atributos include & exclude . Esses atributos controlam a renderização do
conteúdo contido com base nos nomes de ambiente de hospedagem incluídos ou excluídos.
incluir o Core ASP.NET Core 2.0 e posterior
A propriedade include tem um comportamento semelhante do atributo names no ASP.NET Core 1.0.

<environment include="Staging,Production">
<strong>HostingEnvironment.EnvironmentName is Staging or Production</strong>
</environment>

excluir o ASP.NET Core 2.0 e posterior


Por outro lado, a propriedade exclude permite que EnvironmentTagHelper renderize o conteúdo contido para
todos os nomes de ambiente de hospedagem, exceto aqueles especificados.

<environment exclude="Development">
<strong>HostingEnvironment.EnvironmentName is Staging or Production</strong>
</environment>

Recursos adicionais
Trabalhando com vários ambientes no ASP.NET Core
Injeção de dependência no ASP.NET Core
Auxiliar de Marca de Imagem no ASP.NET Core
08/05/2018 • 3 min to read • Edit Online

Por Peter Kellner


O Auxiliar de Marca de Imagem aprimora a marca img ( <img> ). Ele exige uma marca src , bem como o atributo
de boolean asp-append-version .
Se a origem da imagem ( src ) é um arquivo estático no servidor Web host, uma cadeia de caracteres de
extrapolação de cache exclusivo é acrescentada como um parâmetro de consulta à origem da imagem. Isso garante
que, se o arquivo no servidor Web host é alterado, uma URL de solicitação exclusiva que inclui o parâmetro de
solicitação atualizado seja gerada. A cadeia de caracteres de extrapolação de cache é um valor exclusivo que
representa o hash do arquivo de imagem estática.
Se a origem da imagem ( src ) não é um arquivo estático (por exemplo, uma URL remota ou o arquivo não existe
no servidor), o atributo src da marca <img> é gerado sem um parâmetro da cadeia de caracteres de consulta de
extrapolação de cache.

Atributos de Auxiliar de Marca de Imagem


asp-append-version
Quando especificado junto com um atributo src , o Auxiliar de Marca de Imagem é invocado.
Um exemplo de um auxiliar de marca img válido é:

<img src="~/images/asplogo.png"
asp-append-version="true" />

Se o arquivo estático existe no diretório .wwwroot/images/asplogo.png, o HTML gerado é semelhante ao seguinte


(o hash será diferente):

<img
src="/images/asplogo.png?v=Kl_dqr9NVtnMdsM2MUg4qthUnWZm5T1fCEimBPWDNgM"/>

O valor atribuído ao parâmetro v é o valor de hash do arquivo em disco. Se o servidor Web não pode obter o
acesso de leitura ao arquivo estático referenciado, nenhum parâmetro v é adicionado ao atributo src .

src
Para ativar o Auxiliar de Marca de Imagem, o atributo src é obrigatório no elemento <img> .

OBSERVAÇÃO
O Auxiliar de Marca de Imagem usa o provedor Cache no servidor Web local para armazenar o Sha512 calculado de
determinado arquivo. Se o arquivo é solicitado novamente, o Sha512 não precisa ser recalculado. O Cache é invalidado por
um inspetor de arquivo que é anexado ao arquivo quando o Sha512 do arquivo é calculado.

Recursos adicionais
Cache de memória no núcleo do ASP.NET
Exibições parciais
08/02/2018 • 9 min to read • Edit Online

Por Steve Smith, Maher JENDOUBI e Rick Anderson


O ASP.NET Core MVC dá suporte a exibições parciais, que são úteis quando você tem partes reutilizáveis de
páginas da Web que deseja compartilhar entre diferentes exibições.
Exibir ou baixar código de exemplo (como baixar)

O que são Exibições Parciais?


Uma exibição parcial é uma exibição renderizada dentro de outra exibição. A saída HTML gerada pela execução
da exibição parcial é renderizada na exibição de chamada (ou pai). Assim como exibições, as exibições parciais
usam a extensão de arquivo .cshtml.

Quando é necessário usar Exibições Parciais?


As exibições parciais são uma maneira eficiente de dividir exibições grandes em componentes menores. Elas
podem reduzir a duplicação do conteúdo de exibição e permitir que os elementos de exibição sejam
reutilizados. Os elementos de layout comuns devem ser especificados em _Layout.cshtml. O conteúdo
reutilizável que não pertence ao layout pode ser encapsulado em exibições parciais.
Se você tem uma página complexa composta por várias partes lógicas, pode ser útil trabalhar com cada parte
como sua própria exibição parcial. Cada parte da página pode ser exibida em isolamento do restante da página
e a exibição para a página propriamente dita fica muito mais simples, porque contém apenas a estrutura da
página geral e chamadas para renderizar as exibições parciais.
Dica: siga o Princípio Don't Repeat Yourself nas exibições.

Declarando exibições parciais


As exibições parciais são criadas como qualquer outra exibição: crie um arquivo .cshtml dentro da pasta Views .
Não há nenhuma diferença semântica entre uma exibição parcial e uma exibição normal – apenas que são
renderizadas de maneira diferente. Você pode ter uma exibição que é retornada diretamente do ViewResult de
um controlador e a mesma exibição pode ser usada como uma exibição parcial. A principal diferença entre
como uma exibição e uma exibição parcial é renderizada é que as exibições parciais não executam
_ViewStart.cshtml (ao contrário das exibições – saiba mais sobre _ViewStart.cshtml em Layout).

Referenciando uma exibição parcial


Em uma página de exibição, há várias maneiras de renderizar uma exibição parcial. É mais simples usar
Html.Partial , que retorna uma IHtmlString e pode ser referenciada com a colocação de prefixo na chamada
com @ :

O método PartialAsync está disponível para exibições parciais que contêm um código assíncrono (embora,
em geral, não seja recomendado que as exibições contenham um código):
@await Html.PartialAsync("AuthorPartial")

Renderize uma exibição parcial com RenderPartial . Esse método não retorna um resultado; ele transmite a
saída renderizada diretamente para a resposta. Como ele não retorna um resultado, ele precisa ser chamado
dentro de um bloco de código Razor (também chame RenderPartialAsync , se necessário):

@* RenderPartialAsync *@
@{
await Html.RenderPartialAsync("AuthorPartial");

Já que ele transmite o resultado diretamente, RenderPartial e RenderPartialAsync podem ter um melhor
desempenho em alguns cenários. No entanto, na maioria dos casos, é recomendável o uso de Partial e
PartialAsync .

OBSERVAÇÃO
Se as exibições precisam executar o código, o padrão recomendado é usar um componente de exibição, em vez de uma
exibição parcial.

Descoberta de exibição parcial


Ao referenciar uma exibição parcial, você pode se referir ao seu local de várias maneiras:

// Uses a view in current folder with this name


// If none is found, searches the Shared folder
@Html.Partial("ViewName")

// A view with this name must be in the same folder


@Html.Partial("ViewName.cshtml")

// Locate the view based on the application root


// Paths that start with "/" or "~/" refer to the application root
@Html.Partial("~/Views/Folder/ViewName.cshtml")
@Html.Partial("/Views/Folder/ViewName.cshtml")

// Locate the view using relative paths


@Html.Partial("../Account/LoginPartial.cshtml")

Você pode ter diferentes exibições parciais com o mesmo nome em pastas de exibição diferentes. Ao
referenciar as exibições por nome (sem a extensão de arquivo), as exibições em cada pasta usarão a exibição
parcial na mesma pasta com elas. Também especifique uma exibição parcial padrão a ser usada, colocando-a na
pasta Shared. A exibição parcial compartilhada será usada pelas exibições que não têm sua própria versão da
exibição parcial. Você pode ter uma exibição parcial padrão (em Shared), que é substituída por uma exibição
parcial com o mesmo nome na mesma pasta da exibição pai.
As exibições parciais podem ser encadeadas. Ou seja, uma exibição parcial pode chamar outra exibição parcial
(desde que você não crie um loop). Dentro de cada exibição ou exibição parcial, os caminhos relativos são
sempre relativos a essa exibição, não à exibição raiz ou pai.

OBSERVAÇÃO
Se você declarar uma section do Razor em uma exibição parcial, ela não será visível para seus pais; será limitada à
exibição parcial.
Acessando dados em exibições parciais
Quando é criada uma instância de uma exibição parcial, ela recebe uma cópia do dicionário ViewData da
exibição pai. As atualizações feitas nos dados dentro da exibição parcial não são persistidas na exibição pai. Os
ViewData alterados em um parcial exibição são perdidos quando a exibição parcial é retornada.

Passe uma instância de ViewDataDictionary para a exibição parcial:

@Html.Partial("PartialName", customViewData)

Também passe um modelo para uma exibição parcial. Isso pode ser o modelo de exibição da página, ou uma
parte dele, ou um objeto personalizado. Passe um modelo para Partial , PartialAsync , RenderPartial ou
RenderPartialAsync :

@Html.Partial("PartialName", viewModel)

Passe uma instância de ViewDataDictionary e um modelo de exibição para uma exibição parcial:

@await Html.PartialAsync("ArticleSection", section,


new ViewDataDictionary(this.ViewData) { { "index", index } })

A marcação abaixo mostra a exibição Views/Articles/Read.cshtml que contém duas exibições parciais. A
segunda exibição parcial passa um modelo e ViewData para a exibição parcial. Passe um novo dicionário
ViewData mantendo os ViewData existentes se você usar a sobrecarga de construtor do ViewDataDictionary
realçado abaixo:

@using Microsoft.AspNetCore.Mvc.ViewFeatures
@using PartialViewsSample.ViewModels
@model Article

<h2>@Model.Title</h2>
@*Pass the authors name to Views\Shared\AuthorPartial.cshtml*@
@await Html.PartialAsync("AuthorPartial", Model.AuthorName)
@Model.PublicationDate

@*Loop over the Sections and pass in a section and additional ViewData
to the strongly typed Views\Articles\ArticleSection.cshtml partial view.*@
@{ var index = 0;
@foreach (var section in Model.Sections)
{
@await Html.PartialAsync("ArticleSection", section,
new ViewDataDictionary(this.ViewData) { { "index", index } })
index++;
}
}

Views/Shared/AuthorPartial:

@model string
<div>
<h3>@Model</h3>
This partial view came from /Views/Shared/AuthorPartial.cshtml.<br />
</div>

A parcial ArticleSection:
@using PartialViewsSample.ViewModels
@model ArticleSection

<h3>@Model.Title Index: @ViewData["index"] </h3>


<div>
@Model.Content
</div>

Em tempo de execução, as parciais são renderizadas para a exibição pai, que, por sua vez, é renderizada na
saída da exibição parcial _Layout.cshtml
Injeção de dependência em exibições
08/02/2018 • 6 min to read • Edit Online

Por Steve Smith


O ASP.NET Core dá suporte à injeção de dependência em exibições. Isso pode ser útil para serviços específicos a
uma exibição, como localização ou dados necessários apenas para o preenchimento de elementos de exibição.
Você deve tentar manter a separação de interesses entre os controladores e as exibições. A maioria dos dados
exibida pelas exibições deve ser passada pelo controlador.
Exibir ou baixar código de exemplo (como baixar)

Um exemplo simples
Injete um serviço em uma exibição usando a diretiva @inject . Considere @inject como a adição de uma
propriedade à exibição e o preenchimento da propriedade usando a DI.
A sintaxe de @inject : @inject <type> <name>

Um exemplo de @inject em ação:

@using System.Threading.Tasks
@using ViewInjectSample.Model
@using ViewInjectSample.Model.Services
@model IEnumerable<ToDoItem>
@inject StatisticsService StatsService
<!DOCTYPE html>
<html>
<head>
<title>To Do Items</title>
</head>
<body>
<div>
<h1>To Do Items</h1>
<ul>
<li>Total Items: @StatsService.GetCount()</li>
<li>Completed: @StatsService.GetCompletedCount()</li>
<li>Avg. Priority: @StatsService.GetAveragePriority()</li>
</ul>
<table>
<tr>
<th>Name</th>
<th>Priority</th>
<th>Is Done?</th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>@item.Name</td>
<td>@item.Priority</td>
<td>@item.IsDone</td>
</tr>
}
</table>
</div>
</body>
</html>
Essa exibição exibe uma lista de instâncias ToDoItem , junto com um resumo mostrando estatísticas gerais. O
resumo é populado com base no StatisticsService injetado. Esse serviço é registrado para injeção de
dependência em ConfigureServices em Startup.cs:

// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?


LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();

services.AddTransient<IToDoItemRepository, ToDoItemRepository>();
services.AddTransient<StatisticsService>();
services.AddTransient<ProfileOptionsService>();

O StatisticsService faz alguns cálculos no conjunto de instâncias ToDoItem , que ele acessa por meio de um
repositório:

using System.Linq;
using ViewInjectSample.Interfaces;

namespace ViewInjectSample.Model.Services
{
public class StatisticsService
{
private readonly IToDoItemRepository _toDoItemRepository;

public StatisticsService(IToDoItemRepository toDoItemRepository)


{
_toDoItemRepository = toDoItemRepository;
}

public int GetCount()


{
return _toDoItemRepository.List().Count();
}

public int GetCompletedCount()


{
return _toDoItemRepository.List().Count(x => x.IsDone);
}

public double GetAveragePriority()


{
if (_toDoItemRepository.List().Count() == 0)
{
return 0.0;
}

return _toDoItemRepository.List().Average(x => x.Priority);


}
}
}

O repositório de exemplo usa uma coleção em memória. A implementação mostrada acima (que opera em
todos os dados na memória) não é recomendada para conjuntos de dados grandes e acessados remotamente.
A amostra exibe dados do modelo associado à exibição e o serviço injetado na exibição:
Populando os dados de pesquisa
A injeção de exibição pode ser útil para popular opções em elementos de interface do usuário, como listas
suspensas. Considere um formulário de perfil do usuário que inclui opções para especificar o gênero, estado e
outras preferências. A renderização desse formulário usando uma abordagem MVC padrão exigirá que o
controlador solicite serviços de acesso a dados para cada um desses conjuntos de opções e, em seguida, popule
um modelo ou ViewBag com cada conjunto de opções a ser associado.
Uma abordagem alternativa injeta os serviços diretamente na exibição para obter as opções. Isso minimiza a
quantidade de código necessária para o controlador, movendo essa lógica de construção do elemento de
exibição para a própria exibição. A ação do controlador para exibir um formulário de edição de perfil precisa
apenas passar a instância de perfil para o formulário:

using Microsoft.AspNetCore.Mvc;
using ViewInjectSample.Model;

namespace ViewInjectSample.Controllers
{
public class ProfileController : Controller
{
[Route("Profile")]
public IActionResult Index()
{
// TODO: look up profile based on logged-in user
var profile = new Profile()
{
Name = "Steve",
FavColor = "Blue",
Gender = "Male",
State = new State("Ohio","OH")
};
return View(profile);
}
}
}

O formulário HTML usado para atualizar essas preferências inclui listas suspensas para três das propriedades:
Essas listas são populadas por um serviço que foi injetado na exibição:

@using System.Threading.Tasks
@using ViewInjectSample.Model.Services
@model ViewInjectSample.Model.Profile
@inject ProfileOptionsService Options
<!DOCTYPE html>
<html>
<head>
<title>Update Profile</title>
</head>
<body>
<div>
<h1>Update Profile</h1>
Name: @Html.TextBoxFor(m => m.Name)
<br/>
Gender: @Html.DropDownList("Gender",
Options.ListGenders().Select(g =>
new SelectListItem() { Text = g, Value = g }))
<br/>

State: @Html.DropDownListFor(m => m.State.Code,


Options.ListStates().Select(s =>
new SelectListItem() { Text = s.Name, Value = s.Code}))
<br />

Fav. Color: @Html.DropDownList("FavColor",


Options.ListColors().Select(c =>
new SelectListItem() { Text = c, Value = c }))
</div>
</body>
</html>

O ProfileOptionsService é um serviço no nível da interface do usuário criado para fornecer apenas os dados
necessários para esse formulário:
using System.Collections.Generic;

namespace ViewInjectSample.Model.Services
{
public class ProfileOptionsService
{
public List<string> ListGenders()
{
// keeping this simple
return new List<string>() {"Female", "Male"};
}

public List<State> ListStates()


{
// a few states from USA
return new List<State>()
{
new State("Alabama", "AL"),
new State("Alaska", "AK"),
new State("Ohio", "OH")
};
}

public List<string> ListColors()


{
return new List<string>() { "Blue","Green","Red","Yellow" };
}
}
}

DICA
Não se esqueça de registrar tipos que você solicitará por meio da injeção de dependência no método ConfigureServices
em Startup.cs.

Substituindo serviços
Além de injetar novos serviços, essa técnica também pode ser usada para substituir serviços injetados
anteriormente em uma página. A figura abaixo mostra todos os campos disponíveis na página usada no
primeiro exemplo:

Como você pode ver, os campos padrão incluem Html , Component e Url (bem como o StatsService que
injetamos). Se, para a instância, você desejava substituir os Auxiliares HTML padrão por seus próprios, faça isso
com facilidade usando @inject :
@using System.Threading.Tasks
@using ViewInjectSample.Helpers
@inject MyHtmlHelper Html
<!DOCTYPE html>
<html>
<head>
<title>My Helper</title>
</head>
<body>
<div>
Test: @Html.Value
</div>
</body>
</html>

Se deseja estender os serviços existentes, basta usar essa técnica herdando da implementação existente ou
encapsulando-a com sua própria implementação.

Consulte também
Blog de Simon Timms: Getting Lookup Data Into Your View (Inserindo dados de pesquisa na exibição)
Componentes da exibição
08/02/2018 • 17 min to read • Edit Online

Por Rick Anderson


Exibir ou baixar código de exemplo (como baixar)

Introdução aos componentes de exibição


Uma novidade no ASP.NET Core MVC, os componentes de exibição são semelhantes às exibições parciais, mas
são muito mais eficientes. Os componentes de exibição não usam a associação de modelos e dependem apenas
dos dados fornecidos durante uma chamada a eles. Um componente de exibição:
Renderiza uma parte em vez de uma resposta inteira.
Inclui os mesmos benefícios de capacidade de teste e separação de interesses e encontrados entre um
controlador e uma exibição.
Pode ter parâmetros e uma lógica de negócios.
É geralmente invocado em uma página de layout.
Os componentes de exibição destinam-se a qualquer momento em que há uma lógica de renderização
reutilizável muito complexa para uma exibição parcial, como:
Menus de navegação dinâmica
Nuvem de marcas (na qual ela consulta o banco de dados)
Painel de logon
Carrinho de compras
Artigos publicados recentemente
Conteúdo da barra lateral em um blog típico
Um painel de logon que é renderizado em cada página e mostra os links para logoff ou logon, dependendo
do estado de logon do usuário
Um componente de exibição consiste em duas partes: a classe (normalmente derivada de ViewComponent) e o
resultado que ele retorna (normalmente, uma exibição). Assim como os controladores, um componente de
exibição pode ser um POCO, mas a maioria dos desenvolvedores desejará aproveitar os métodos e as
propriedades disponíveis com a derivação de ViewComponent .

Criando um componente de exibição


Esta seção contém os requisitos de alto nível para a criação de um componente de exibição. Mais adiante neste
artigo, examinaremos cada etapa em detalhes e criaremos um componente de exibição.
A classe de componente de exibição
Uma classe de componente de exibição pode ser criada por um dos seguintes:
Derivação de ViewComponent
Decoração de uma classe com o atributo [ViewComponent] ou derivação de uma classe com o atributo
[ViewComponent]
Criação de uma classe em que o nome termina com o sufixo ViewComponent
Assim como os controladores, os componentes de exibição precisam ser classes públicas, não aninhadas e não
abstratas. O nome do componente de exibição é o nome da classe com o sufixo "ViewComponent" removido.
Também pode ser especificado de forma explícita com a propriedade ViewComponentAttribute.Name .
Uma classe de componente de exibição:
Dá suporte total à injeção de dependência do construtor
Não participa do ciclo de vida do controlador, o que significa que não é possível usar filtros em um
componente de exibição
Métodos de componente de exibição
Um componente de exibição define sua lógica em um método InvokeAsync que retorna um
IViewComponentResult . Os parâmetros são recebidos diretamente da invocação do componente de exibição, não
da associação de modelos. Um componente de exibição nunca manipula uma solicitação diretamente.
Normalmente, um componente de exibição inicializa um modelo e passa-o para uma exibição chamando o
método View . Em resumo, os métodos de componente de exibição:
Definem um método InvokeAsync que retorna um IViewComponentResult
Normalmente, inicializam um modelo e passam-o para uma exibição chamando o método ViewComponent
View
Os parâmetros são recebidos do método de chamada, não do HTTP e não há nenhuma associação de
modelos
Não podem ser acessados diretamente como um ponto de extremidade HTTP e são invocados no código
(normalmente em uma exibição). Um componente de exibição nunca manipula uma solicitação
São sobrecarregados na assinatura, em vez de nos detalhes da solicitação HTTP atual
Caminho de pesquisa de exibição
O tempo de execução pesquisa a exibição nos seguintes caminhos:
Views/<nome_do_controlador>/Components/<nome_do_componente_da_exibição>/<nome_do_modo_de_exibição>
Views/Shared/Components/<nome_do_componente_da_exibição>/<nome_do_modo_de_exibição>
O nome de exibição padrão de um componente de exibição é Default, o que significa que o arquivo de exibição
geralmente será nomeado Default.cshtml. Especifique outro nome de exibição ao criar o resultado do
componente de exibição ou ao chamar o método View .
Recomendamos que você nomeie o arquivo de exibição Default.cshtml e use o caminho
Views/Shared/Components/<nome_do_componente_da_exibição>/<nome_do_modo_de_exibição>. O
componente de exibição PriorityList usado nesta amostra usa
Views/Shared/Components/PriorityList/Default.cshtml como a exibição do componente de exibição.

Invocando um componente de exibição


Para usar o componente de exibição, chame o seguinte em uma exibição:

@Component.InvokeAsync("Name of view component", <anonymous type containing parameters>)

Os parâmetros serão passados para o método InvokeAsync . O componente de exibição PriorityList


desenvolvido no artigo é invocado por meio do arquivo de exibição Views/Todo/Index.cshtml. A seguir, o método
InvokeAsync é chamado com dois parâmetros:

@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })

Invocando um componente de exibição como um Auxiliar de Marca


Para o ASP.NET Core 1.1 e superior, invoque um componente de exibição como um Auxiliar de Marca:

<vc:priority-list max-priority="2" is-done="false">


</vc:priority-list>

Os parâmetros de classe e de método na formatação Pascal Case para Auxiliares de Marca são convertidos em
seu kebab case em minúsculas. Para invocar um componente de exibição, o Auxiliar de Marca usa o elemento
<vc></vc> . O componente de exibição é especificado da seguinte maneira:

<vc:[view-component-name]
parameter1="parameter1 value"
parameter2="parameter2 value">
</vc:[view-component-name]>

Observação: para usar um Componente de Exibição como um Auxiliar de Marca, é necessário registrar o
assembly que contém o Componente de Exibição usando a diretiva @addTagHelper . Por exemplo, se o
Componente de Exibição está em um assembly chamado "MyWebApp", adicione a seguinte diretiva ao arquivo
_ViewImports.cshtml :

@addTagHelper *, MyWebApp

Registre um Componente de Exibição como um Auxiliar de Marca em qualquer arquivo que referencia o
Componente de Exibição. Consulte Gerenciando o escopo do Auxiliar de Marca para obter mais informações
sobre como registrar Auxiliares de Marca.
O método InvokeAsync usado neste tutorial:

@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })

Na marcação do Auxiliar de Marca:

<vc:priority-list max-priority="2" is-done="false">


</vc:priority-list>

Na amostra acima, o componente de exibição PriorityList torna-se priority-list . Os parâmetros para o


componente de exibição são passados como atributos em kebab case em minúsculas.
Invocando um componente de exibição diretamente em um controlador
Os componentes de exibição normalmente são invocados em uma exibição, mas você pode invocá-los
diretamente em um método do controlador. Embora os componentes de exibição não definam pontos de
extremidade como controladores, você pode implementar com facilidade uma ação do controlador que retorna o
conteúdo de um ViewComponentResult .
Neste exemplo, o componente de exibição é chamado diretamente no controlador:

public IActionResult IndexVC()


{
return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
}

Passo a passo: criando um componente de exibição simples


Baixe, compile e teste o código inicial. É um projeto simples com um controlador Todo que exibe uma lista de
itens Todo.

Adicionar uma classe ViewComponent


Crie uma pasta ViewComponents e adicione a seguinte classe PriorityListViewComponent :

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents
{
public class PriorityListViewComponent : ViewComponent
{
private readonly ToDoContext db;

public PriorityListViewComponent(ToDoContext context)


{
db = context;
}

public async Task<IViewComponentResult> InvokeAsync(


int maxPriority, bool isDone)
{
var items = await GetItemsAsync(maxPriority, isDone);
return View(items);
}
private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
{
return db.ToDo.Where(x => x.IsDone == isDone &&
x.Priority <= maxPriority).ToListAsync();
}
}
}

Observações sobre o código:


As classes de componente de exibição podem ser contidas em qualquer pasta do projeto.
Como o nome da classe PriorityListViewComponent termina com o sufixo ViewComponent, o tempo
de execução usará a cadeia de caracteres "PriorityList" ao referenciar o componente de classe em uma
exibição. Explicarei isso mais detalhadamente mais adiante.
O atributo [ViewComponent] pode alterar o nome usado para referenciar um componente de exibição. Por
exemplo, poderíamos nomear a classe XYZ e aplicar o atributo ViewComponent :

[ViewComponent(Name = "PriorityList")]
public class XYZ : ViewComponent

O atributo [ViewComponent] acima instrui o seletor de componente de exibição a usar o nome


PriorityList ao procurar as exibições associadas ao componente e a usar a cadeia de caracteres
"PriorityList" ao referenciar o componente de classe em uma exibição. Explicarei isso mais
detalhadamente mais adiante.
O componente usa a injeção de dependência para disponibilizar o contexto de dados.
InvokeAsync expõe um método que pode ser chamado em uma exibição e pode usar um número
arbitrário de argumentos.
O método InvokeAsync retorna o conjunto de itens ToDo que atendem aos parâmetros isDone e
maxPriority .
Criar a exibição do Razor do componente de exibição
Crie a pasta Views/Shared/Components. Essa pasta deve nomeada Components.
Crie a pasta Views/Shared/Components/PriorityList. Esse nome de pasta deve corresponder ao nome da
classe do componente de exibição ou ao nome da classe menos o sufixo (se seguimos a convenção e
usamos o sufixo ViewComponent no nome da classe). Se você usou o atributo ViewComponent , o nome da
classe precisa corresponder à designação de atributo.
Crie uma exibição do Razor Views/Shared/Components/PriorityList/Default.cshtml: [!code-cshtmlMain]
A exibição do Razor usa uma lista de TodoItem e exibe-os. Se o método InvokeAsync do componente de
exibição não passar o nome da exibição (como em nossa amostra), Default será usado como o nome da
exibição, por convenção. Mais adiante no tutorial, mostrarei como passar o nome da exibição. Para
substituir o estilo padrão de um controlador específico, adicione uma exibição à pasta de exibição
específica ao controlador (por exemplo, Views/Todo/Components/PriorityList/Default.cshtml).
Se o componente de exibição é específico ao controlador, adicione-o à pasta específica ao controlador
(Views/Todo/Components/PriorityList/Default.cshtml).
Adicione um div que contém uma chamada ao componente da lista de prioridades à parte inferior do
arquivo Views/Todo/index.cshtml:

</table>
<div>
@await Component.InvokeAsync("PriorityList", new { maxPriority = 2, isDone = false })
</div>

A marcação @await Component.InvokeAsync mostra a sintaxe para chamar componentes de exibição. O primeiro
argumento é o nome do componente que queremos invocar ou chamar. Os parâmetros seguintes são passados
para o componente. InvokeAsync pode usar um número arbitrário de argumentos.
Teste o aplicativo. A seguinte imagem mostra a lista ToDo e os itens de prioridade:
Também chame o componente de exibição diretamente no controlador:

public IActionResult IndexVC()


{
return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
}

Especificando um nome de exibição


Um componente de exibição complexo pode precisar especificar uma exibição não padrão em algumas
condições. O código a seguir mostra como especificar a exibição "PVC" no método InvokeAsync . Atualize o
método InvokeAsync na classe PriorityListViewComponent .
public async Task<IViewComponentResult> InvokeAsync(
int maxPriority, bool isDone)
{
string MyView = "Default";
// If asking for all completed tasks, render with the "PVC" view.
if (maxPriority > 3 && isDone == true)
{
MyView = "PVC";
}
var items = await GetItemsAsync(maxPriority, isDone);
return View(MyView, items);
}

Copie o arquivo Views/Shared/Components/PriorityList/Default.cshtml para uma exibição nomeada


Views/Shared/Components/PriorityList/PVC.cshtml. Adicione um cabeçalho para indicar que a exibição PVC
está sendo usada.

@model IEnumerable<ViewComponentSample.Models.TodoItem>

<h2> PVC Named Priority Component View</h2>


<h4>@ViewBag.PriorityMessage</h4>
<ul>
@foreach (var todo in Model)
{
<li>@todo.Name</li>
}
</ul>

Atualize Views/TodoList/Index.cshtml:

@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })

Execute o aplicativo e verifique a exibição PVC.


Se a exibição PVC não é renderizada, verifique se você está chamando o componente de exibição com uma
prioridade igual a 4 ou superior.
Examinar o caminho de exibição
Altere o parâmetro de prioridade para três ou menos para que a exibição de prioridade não seja
retornada.
Renomeie temporariamente o Views/Todo/Components/PriorityList/Default.cshtml como 1Default.cshtml.
Teste o aplicativo. Você obterá o seguinte erro:

An unhandled exception occurred while processing the request.


InvalidOperationException: The view 'Components/PriorityList/Default' wasn't found. The following
locations were searched:
/Views/ToDo/Components/PriorityList/Default.cshtml
/Views/Shared/Components/PriorityList/Default.cshtml
EnsureSuccessful

Copie Views/Todo/Components/PriorityList/1Default.cshtml para


Views/Shared/Components/PriorityList/Default.cshtml.
Adicione uma marcação ao componente de exibição Todo Shared para indicar que a exibição foi obtida da
pasta Shared.
Teste o componente de exibição Shared.
Evitando cadeias de caracteres mágicas
Se deseja obter segurança em tempo de compilação, substitua o nome do componente de exibição embutido em
código pelo nome da classe. Crie o componente de exibição sem o sufixo "ViewComponent":

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents
{
public class PriorityList : ViewComponent
{
private readonly ToDoContext db;

public PriorityList(ToDoContext context)


{
db = context;
}

public async Task<IViewComponentResult> InvokeAsync(


int maxPriority, bool isDone)
{
var items = await GetItemsAsync(maxPriority, isDone);
return View(items);
}
private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
{
return db.ToDo.Where(x => x.IsDone == isDone &&
x.Priority <= maxPriority).ToListAsync();
}
}
}

Adicione uma instrução using ao arquivo de exibição do Razor e use o operador nameof :
@using ViewComponentSample.Models
@using ViewComponentSample.ViewComponents
@model IEnumerable<TodoItem>

<h2>ToDo nameof</h2>
<!-- Markup removed for brevity. -->
}
</table>

<div>

@await Component.InvokeAsync(nameof(PriorityList), new { maxPriority = 4, isDone = true })


</div>

Recursos adicionais
Injeção de dependência em exibições
Manipulando solicitações com controladores no
ASP.NET Core MVC
08/02/2018 • 10 min to read • Edit Online

Por Steve Smith e Scott Addie


Controladores, ações e resultados da ação são uma parte fundamental de como os desenvolvedores criam
aplicativos usando o ASP.NET Core MVC.

O que é um controlador?
Um controlador é usado para definir e agrupar um conjunto de ações. Uma ação (ou método de ação) é um
método em um controlador que manipula solicitações. Os controladores agrupam ações semelhantes de forma
lógica. Essa agregação de ações permite que conjuntos de regras comuns, como roteamento, cache e autorização,
sejam aplicados em conjunto. As solicitações são mapeadas para ações por meio de roteamento.
Por convenção, as classes do controlador:
Residem na pasta Controllers no nível raiz do projeto
Herdam de Microsoft.AspNetCore.Mvc.Controller
Um controlador é uma classe que pode ser instanciada, em que, pelo menos, uma das seguintes condições é
verdadeira:
O nome da classe tem "Controller" como sufixo
A classe herda de uma classe cujo nome tem "Controller" como sufixo
A classe está decorada com o atributo [Controller]
Uma classe de controlador não deve ter um atributo [NonController] associado.
Os controladores devem seguir o Princípio de Dependências Explícitas. Há algumas abordagens para
implementar esse princípio. Se várias ações do controlador exigem o mesmo serviço, considere o uso da injeção
de construtor para solicitar essas dependências. Se o serviço é necessário para um único método de ação,
considere o uso da Injeção de Ação para solicitar a dependência.
Dentro do padrão Model-View -Controller, um controlador é responsável pelo processamento inicial da solicitação
e criação de uma instância do modelo. Em geral, as decisões de negócios devem ser tomadas dentro do modelo.
O controlador usa o resultado do processamento do modelo (se houver) e retorna a exibição correta e seus dados
da exibição associada ou o resultado da chamada à API. Saiba mais em Visão geral do ASP.NET Core MVC e
Introdução ao ASP.NET Core MVC e ao Visual Studio.
O controlador é uma abstração no nível da interface do usuário. Suas responsabilidades são garantir que os
dados de solicitação sejam válidos e escolher qual exibição (ou resultado de uma API) deve ser retornada. Em
aplicativos bem fatorados, ele não inclui diretamente o acesso a dados ou a lógica de negócios. Em vez disso, o
controlador delega essas responsabilidades a serviços.

Definindo ações
Métodos públicos em um controlador, exceto aqueles decorados com o atributo [NonAction] , são ações.
Parâmetros em ações são associados aos dados de solicitação e validados usando a associação de modelos. A
validação de modelo ocorre em tudo o que é associado ao modelo. O valor da propriedade ModelState.IsValid
indica se a associação de modelos e a validação foram bem-sucedidas.
Métodos de ação devem conter uma lógica para mapear uma solicitação para um interesse de negócios.
Normalmente, interesses de negócios devem ser representados como serviços acessados pelo controlador por
meio da injeção de dependência. Em seguida, as ações mapeiam o resultado da ação de negócios para um estado
do aplicativo.
As ações podem retornar qualquer coisa, mas frequentemente retornam uma instância de IActionResult (ou
Task<IActionResult> para métodos assíncronos) que produz uma resposta. O método de ação é responsável por
escolher o tipo de resposta. O resultado da ação é responsável pela resposta.
Métodos auxiliares do controlador
Os controladores geralmente herdam de Controller, embora isso não seja necessário. A derivação de Controller
fornece acesso a três categorias de métodos auxiliares:
1. Métodos que resultam em um corpo de resposta vazio
Nenhum cabeçalho de resposta HTTP Content-Type é incluído, pois o corpo da resposta não tem nenhum
conteúdo a ser descrito.
Há dois tipos de resultado nessa categoria: Redirecionamento e Código de Status HTTP.
Código de Status HTTP
Esse tipo retorna um código de status HTTP. Alguns métodos auxiliares desse tipo são BadRequest ,
NotFound e Ok . Por exemplo, return BadRequest(); produz um código de status 400 quando executado.
Quando métodos como BadRequest , NotFound e Ok estão sobrecarregados, eles deixam de se qualificar
como respondentes do Código de Status HTTP, pois a negociação de conteúdo está em andamento.
Redirecionamento
Esse tipo retorna um redirecionamento para uma ação ou um destino (usando Redirect , LocalRedirect ,
RedirectToAction ou RedirectToRoute ). Por exemplo,
return RedirectToAction("Complete", new {id = 123}); redireciona para Complete , passando um objeto
anônimo.
O tipo de resultado do Redirecionamento é diferente do tipo do Código de Status HTTP, principalmente na
adição de um cabeçalho de resposta HTTP Location .
2. Métodos que resultam em um corpo de resposta não vazio com um tipo de conteúdo predefinido
A maioria dos métodos auxiliares desta categoria inclui uma propriedade ContentType , permitindo que você
defina o cabeçalho de resposta Content-Type para descrever o corpo da resposta.
Há dois tipos de resultado nessa categoria: Exibição e Resposta Formatada.
Exibir
Esse tipo retorna uma exibição que usa um modelo para renderizar HTML. Por exemplo,
return View(customer); passa um modelo para a exibição para associação de dados.

Resposta Formatada
Esse tipo retorna JSON ou um formato de troca de dados semelhante para representar um objeto de uma
maneira específica. Por exemplo, return Json(customer); serializa o objeto fornecido no formato JSON.
Outros métodos comuns desse tipo incluem File , e VirtualFile . Por exemplo,
PhysicalFile
return PhysicalFile(customerFilePath, "text/xml"); retorna um arquivo XML descrito por um valor do
cabeçalho de resposta Content-Type igual a "text/xml".
3. Métodos que resultam em um corpo de resposta não vazio formatado em um tipo de conteúdo negociado com o cliente
Essa categoria é mais conhecida como Negociação de Conteúdo. A Negociação de conteúdo aplica-se sempre
que uma ação retorna um tipo ObjectResult ou algo diferente de uma implementação IActionResult. Uma ação
que retorna uma implementação não IActionResult (por exemplo, object ) também retorna uma Resposta
Formatada.
Alguns métodos auxiliares desse tipo incluem BadRequest , CreatedAtRoute e Ok . Exemplos desses métodos
incluem return BadRequest(modelState); , return CreatedAtRoute("routename", values, newobject); e
return Ok(value); , respectivamente. Observe que BadRequest e Ok fazem a negociação de conteúdo apenas
quando um valor é passado; sem que um valor seja passado, eles atuam como tipos de resultado do Código de
Status HTTP. Por outro lado, o método CreatedAtRoute sempre faz a negociação de conteúdo, pois todas as suas
sobrecargas exigem que um valor seja passado.
Interesses paralelos
Normalmente, os aplicativos compartilham partes de seu fluxo de trabalho. Exemplos incluem um aplicativo que
exige autenticação para acessar o carrinho de compras ou um aplicativo que armazena dados em cache em
algumas páginas. Para executar a lógica antes ou depois de um método de ação, use um filtro. O uso de Filtros em
interesses paralelos pode reduzir a duplicação, possibilitando que elas sigam o princípio DRY (Don't Repeat
Yourself ).
A maioria dos atributos de filtro, como [Authorize] , pode ser aplicada no nível do controlador ou da ação,
dependendo do nível desejado de granularidade.
O tratamento de erro e o cache de resposta costumam ser interesses paralelos:
Tratamento de erro
Cache de resposta
Muitos interesses paralelos podem ser abordados com filtros ou um middleware personalizado.
Ações de roteamento para o controlador
10/04/2018 • 57 min to read • Edit Online

Por Ryan Nowak e Rick Anderson


O ASP.NET Core MVC usa o middleware de Roteamento para fazer as correspondências das URLs de
solicitações de entrada e mapeá-las para ações. As rotas são definidas em atributos ou no código de
inicialização. Elas descrevem como deve ser feita a correspondência entre caminhos de URL e ações. As
rotas também são usadas para gerar URLs (para links) enviados em resposta.
As ações são roteadas convencionalmente ou segundo os atributos. Colocar uma rota no controlador
ou na ação faz com que ela seja roteada segundo o atributo. Para obter mais informações, consulte
Roteamento misto.
Este documento explicará as interações entre o MVC e o roteamento e como aplicativos MVC comuns
usam recursos de roteamento. Consulte Roteamento para obter detalhes sobre o roteamento avançado.

Configurando o middleware de Roteamento


No método Configurar, você poderá ver código semelhante a:

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

Dentro da chamada para UseMvc , MapRoute é usado para criar uma única rota, que chamaremos de
rota default . A maioria dos aplicativos MVC usa uma rota com um modelo semelhante à rota
default .

O modelo de rota "{controller=Home}/{action=Index}/{id?}" pode corresponder a um caminho de URL


como /Products/Details/5 e extrai os valores de rota
{ controller = Products, action = Details, id = 5 } gerando tokens para o caminho. O MVC tentará
localizar um controlador chamado ProductsController e executar a ação Details :

public class ProductsController : Controller


{
public IActionResult Details(int id) { ... }
}

Observe que, neste exemplo, a associação de modelos usaria o valor de id = 5 para definir o
parâmetro id como 5 ao invocar essa ação. Consulte Associação de modelos para obter mais
detalhes.
Usando a rota default :

routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");

O modelo da rota:
{controller=Home} define Home como o controller padrão
{action=Index} define Index como o action padrão
{id?} define id como opcional

Parâmetros de rota opcionais e padrão não precisam estar presentes no caminho da URL para que haja
uma correspondência. Consulte Referência de modelo de rota para obter uma descrição detalhada da
sintaxe do modelo de rota.
"{controller=Home}/{action=Index}/{id?}" pode corresponder ao caminho da URL / e produzirá os
valores de rota { controller = Home, action = Index } . Os valores de controller e action usam os
valores padrão, id não produz um valor, uma vez que não há nenhum segmento correspondente no
caminho da URL. O MVC usaria esses valores de rota para selecionar a ação HomeController e Index :

public class HomeController : Controller


{
public IActionResult Index() { ... }
}

Usando essa definição de controlador e modelo de rota, a ação HomeController.Index seria executada
para qualquer um dos caminhos de URL a seguir:
/Home/Index/17

/Home/Index

/Home

O método de conveniência UseMvcWithDefaultRoute :

app.UseMvcWithDefaultRoute();

Pode ser usado para substituir:

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

UseMvc e UseMvcWithDefaultRoute adicionam uma instância de RouterMiddleware ao pipeline de


middleware. O MVC não interage diretamente com o middleware e usa o roteamento para tratar das
solicitações. O MVC é conectado às rotas por meio de uma instância de MvcRouteHandler . O código
dentro de UseMvc é semelhante ao seguinte:

var routes = new RouteBuilder(app);

// Add connection to MVC, will be hooked up by calls to MapRoute.


routes.DefaultHandler = new MvcRouteHandler(...);

// Execute callback to register routes.


// routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");

// Create route collection and add the middleware.


app.UseRouter(routes.Build());

UseMvc não define diretamente nenhuma rota, ele adiciona um espaço reservado à coleção de rotas
para a rota attribute . A sobrecarga UseMvc(Action<IRouteBuilder>) permite adicionar suas próprias
rotas e também dá suporte ao roteamento de atributos. UseMvc e todas as suas variações adicionam um
espaço reservado à rota do atributo – o roteamento de atributos sempre está disponível,
independentemente de como você configura UseMvc . UseMvcWithDefaultRoute define uma rota padrão e
dá suporte ao roteamento de atributos. A seção Roteamento de atributos inclui mais detalhes sobre o
roteamento de atributos.

Roteamento convencional
A rota default :

routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");

é um exemplo de roteamento convencional. Nós chamamos esse estilo de roteamento convencional


porque ele estabelece uma convenção para caminhos de URL:
o primeiro segmento do caminho é mapeado para o nome do controlador,
o segundo é mapeado para o nome da ação,
o terceiro segmento é usado para uma id opcional usado para mapear para uma entidade de
modelo.
Usando essa rota , o caminho da URL /Products/List é mapeado para a ação
default
ProductsController.List e /Blog/Article/17 é mapeado para BlogController.Article . Esse
mapeamento é baseado somente nos nomes do controlador e da ação e não é baseado em
namespaces, locais de arquivos de origem ou parâmetros de método.

DICA
Usar o roteamento convencional com a rota padrão permite compilar o aplicativo rapidamente sem precisar criar
um novo padrão de URL para cada ação que você definir. Para um aplicativo com ações de estilo CRUD, ter
consistência para as URLs em seus controladores pode ajudar a simplificar seu código e a tornar sua interface do
usuário mais previsível.

AVISO
O id é definido como opcional pelo modelo de rota, o que significa que suas ações podem ser executadas sem
a ID fornecida como parte da URL. Normalmente, o que acontecerá se id for omitido da URL é que ele será
definido como 0 pela associação de modelos e, dessa forma, não será encontrada no banco de dados nenhuma
entidade correspondente a id == 0 . O roteamento de atributos pode lhe proporcionar controle refinado para
tornar a ID obrigatória para algumas ações e não para outras. Por convenção, a documentação incluirá
parâmetros opcionais, como id , quando for provável que eles apareçam no uso correto.

Várias rotas
É possível adicionar várias rotas dentro de UseMvc adicionando mais chamadas para MapRoute . Fazer
isso permite que você defina várias convenções ou que adicione rotas convencionais dedicadas a uma
ação específica, como:
app.UseMvc(routes =>
{
routes.MapRoute("blog", "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});

A rota blog aqui é uma rota convencional dedicada, o que significa que ela usa o sistema de
roteamento convencional, mas é dedicada a uma ação específica. Como controller e action não
aparecem no modelo de rota como parâmetros, eles só podem ter os valores padrão e, portanto, essa
rota sempre será mapeada para a ação BlogController.Article .
As rotas na coleção de rotas são ordenadas e serão processadas na ordem em que forem adicionadas.
Portanto, neste exemplo, a rota blog será tentada antes da rota default .

OBSERVAÇÃO
Rotas convencionais dedicadas geralmente usam parâmetros de rota que capturam tudo, como {*article} ,
para capturar a parte restante do caminho da URL. Isso pode fazer com que uma rota fique "muito ambiciosa",
ou seja, que faça a correspondência com URLs que deveriam ser correspondidas com outras rotas. Coloque as
rotas "ambiciosas" mais adiante na tabela de rotas para solucionar esse problema.

Fallback
Como parte do processamento de solicitações, o MVC verificará se o valores das rotas podem ser
usados para encontrar um controlador e uma ação em seu aplicativo. Se os valores das rotas não
corresponderem a uma ação, a rota não será considerada correspondente e a próxima rota será tentada.
Isso é chamado de fallback e sua finalidade é simplificar casos em que rotas convencionais se
sobrepõem.
Desambiguação de ações
Quando duas ações correspondem por meio do roteamento, o MVC precisa resolver a ambiguidade
para escolher a "melhor" candidata ou lançar uma exceção. Por exemplo:

public class ProductsController : Controller


{
public IActionResult Edit(int id) { ... }

[HttpPost]
public IActionResult Edit(int id, Product product) { ... }
}

Esse controlador define duas ações que fariam a correspondência entre caminho da URL
/Products/Edit/17 e a os dados da rota { controller = Products, action = Edit, id = 17 } . Este é um
padrão comum para controladores MVC em que Edit(int) mostra um formulário para editar um
produto e Edit(int, Product) processa o formulário postado. Para que isso seja possível, o MVC
precisa escolher Edit(int, Product) quando a solicitação é um POST HTTP e Edit(int) quando o
verbo HTTP é qualquer outra coisa.
O HttpPostAttribute ( [HttpPost] ) é uma implementação de IActionConstraint que só permite que a
ação seja selecionada quando o verbo HTTP é POST . A presença de um IActionConstraint faz do
Edit(int, Product) uma "melhor" correspondência do que Edit(int) , portanto, Edit(int, Product)
será tentado primeiro.
Você só precisará gravar implementações personalizadas de IActionConstraint em cenários
especializados, mas é importante compreender a função de atributos como HttpPostAttribute –
atributos semelhantes são definidos para outros verbos HTTP. No roteamento convencional, é comum
que ações usem o mesmo nome de ação quando fazem parte de um fluxo de trabalho de
show form -> submit form . A conveniência desse padrão ficará mais aparente após você revisar a seção
Noções básicas sobre IActionConstraint.
Se várias rotas corresponderem e o MVC não puder encontrar uma rota "melhor", ele gerará um
AmbiguousActionException .

Nomes de rotas
As cadeias de caracteres "blog" e "default" nos exemplos a seguir são nomes de rotas:

app.UseMvc(routes =>
{
routes.MapRoute("blog", "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});

Os nomes de rotas dão a uma rota um nome lógico, de modo que a rota nomeada possa ser usada para
geração de URL. Isso simplifica muito a criação de URLs quando a ordenação de rotas poderia
complicá-la. Nomes de rotas devem ser exclusivos no nível do aplicativo.
Os nomes de rotas não têm impacto sobre a correspondência de URLs ou o tratamento de solicitações;
eles são usados apenas para a geração de URLs. Roteamento tem informações mais detalhadas sobre
geração de URLs, incluindo a geração de URLs em auxiliares específicos do MVC.

Roteamento de atributo
O roteamento de atributo usa um conjunto de atributos para mapear ações diretamente para modelos
de rota. No exemplo a seguir, app.UseMvc(); é usado no método Configure e nenhuma rota é passada.
O HomeController corresponderá a um conjunto de URLs semelhantes ao que a rota padrão
{controller=Home}/{action=Index}/{id?} corresponderia:

public class HomeController : Controller


{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
public IActionResult Index()
{
return View();
}
[Route("Home/About")]
public IActionResult About()
{
return View();
}
[Route("Home/Contact")]
public IActionResult Contact()
{
return View();
}
}

A ação HomeController.Index() será executada para qualquer um dos caminhos de URL / , /Home ou
/Home/Index .
OBSERVAÇÃO
Este exemplo destaca uma diferença importante de programação entre o roteamento de atributo e o
roteamento convencional. O roteamento de atributo requer mais entradas para especificar uma rota; a rota
padrão convencional manipula as rotas de forma mais sucinta. No entanto, o roteamento de atributo permite (e
exige) o controle preciso de quais modelos de rota se aplicam a cada ação.

Com o roteamento de atributo, o nome do controlador e os nomes de ação não desempenham


nenhuma função quanto a qual ação é selecionada. Este exemplo corresponderá as mesmas URLs que
o exemplo anterior.

public class MyDemoController : Controller


{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
public IActionResult MyIndex()
{
return View("Index");
}
[Route("Home/About")]
public IActionResult MyAbout()
{
return View("About");
}
[Route("Home/Contact")]
public IActionResult MyContact()
{
return View("Contact");
}
}

OBSERVAÇÃO
Os modelos de rota acima não definem parâmetros de rota para action , area e controller . Na verdade,
esses parâmetros de rota não são permitidos em rotas de atributo. Uma vez que o modelo de rota já está
associado a uma ação, não faria sentido analisar o nome da ação da URL.

Roteamento de atributo com atributos Http[Verb]


O roteamento de atributo também pode usar atributos Http[Verb] , como HttpPostAttribute . Todos
esses atributos podem aceitar um modelo de rota. Este exemplo mostra duas ações que correspondem
ao mesmo modelo de rota:

[HttpGet("/products")]
public IActionResult ListProducts()
{
// ...
}

[HttpPost("/products")]
public IActionResult CreateProduct(...)
{
// ...
}

Para um caminho de URL como /products , a ação ProductsApi.ListProducts será executada quando o
verbo HTTP for GET e ProductsApi.CreateProduct será executado quando o verbo HTTP for POST .
Primeiro, o roteamento de atributo faz a correspondência da URL com o conjunto de modelos de rota
definidos por atributos de rota. Quando um modelo de rota for correspondente, restrições de
IActionConstraint serão aplicadas para determinar quais ações podem ser executadas.

DICA
Ao compilar uma API REST, é raro que você queira usar [Route(...)] em um método de ação. É melhor usar o
Http*Verb*Attributes mais específico para ser preciso quanto ao que tem suporte de sua API. Espera-se que
clientes de APIs REST saibam quais caminhos e verbos HTTP são mapeados para operações lógicas específicas.

Como uma rota de atributo se aplica a uma ação específica, é fácil fazer com que parâmetros sejam
obrigatórios como parte da definição do modelo de rota. Neste exemplo, id é obrigatório como parte
do caminho da URL.

public class ProductsApiController : Controller


{
[HttpGet("/products/{id}", Name = "Products_List")]
public IActionResult GetProduct(int id) { ... }
}

A ação ProductsApi.GetProduct(int) será executada para um caminho de URL como /products/3 , mas
não para um caminho de URL como /products . Consulte Roteamento para obter uma descrição
completa de modelos de rota e as opções relacionadas.

Nome da rota
O código a seguir define um nome da rota como Products_List :

public class ProductsApiController : Controller


{
[HttpGet("/products/{id}", Name = "Products_List")]
public IActionResult GetProduct(int id) { ... }
}

Nomes de rota podem ser usados para gerar uma URL com base em uma rota específica. Nomes de
rota não têm impacto sobre o comportamento de correspondência da URL e são usados somente para
geração de URLs. Nomes de rotas devem ser exclusivos no nível do aplicativo.

OBSERVAÇÃO
Compare isso com a rota padrão convencional, que define o parâmetro id como opcional ( {id?} ). Essa
capacidade de especificar APIs de forma específica tem vantagens, como permitir que /products e
/products/5 sejam expedidos para ações diferentes.

Combinando rotas
Para tornar o roteamento de atributo menos repetitivo, os atributos de rota no controlador são
combinados com atributos de rota nas ações individuais. Modelos de rota definidos no controlador
precedem modelos de rota nas ações. Colocar um atributo de rota no controlador faz com que todas as
ações no controlador usem o roteamento de atributo.
[Route("products")]
public class ProductsApiController : Controller
{
[HttpGet]
public IActionResult ListProducts() { ... }

[HttpGet("{id}")]
public ActionResult GetProduct(int id) { ... }
}

Neste exemplo, o caminho de URL /products pode corresponder a ProductsApi.ListProducts e o


caminho de URL /products/5 pode corresponder a ProductsApi.GetProduct(int) . Essas duas ações são
correspondentes somente ao GET HTTP porque são decoradas com o HttpGetAttribute .
Modelos de rota aplicados a uma ação que começam com um / não são combinados com modelos de
rota aplicados ao controlador. Este exemplo corresponde a um conjunto de caminhos de URL
semelhante à rota padrão.

[Route("Home")]
public class HomeController : Controller
{
[Route("")] // Combines to define the route template "Home"
[Route("Index")] // Combines to define the route template "Home/Index"
[Route("/")] // Doesn't combine, defines the route template ""
public IActionResult Index()
{
ViewData["Message"] = "Home index";
var url = Url.Action("Index", "Home");
ViewData["Message"] = "Home index" + "var url = Url.Action; = " + url;
return View();
}

[Route("About")] // Combines to define the route template "Home/About"


public IActionResult About()
{
return View();
}
}

Ordenando rotas de atributos


Diferente de rotas convencionais, que são executadas em uma ordem definida, o roteamento de
atributo cria uma árvore e faz a correspondência de todas as rotas simultaneamente. O comportamento
é como se as entradas de rota fossem colocadas em uma ordem ideal; as rotas mais específicas têm
uma chance de ser executadas antes das rotas mais gerais.
Por exemplo, uma rota como blog/search/{topic} é mais específica que uma rota como
blog/{*article} . Em termos da lógica, a rota blog/search/{topic} é "executada" primeiro, por padrão,
porque essa é a única ordem que faz sentido. Usando o roteamento convencional, o desenvolvedor é
responsável por colocar as rotas na ordem desejada.
Rotas de atributos podem configurar uma ordem, usando a propriedade Order de todos os atributos
de rota fornecidos pela estrutura. As rotas são processadas segundo uma classificação crescente da
propriedade Order . A ordem padrão é 0 . Uma rota definida usando Order = -1 será executada antes
de rotas que não definem uma ordem. Uma rota definida usando Order = 1 será executada após a
ordem das rotas padrão.
DICA
Evite depender de Order . Se o seu espaço de URL exigir valores de ordem explícita para fazer o roteamento
corretamente, provavelmente ele também será confuso para os clientes. De modo geral, o roteamento de
atributos selecionará a rota correta com a correspondência de URL. Se a ordem padrão usada para a geração de
URL não estiver funcionando, usar o nome da rota como uma substituição geralmente será mais simples do que
aplicar a propriedade Order .

Substituição de token em modelos de rota ([controlador] [ação]


[área])
Para conveniência, as rotas de atributo dão suporte à substituição de token colocando um token entre
chaves quadradas ( [ , ] ). Os tokens [action] , [area] e [controller] serão substituídos pelos
valores do nome da ação, do nome da área e do nome do controlador da ação em que a rota é definida.
Neste exemplo, as ações podem corresponder a caminhos de URL conforme descrito nos comentários:

[Route("[controller]/[action]")]
public class ProductsController : Controller
{
[HttpGet] // Matches '/Products/List'
public IActionResult List() {
// ...
}

[HttpGet("{id}")] // Matches '/Products/Edit/{id}'


public IActionResult Edit(int id) {
// ...
}
}

A substituição de token ocorre como a última etapa da criação das rotas de atributo. O exemplo acima
se comportará da mesma forma que o código a seguir:

public class ProductsController : Controller


{
[HttpGet("[controller]/[action]")] // Matches '/Products/List'
public IActionResult List() {
// ...
}

[HttpGet("[controller]/[action]/{id}")] // Matches '/Products/Edit/{id}'


public IActionResult Edit(int id) {
// ...
}
}

Rotas de atributo também podem ser combinadas com herança. Isso é especialmente eficiente em
combinação com a substituição de token.
[Route("api/[controller]")]
public abstract class MyBaseController : Controller { ... }

public class ProductsController : MyBaseController


{
[HttpGet] // Matches '/api/Products'
public IActionResult List() { ... }

[HttpPost("{id}")] // Matches '/api/Products/{id}'


public IActionResult Edit(int id) { ... }
}

A substituição de token também se aplica a nomes de rota definidos por rotas de atributo.
[Route("[controller]/[action]", Name="[controller]_[action]")] gera um nome de rota exclusivo para
cada ação.
Para corresponder ao delimitador de substituição de token literal [ ou ] , faça seu escape repetindo o
caractere ( [[ ou ]] ).
Várias rotas
O roteamento de atributo dá suporte à definição de várias rotas que atingem a mesma ação. O uso
mais comum desse recurso é para simular o comportamento da rota convencional padrão, conforme
mostrado no exemplo a seguir:

[Route("[controller]")]
public class ProductsController : Controller
{
[Route("")] // Matches 'Products'
[Route("Index")] // Matches 'Products/Index'
public IActionResult Index()
}

Colocar vários atributos de rota no controlador significa que cada um deles será combinado com cada
um dos atributos de rota nos métodos de ação.

[Route("Store")]
[Route("[controller]")]
public class ProductsController : Controller
{
[HttpPost("Buy")] // Matches 'Products/Buy' and 'Store/Buy'
[HttpPost("Checkout")] // Matches 'Products/Checkout' and 'Store/Checkout'
public IActionResult Buy()
}

Quando vários atributos de rota (que implementam IActionConstraint ) são colocados em uma ação,
cada restrição da ação combina com o modelo de rota do atributo que a definiu.

[Route("api/[controller]")]
public class ProductsController : Controller
{
[HttpPut("Buy")] // Matches PUT 'api/Products/Buy'
[HttpPost("Checkout")] // Matches POST 'api/Products/Checkout'
public IActionResult Buy()
}
DICA
Embora o uso de várias rotas em ações possa parecer eficaz, é melhor manter o espaço de URL de seu aplicativo
simples e bem definido. Use várias rotas em ações somente quando for necessário; por exemplo, para dar
suporte a clientes existentes.

Especificando parâmetros opcionais, valores padrão e restrições da rota de atributo


Rotas de atributo dão suporte à mesma sintaxe embutida que as rotas convencionais para especificar
parâmetros opcionais, valores padrão e restrições.

[HttpPost("product/{id:int}")]
public IActionResult ShowProduct(int id)
{
// ...
}

Consulte Referência de modelo de rota para obter uma descrição detalhada da sintaxe do modelo de
rota.
Atributos de rota personalizados usando IRouteTemplateProvider

Todos os atributos de rota fornecidos na estrutura ( [Route(...)] , [HttpGet(...)] etc.) implementam a


interface IRouteTemplateProvider . O MVC procura atributos em classes de controlador e métodos de
ação quando o aplicativo é iniciado e usa aqueles que implementam IRouteTemplateProvider para criar
o conjunto inicial de rotas.
Você pode implementar IRouteTemplateProvider para definir seus próprios atributos de rota. Cada
IRouteTemplateProvider permite definir uma única rota com um nome, uma ordem e um modelo de
rota personalizado:

public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider


{
public string Template => "api/[controller]";

public int? Order { get; set; }

public string Name { get; set; }


}

O atributo do exemplo acima configura automaticamente o Template como "api/[controller]"


quando [MyApiController] é aplicado.
Usando o Modelo de Aplicativo para personalizar rotas de atributo
O modelo de aplicativo é um modelo de objeto criado durante a inicialização com todos os metadados
usados pelo MVC para rotear e executar suas ações. O modelo de aplicativo inclui todos os dados
reunidos dos atributos de rota (por meio de IRouteTemplateProvider ). Você pode escrever convenções
para modificar o modelo do aplicativo no momento da inicialização para personalizar o comportamento
do roteamento. Esta seção mostra um exemplo simples de personalização de roteamento usando o
modelo de aplicativo.
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System.Linq;
using System.Text;
public class NamespaceRoutingConvention : IControllerModelConvention
{
private readonly string _baseNamespace;

public NamespaceRoutingConvention(string baseNamespace)


{
_baseNamespace = baseNamespace;
}

public void Apply(ControllerModel controller)


{
var hasRouteAttributes = controller.Selectors.Any(selector =>
selector.AttributeRouteModel != null);
if (hasRouteAttributes)
{
// This controller manually defined some routes, so treat this
// as an override and not apply the convention here.
return;
}

// Use the namespace and controller name to infer a route for the controller.
//
// Example:
//
// controller.ControllerTypeInfo -> "My.Application.Admin.UsersController"
// baseNamespace -> "My.Application"
//
// template => "Admin/[controller]"
//
// This makes your routes roughly line up with the folder structure of your project.
//
var namespc = controller.ControllerType.Namespace;

var template = new StringBuilder();


template.Append(namespc, _baseNamespace.Length + 1,
namespc.Length - _baseNamespace.Length - 1);
template.Replace('.', '/');
template.Append("/[controller]");

foreach (var selector in controller.Selectors)


{
selector.AttributeRouteModel = new AttributeRouteModel()
{
Template = template.ToString()
};
}
}
}

Roteamento misto: roteamento de atributo versus roteamento


convencional
Aplicativos MVC podem combinar o uso do roteamento convencional e do roteamento de atributo. É
comum usar rotas convencionais para controladores que servem páginas HTML para navegadores e
usar o roteamento de atributo para controladores que servem APIs REST.
As ações são roteadas convencionalmente ou segundo os atributos. Colocar uma rota no controlador
ou na ação faz com que ela seja roteada segundo o atributo. Ações que definem rotas de atributo não
podem ser acessadas por meio das rotas convencionais e vice-versa. Qualquer atributo de rota no
controlador faz com que todas as ações no atributo de controlador sejam roteadas.
OBSERVAÇÃO
O que diferencia os dois tipos de sistemas de roteamento é o processo aplicado após uma URL corresponder a
um modelo de rota. No roteamento convencional, os valores de rota da correspondência são usados para
escolher a ação e o controlador em uma tabela de pesquisa com todas as ações roteadas convencionais. No
roteamento de atributo, cada modelo já está associado a uma ação e nenhuma pesquisa adicional é necessária.

Geração de URL
Aplicativos MVC podem usar os recursos de geração de URL do roteamento para gerar links de URL
para ações. Gerar URLs elimina a necessidade de codificar URLs, tornando seu código mais robusto e
sustentável. Esta seção tem como foco os recursos de geração de URL fornecidos pelo MVC e só
aborda as noções básicas de como a geração de URL funciona. Consulte Roteamento para obter uma
descrição detalhada da geração de URL.
A interface IUrlHelper é a parte subjacente da infraestrutura entre o MVC e o roteamento para
geração de URL. Você encontrará uma instância de IUrlHelper disponível por meio da propriedade
Url em controladores, exibições e componentes de exibição.

Neste exemplo, a interface IUrlHelper é usada por meio a propriedade Controller.Url para gerar uma
URL para outra ação.

using Microsoft.AspNetCore.Mvc;

public class UrlGenerationController : Controller


{
public IActionResult Source()
{
// Generates /UrlGeneration/Destination
var url = Url.Action("Destination");
return Content($"Go check out {url}, it's really great.");
}

public IActionResult Destination()


{
return View();
}
}

Se o aplicativo estiver usando a rota convencional padrão, o valor da variável url será a cadeia de
caracteres do caminho de URL /UrlGeneration/Destination . Esse caminho de URL é criado pelo
roteamento combinando os valores de rota da solicitação atual (valores de ambiente) com os valores
passados para Url.Action e substituindo esses valores no modelo de rota:

ambient values: { controller = "UrlGeneration", action = "Source" }


values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}

result: /UrlGeneration/Destination

Cada parâmetro de rota no modelo de rota tem seu valor substituído por nomes correspondentes com
os valores e os valores de ambiente. Um parâmetro de rota que não tem um valor pode usar um valor
padrão se houver um ou pode ser ignorado se for opcional (como no caso de id neste exemplo). A
geração de URL falhará se qualquer parâmetro de rota obrigatório não tiver um valor correspondente.
Se a geração de URL falhar para uma rota, a rota seguinte será tentada até que todas as rotas tenham
sido tentadas ou que uma correspondência seja encontrada.
O exemplo de Url.Action acima pressupõe que o roteamento seja convencional, mas a geração de
URL funciona de forma semelhante com o roteamento de atributo, embora os conceitos sejam
diferentes. Com o roteamento convencional, os valores de rota são usados para expandir um modelo e
os valores de rota para controller e action normalmente são exibidos no modelo – isso funciona
porque as URLs correspondidas pelo roteamento aderem a uma convenção. No roteamento de
atributo, os valores de rota para controller e action não podem ser exibidos no modelo; em vez
disso, eles são usados para pesquisar o modelo a ser usado.
Este exemplo usa o roteamento de atributo:

// In Startup class
public void Configure(IApplicationBuilder app)
{
app.UseMvc();
}

using Microsoft.AspNetCore.Mvc;

public class UrlGenerationController : Controller


{
[HttpGet("")]
public IActionResult Source()
{
var url = Url.Action("Destination"); // Generates /custom/url/to/destination
return Content($"Go check out {url}, it's really great.");
}

[HttpGet("custom/url/to/destination")]
public IActionResult Destination() {
return View();
}
}

O MVC cria uma tabela de pesquisa de todas as ações de atributo roteadas e faz a correspondência dos
valores de controller e action para selecionar o modelo de rota a ser usado para geração de URL.
Na amostra acima, custom/url/to/destination é gerado.
Gerando URLs pelo nome da ação
Url.Action ( IUrlHelper . Action ) e todas as sobrecargas relacionadas são baseadas na ideia de que
você deseja especificar ao que está vinculando, especificando um nome do controlador e um nome da
ação.

OBSERVAÇÃO
Ao usar Url.Action , os valores de rota atuais para controller e action são especificados para você – o
valor de controller e action fazem parte de valores de ambiente e de valores. O método Url.Action
sempre usa os valores atuais de action e controller e gera um caminho de URL que roteia para a ação
atual.

O roteamento tenta usar os valores em valores de ambiente para preencher informações que você não
forneceu ao gerar uma URL. Usando uma rota como {a}/{b}/{c}/{d} e valores de ambiente
{ a = Alice, b = Bob, c = Carol, d = David } , o roteamento tem informações suficientes para gerar
uma URL sem valores adicionais – uma vez que todos os parâmetros de rota têm um valor. Se você
tiver adicionado o valor { d = Donovan } , o valor { d = David } será ignorado e o caminho de URL
gerado será Alice/Bob/Carol/Donovan .
AVISO
Caminhos de URL são hierárquicos. No exemplo acima, se você tiver adicionado o valor { c = Cheryl } ,
ambos os valores { c = Carol, d = David } serão ignorados. Nesse caso, não teremos mais um valor para
d e a geração de URL falhará. Você precisaria especificar o valor desejado de c e d . Você pode esperar se
deparar com esse problema com a rota padrão ( {controller}/{action}/{id?} ) – mas raramente encontrará
esse comportamento na prática, pois Url.Action sempre especificará explicitamente um valor de controller
e action .

Sobrecargas maiores de Url.Action também usam um objeto adicional de valores de rota para
fornecer valores para parâmetros de rota diferentes de controller e action . É mais comum ver isso
com id como Url.Action("Buy", "Products", new { id = 17 }) . Por convenção, o objeto de valores de
rota geralmente é um objeto de tipo anônimo, mas também pode ser um IDictionary<> ou um objeto
.NET simples. Qualquer valor de rota adicional que não corresponder aos parâmetros de rota será
colocado na cadeia de caracteres de consulta.

using Microsoft.AspNetCore.Mvc;

public class TestController : Controller


{
public IActionResult Index()
{
// Generates /Products/Buy/17?color=red
var url = Url.Action("Buy", "Products", new { id = 17, color = "red" });
return Content(url);
}
}

DICA
Para criar uma URL absoluta, use uma sobrecarga que aceita um protocol :
Url.Action("Buy", "Products", new { id = 17 }, protocol: Request.Scheme)

Gerando URLs pela rota


O código acima demonstrou a geração de uma URL passando o nome do controlador e da ação.
IUrlHelper também fornece a família de métodos Url.RouteUrl . Esses métodos são semelhantes a
Url.Action , mas não copiam os valores atuais de action e controller para os valores de rota. O uso
mais comum é especificar um nome de rota para usar uma rota específica para gerar a URL,
geralmente sem especificar um nome de controlador ou de ação.
using Microsoft.AspNetCore.Mvc;

public class UrlGenerationController : Controller


{
[HttpGet("")]
public IActionResult Source()
{
var url = Url.RouteUrl("Destination_Route"); // Generates /custom/url/to/destination
return Content($"See {url}, it's really great.");
}

[HttpGet("custom/url/to/destination", Name = "Destination_Route")]


public IActionResult Destination() {
return View();
}
}

Gerar URLs em HTML


IHtmlHelper fornece o métodos Html.BeginForm e Html.ActionLink de HtmlHelper para gerar
elementos <form> e <a> respectivamente. Esses métodos usam o método Url.Action para gerar uma
URL e aceitam argumentos semelhantes. O complementos Url.RouteUrl para HtmlHelper são
Html.BeginRouteForm e Html.RouteLink , que têm uma funcionalidade semelhante.

TagHelpers geram URLs por meio do TagHelper form e do TagHelper <a> . Ambos usam IUrlHelper
para sua implementação. Consulte Trabalhando com Formulários para obter mais informações.
Nos modos de exibição, o IUrlHelper está disponível por meio da propriedade Url para qualquer
geração de URL ad hoc não abordada acima.
Gerando URLS nos resultados da ação
Os exemplos acima mostraram o uso de IUrlHelper em um controlador, enquanto o uso mais comum
em um controlador é gerar uma URL como parte do resultado de uma ação.
As classes base ControllerBase e Controller fornecem métodos de conveniência para resultados de
ação que fazem referência a outra ação. Um uso típico é para redirecionar após aceitar a entrada do
usuário.

public Task<IActionResult> Edit(int id, Customer customer)


{
if (ModelState.IsValid)
{
// Update DB with new details.
return RedirectToAction("Index");
}
}

Os métodos de fábrica dos resultados da ação seguem um padrão semelhante aos métodos em
IUrlHelper .

Caso especial para rotas convencionais dedicadas


O roteamento convencional pode usar um tipo especial de definição de rota chamado rota
convencional dedicada. No exemplo a seguir, a rota chamada blog é uma rota convencional dedicada.
app.UseMvc(routes =>
{
routes.MapRoute("blog", "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});

Usando essas definições de rota, Url.Action("Index", "Home") gerará o caminho de URL / com a rota
default , mas por quê? Você poderia imaginar que os valores de rota
{ controller = Home, action = Index } seriam suficientes para gerar uma URL usando blog e o
resultado seria /blog?action=Index&controller=Home .
Rotas convencionais dedicadas dependem de um comportamento especial de valores padrão que não
têm um parâmetro de rota correspondente que impeça que a rota seja "muito ambiciosa" com a
geração de URLs. Nesse caso, os valores padrão são { controller = Blog, action = Article } e nem
controller ou action aparece como um parâmetro de rota. Quando o roteamento executa a geração
de URL, os valores fornecidos devem corresponder aos valores padrão. A geração de URL usando
blog falhará porque os valores de { controller = Home, action = Index } não correspondem a
{ controller = Blog, action = Article } . O roteamento, então, faz o fallback para tentar default , que
é bem-sucedido.

Áreas
Áreas são um recurso do MVC usado para organizar funcionalidades relacionadas em um grupo como
um namespace de roteamento (para ações do controlador) e estrutura de pasta (para exibições)
separada. O uso de áreas permite que um aplicativo tenha vários controladores com o mesmo nome,
desde que tenham áreas diferentes. O uso de áreas cria uma hierarquia para fins de roteamento,
adicionando outro parâmetro de rota, area a controller e action . Esta seção aborda como o
roteamento interage com as áreas. Consulte Áreas para obter detalhes sobre como as áreas são usadas
com exibições.
O exemplo a seguir configura o MVC para usar a rota convencional padrão e uma rota de área para
uma área chamada Blog :

app.UseMvc(routes =>
{
routes.MapAreaRoute("blog_route", "Blog",
"Manage/{controller}/{action}/{id?}");
routes.MapRoute("default_route", "{controller}/{action}/{id?}");
});

Ao fazer a correspondência de um caminho de URL como /Manage/Users/AddUser , a primeira rota


produzirá os valores de rota { area = Blog, controller = Users, action = AddUser } . O valor de rota
area é produzido por um valor padrão para area . De fato, a rota criada por MapAreaRoute é
equivalente à seguinte:

app.UseMvc(routes =>
{
routes.MapRoute("blog_route", "Manage/{controller}/{action}/{id?}",
defaults: new { area = "Blog" }, constraints: new { area = "Blog" });
routes.MapRoute("default_route", "{controller}/{action}/{id?}");
});

MapAreaRoute cria uma rota usando um valor padrão e a restrição para area usando o nome da área
fornecido, nesse caso, Blog . O valor padrão garante que a rota sempre produza { area = Blog, ... } ,
a restrição requer o valor { area = Blog, ... } para geração de URL.

DICA
O roteamento convencional é dependente da ordem. De modo geral, rotas com áreas devem ser colocadas mais
no início na tabela de rotas, uma vez que são mais específicas que rotas sem uma área.

Usando o exemplo acima, os valores de rota corresponderiam à ação a seguir:

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
[Area("Blog")]
public class UsersController : Controller
{
public IActionResult AddUser()
{
return View();
}
}
}

O AreaAttribute é o que indica que um controlador faz parte de uma área; dizemos que esse
controlador está na área Blog . Controladores sem um atributo [Area] não são membros de nenhuma
área e não corresponderão quando o valor de rota area for fornecido pelo roteamento. No exemplo a
seguir, somente o primeiro controlador listado pode corresponder aos valores de rota
{ area = Blog, controller = Users, action = AddUser } .

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
[Area("Blog")]
public class UsersController : Controller
{
public IActionResult AddUser()
{
return View();
}
}
}

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace2
{
// Matches { area = Zebra, controller = Users, action = AddUser }
[Area("Zebra")]
public class UsersController : Controller
{
public IActionResult AddUser()
{
return View();
}
}
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace3
{
// Matches { area = string.Empty, controller = Users, action = AddUser }
// Matches { area = null, controller = Users, action = AddUser }
// Matches { controller = Users, action = AddUser }
public class UsersController : Controller
{
public IActionResult AddUser()
{
return View();

}
}
}

OBSERVAÇÃO
O namespace de cada controlador é mostrado aqui para fins de integridade – caso contrário, os controladores
teriam um conflito de nomenclatura e gerariam um erro do compilador. Namespaces de classe não têm efeito
sobre o roteamento do MVC.

Os primeiros dois controladores são membros de áreas e correspondem somente quando seus
respectivos nomes de área são fornecidos pelo valor de rota area . O terceiro controlador não é um
membro de nenhuma área e só pode corresponder quando nenhum valor para area for fornecido pelo
roteamento.

OBSERVAÇÃO
Em termos de não corresponder a nenhum valor, a ausência do valor de area é equivalente ao valor de area
ser nulo ou uma cadeia de caracteres vazia.

Ao executar uma ação dentro de uma área, o valor de rota para area estará disponível como um valor
de ambiente para o roteamento usar para geração de URL. Isso significa que, por padrão, as áreas
atuam como se fossem autoadesivas para a geração de URL, como demonstrado no exemplo a seguir.

app.UseMvc(routes =>
{
routes.MapAreaRoute("duck_route", "Duck",
"Manage/{controller}/{action}/{id?}");
routes.MapRoute("default", "Manage/{controller=Home}/{action=Index}/{id?}");
});
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace4
{
[Area("Duck")]
public class UsersController : Controller
{
public IActionResult GenerateURLInArea()
{
// Uses the 'ambient' value of area
var url = Url.Action("Index", "Home");
// returns /Manage
return Content(url);
}

public IActionResult GenerateURLOutsideOfArea()


{
// Uses the empty value for area
var url = Url.Action("Index", "Home", new { area = "" });
// returns /Manage/Home/Index
return Content(url);
}
}
}

Entendendo IActionConstraint
OBSERVAÇÃO
Esta seção é uma análise aprofundada dos elementos internos da estrutura e de como o MVC escolhe uma ação
para ser executada. Um aplicativo típico não precisará de um IActionConstraint personalizado

Provavelmente, você já usou IActionConstraint mesmo que não esteja familiarizado com a interface. O
atributo [HttpGet] e atributos [Http-VERB] semelhantes implementam IActionConstraint para limitar
a execução de um método de ação.

public class ProductsController : Controller


{
[HttpGet]
public IActionResult Edit() { }

public IActionResult Edit(...) { }


}

Presumindo a rota convencional padrão, o caminho de URL /Products/Edit produziria os valores


{ controller = Products, action = Edit } , que corresponderiam a ambas as ações mostradas aqui. Na
terminologia IActionConstraint , diríamos que essas duas ações são consideradas candidatas – uma vez
que ambas correspondem aos dados da rota.
Quando for executado, HttpGetAttribute indicará que Edit() corresponde a GET e não corresponde a
nenhum outro verbo HTTP. A ação Edit(...) não tem restrições definidas e, portanto, corresponderá a
qualquer verbo HTTP. Sendo assim, supondo um POST , Edit(...) será correspondente. Mas, para um
GET , ambas as ações ainda podem corresponder – no entanto, uma ação com um IActionConstraint
sempre é considerada melhor que uma ação sem. Assim, como Edit() tem [HttpGet] , ela é
considerada mais específica e será selecionada se as duas ações puderem corresponder.
Conceitualmente, IActionConstraint é uma forma de sobrecarga, mas em vez de uma sobrecarga de
métodos com o mesmo nome, trata-se da sobrecarga entre ações que correspondem à mesma URL. O
roteamento de atributo também usa IActionConstraint e pode fazer com que ações de controladores
diferentes sejam consideradas candidatas.
Implementando IActionConstraint
A maneira mais simples de implementar um IActionConstraint é criar uma classe derivada de
System.Attribute e colocá-la em suas ações e controladores. O MVC descobrirá automaticamente
qualquer IActionConstraint que for aplicado como atributo. Você pode usar o modelo de aplicativo
para aplicar restrições e, provavelmente, essa é a abordagem mais flexível, pois permite a você faça uma
metaprogramação de como elas são aplicadas.
No exemplo a seguir, uma restrição escolhe uma ação com base em um código de país dos dados de
rota. O exemplo completo no GitHub.

public class CountrySpecificAttribute : Attribute, IActionConstraint


{
private readonly string _countryCode;

public CountrySpecificAttribute(string countryCode)


{
_countryCode = countryCode;
}

public int Order


{
get
{
return 0;
}
}

public bool Accept(ActionConstraintContext context)


{
return string.Equals(
context.RouteContext.RouteData.Values["country"].ToString(),
_countryCode,
StringComparison.OrdinalIgnoreCase);
}
}

Você é responsável por implementar o método Accept e por escolher uma "ordem" na qual a restrição
deve ser executada. Nesse caso, o método Accept retorna true para indicar que a ação é
correspondente quando o valor de rota country é correspondente. Isso é diferente de um
RouteValueAttribute , pois permite o fallback para uma ação não atribuída. O exemplo mostra que se
você definir uma ação en-US , um código de país como fr-FR fará o fallback para um controlador mais
genérico que não tem [CountrySpecific(...)] aplicado.
A propriedade Order decide de qual estágio a restrição faz parte. Restrições de ação são executadas em
grupos com base no Order . Por exemplo, todos atributos de método HTTP fornecidos pela estrutura
usam o mesmo valor de Order para que sejam executados no mesmo estágio. Você pode ter tantos
estágios quantos forem necessários para implementar suas políticas desejadas.

DICA
Para decidir o valor para Order , considere se sua restrição deve ou não deve ser aplicada antes de métodos
HTTP. Números inferiores são executados primeiro.
Uploads de arquivos no ASP.NET Core
08/05/2018 • 13 min to read • Edit Online

Por Steve Smith


Ações do ASP.NET MVC dão suporte ao upload de um ou mais arquivos usando associação de modelos simples
para arquivos menores ou streaming para arquivos maiores.
Exibir ou baixar a amostra do GitHub

Upload de arquivos pequenos com a associação de modelos


Para carregar arquivos pequenos, você pode usar um formulário HTML com várias partes ou construir uma
solicitação POST usando JavaScript. Um formulário de exemplo usando Razor, que dá suporte a vários arquivos
carregados, é mostrado abaixo:

<form method="post" enctype="multipart/form-data" asp-controller="UploadFiles" asp-action="Index">


<div class="form-group">
<div class="col-md-10">
<p>Upload one or more files using this form:</p>
<input type="file" name="files" multiple />
</div>
</div>
<div class="form-group">
<div class="col-md-10">
<input type="submit" value="Upload" />
</div>
</div>
</form>

Para dar suporte a uploads de arquivos, os formulários HTML devem especificar um enctype igual a
multipart/form-data . O elemento de entrada files mostrado acima dá suporte ao upload de vários arquivos.
Omita o atributo multiple neste elemento de entrada para permitir o upload de apenas um arquivo. A marcação
acima é renderizada em um navegador como:

Os arquivos individuais carregados no servidor podem ser acessados por meio da Associação de modelos usando
a interface IFormFile. IFormFile tem esta estrutura:
public interface IFormFile
{
string ContentType { get; }
string ContentDisposition { get; }
IHeaderDictionary Headers { get; }
long Length { get; }
string Name { get; }
string FileName { get; }
Stream OpenReadStream();
void CopyTo(Stream target);
Task CopyToAsync(Stream target, CancellationToken cancellationToken = null);
}

AVISO
Não dependa ou confie na propriedade FileName sem validação. A propriedade FileName deve ser usada somente para
fins de exibição.

Ao fazer upload de arquivos usando associação de modelos e a interface IFormFile , o método de ação pode
aceitar um único IFormFile ou um IEnumerable<IFormFile> (ou List<IFormFile> ) que representa vários arquivos.
O exemplo a seguir executa um loop em um ou mais arquivos carregados, salva-os no sistema de arquivos local e
retorna o número total e o tamanho dos arquivos carregados.
Aviso: O código a seguir usa GetTempFileName , que gera um IOException se mais de 65.535 arquivos são criados
sem excluir os arquivos temporários anteriores. Um aplicativo real deverá excluir arquivos temporários ou use
GetTempPath e GetRandomFileName para criar nomes de arquivo temporário. O limite de 65535 arquivos é por
servidor, para que possa usar outro aplicativo no servidor de backup de todos os arquivos de 65535.

[HttpPost("UploadFiles")]
public async Task<IActionResult> Post(List<IFormFile> files)
{
long size = files.Sum(f => f.Length);

// full path to file in temp location


var filePath = Path.GetTempFileName();

foreach (var formFile in files)


{
if (formFile.Length > 0)
{
using (var stream = new FileStream(filePath, FileMode.Create))
{
await formFile.CopyToAsync(stream);
}
}
}

// process uploaded files


// Don't rely on or trust the FileName property without validation.

return Ok(new { count = files.Count, size, filePath});


}

Arquivos carregados usando a técnica IFormFile são armazenados em buffer na memória ou no disco no servidor
Web antes de serem processados. Dentro do método de ação, o conteúdo de IFormFile podem ser acessado
como um fluxo. Além do sistema de arquivos local, os arquivos podem ser transmitidos para o Armazenamento de
Blobs do Azure ou para o Entity Framework.
Para armazenar dados de arquivo binário em um banco de dados usando o Entity Framework, defina uma
propriedade do tipo byte[] na entidade:

public class ApplicationUser : IdentityUser


{
public byte[] AvatarImage { get; set; }
}

Especifique uma propriedade de viewmodel do tipo IFormFile :

public class RegisterViewModel


{
// other properties omitted

public IFormFile AvatarImage { get; set; }


}

OBSERVAÇÃO
IFormFile pode ser usado diretamente como um parâmetro de método de ação ou como uma propriedade de viewmodel,
como mostrado acima.

Copie o IFormFile para um fluxo e salve-o na matriz de bytes:

// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser {
UserName = model.Email,
Email = model.Email
};
using (var memoryStream = new MemoryStream())
{
await model.AvatarImage.CopyToAsync(memoryStream);
user.AvatarImage = memoryStream.ToArray();
}
// additional logic omitted

// Don't rely on or trust the model.AvatarImage.FileName property


// without validation.
}

OBSERVAÇÃO
Tenha cuidado ao armazenar dados binários em bancos de dados relacionais, pois isso pode afetar negativamente o
desempenho.

Upload de arquivos grandes usando streaming


Se o tamanho ou a frequência dos uploads de arquivos estiver causando problemas de recursos para o aplicativo,
considere transmitir o upload dos arquivos por streaming em vez de armazená-los completamente em buffer,
como na abordagem de associação de modelos mostrada acima. Embora o uso de IFormFile e da associação de
modelos seja uma solução muito mais simples, o streaming requer a execução de algumas etapas para ser
implementado corretamente.

OBSERVAÇÃO
Qualquer arquivo armazenado em buffer que exceder 64KB será movido do RAM para um arquivo temporário em disco no
servidor. Os recursos (disco, RAM) usados pelos uploads de arquivos dependem do número e do tamanho dos uploads de
arquivos simultâneos. Streaming não é tanto uma questão de desempenho, e sim de escala. Se você tentar armazenar muitos
uploads em buffer, seu site falhará quando ficar sem memória ou sem espaço em disco.

O exemplo a seguir demonstra como usar JavaScript/Angular para fazer o streaming para uma ação do
controlador. O token antifalsificação do arquivo é gerado usando um atributo de filtro personalizado e passada nos
cabeçalhos HTTP em vez do corpo da solicitação. Como um método de ação processa os dados carregados
diretamente, a associação de modelos é desabilitada por outro filtro. Dentro da ação, o conteúdo do formulário é
lido usando um MultipartReader , que lê cada MultipartSection individual, processando o arquivo ou
armazenando o conteúdo conforme apropriado. Após todas as seções serem lidas, a ação executa sua própria
associação de modelos.
A ação inicial carrega o formulário e salva um token antifalsificação em um cookie (por meio do atributo
GenerateAntiforgeryTokenCookieForAjax ):

[HttpGet]
[GenerateAntiforgeryTokenCookieForAjax]
public IActionResult Index()
{
return View();
}

O atributo usa o suporte interno Antifalsificação do ASP.NET Core para definir um cookie com um token de
solicitação:

public class GenerateAntiforgeryTokenCookieForAjaxAttribute : ActionFilterAttribute


{
public override void OnActionExecuted(ActionExecutedContext context)
{
var antiforgery = context.HttpContext.RequestServices.GetService<IAntiforgery>();

// We can send the request token as a JavaScript-readable cookie,


// and Angular will use it by default.
var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);
context.HttpContext.Response.Cookies.Append(
"XSRF-TOKEN",
tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
}

O Angular passa automaticamente um token antifalsificação em um cabeçalho de solicitação chamado


X-XSRF-TOKEN . O aplicativo ASP.NET Core MVC é configurado para se referir a esse cabeçalho em sua
configuração em Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// Angular's default header name for sending the XSRF token.
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");

services.AddMvc();
}

O atributo DisableFormValueModelBinding , mostrado abaixo, é usado para desabilitar a associação de modelos para
o método de ação Upload .

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}

public void OnResourceExecuted(ResourceExecutedContext context)


{
}
}

Como a associação de modelos é desabilitada, o método de ação Upload não aceita parâmetros. Ele trabalha
diretamente com a propriedade Request de ControllerBase . Um MultipartReader é usado para ler cada seção. O
arquivo é salvo com um nome de arquivo GUID e os dados de chave/valor são armazenados em um
KeyValueAccumulator . Após todas as seções terem sido lidas, o conteúdo do KeyValueAccumulator é usado para
associar os dados do formulário a um tipo de modelo.
O método Upload completo é mostrado abaixo:
Aviso: O código a seguir usa GetTempFileName , que gera um IOException se mais de 65.535 arquivos são criados
sem excluir os arquivos temporários anteriores. Um aplicativo real deverá excluir arquivos temporários ou use
GetTempPath e GetRandomFileName para criar nomes de arquivo temporário. O limite de 65535 arquivos é por
servidor, para que possa usar outro aplicativo no servidor de backup de todos os arquivos de 65535.

// 1. Disable the form value model binding here to take control of handling
// potentially large files.
// 2. Typically antiforgery tokens are sent in request body, but since we
// do not want to read the request body early, the tokens are made to be
// sent via headers. The antiforgery token filter first looks for tokens
// in the request header and then falls back to reading the body.
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Upload()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
return BadRequest($"Expected a multipart request, but got {Request.ContentType}");
}

// Used to accumulate all the form url encoded key value pairs in the
// request.
var formAccumulator = new KeyValueAccumulator();
string targetFilePath = null;

var boundary = MultipartRequestHelper.GetBoundary(


MediaTypeHeaderValue.Parse(Request.ContentType),
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);

var section = await reader.ReadNextSectionAsync();


while (section != null)
{
ContentDispositionHeaderValue contentDisposition;
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition,
out contentDisposition);

if (hasContentDispositionHeader)
{
if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
{
targetFilePath = Path.GetTempFileName();
using (var targetStream = System.IO.File.Create(targetFilePath))
{
await section.Body.CopyToAsync(targetStream);

_logger.LogInformation($"Copied the uploaded file '{targetFilePath}'");


}
}
else if (MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition))
{
// Content-Disposition: form-data; name="key"
//
// value

// Do not limit the key name length here because the


// multipart headers length limit is already in effect.
var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name);
var encoding = GetEncoding(section);
using (var streamReader = new StreamReader(
section.Body,
encoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true))
{
// The value length limit is enforced by MultipartBodyLengthLimit
var value = await streamReader.ReadToEndAsync();
if (String.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
{
value = String.Empty;
}
formAccumulator.Append(key, value);

if (formAccumulator.ValueCount > _defaultFormOptions.ValueCountLimit)


{
throw new InvalidDataException($"Form key count limit
{_defaultFormOptions.ValueCountLimit} exceeded.");
}
}
}
}

// Drains any remaining section body that has not been consumed and
// reads the headers for the next section.
section = await reader.ReadNextSectionAsync();
}

// Bind form data to a model


var user = new User();
var formValueProvider = new FormValueProvider(
BindingSource.Form,
new FormCollection(formAccumulator.GetResults()),
CultureInfo.CurrentCulture);
var bindingSuccessful = await TryUpdateModelAsync(user, prefix: "",
valueProvider: formValueProvider);
if (!bindingSuccessful)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
}

var uploadedData = new UploadedData()


{
Name = user.Name,
Age = user.Age,
Zipcode = user.Zipcode,
FilePath = targetFilePath
};
return Json(uploadedData);
}

Solução de problemas
Abaixo, são listados alguns problemas comuns encontrados ao trabalhar com o upload de arquivos e suas
possíveis soluções.
Erro não encontrado inesperado com o IIS
O erro a seguir indica que o upload do arquivo excede o maxAllowedContentLength configurado do servidor:

HTTP 404.13 - Not Found


The request filtering module is configured to deny a request that exceeds the request content length.

A configuração padrão é 30000000 , que é aproximadamente 28,6 MB. O valor pode ser personalizado editando
web.config:

<system.webServer>
<security>
<requestFiltering>
<!-- This will handle requests up to 50MB -->
<requestLimits maxAllowedContentLength="52428800" />
</requestFiltering>
</security>
</system.webServer>

Essa configuração só se aplica ao IIS. Esse comportamento não ocorre por padrão quando a hospedagem é feita
no Kestrel. Para obter mais informações, consulte Limites de solicitação <requestLimits>.
Exceção de referência nula com IFormFile
Se o controlador estiver aceitando arquivos carregados usando IFormFile , mas você observar que o valor sempre
é nulo, confirme que seu formulário HTML está especificando um valor de enctype igual a multipart/form-data .
Se esse atributo não estiver definido no elemento <form> , o upload do arquivo não ocorrerá e os argumentos
IFormFile associados serão nulos.
Injeção de dependência em controladores
08/02/2018 • 9 min to read • Edit Online

Por Steve Smith


Controladores do ASP.NET Core MVC devem solicitar suas dependências explicitamente por meio de seus
construtores. Em algumas instâncias, ações individuais do controlador podem exigir um serviço e pode não fazer
sentido solicitá-lo no nível do controlador. Nesse caso, você também pode optar por injetar um serviço como um
parâmetro no método de ação.
Exibir ou baixar código de exemplo (como baixar)

Injeção de dependência
A injeção de dependência é uma técnica que segue o Princípio de inversão de dependência, permitindo que
aplicativos sejam compostos por módulos acoplados de forma flexível. O ASP.NET Core tem suporte interno para
a injeção de dependência, o que facilita a manutenção e o teste de aplicativos.

Injeção de construtor
O suporte interno do ASP.NET Core para injeção de dependência baseada no construtor se estende para
controladores MVC. Simplesmente adicionando um tipo de serviço ao seu controlador como um parâmetro de
construtor, o ASP.NET Core tentará resolver o tipo usando seu contêiner de serviço interno. Os serviços são
normalmente, mas não sempre, definidos usando interfaces. Por exemplo, se seu aplicativo tiver lógica de
negócios que depende da hora atual, você pode injetar um serviço que recupera a hora (em vez fazer o hard-
coding), o que permite que seus testes sejam passados em implementações que usam uma hora definida.

using System;

namespace ControllerDI.Interfaces
{
public interface IDateTime
{
DateTime Now { get; }
}
}

Implementar uma interface como esta para que ele use o relógio do sistema em tempo de execução é simples:

using System;
using ControllerDI.Interfaces;

namespace ControllerDI.Services
{
public class SystemDateTime : IDateTime
{
public DateTime Now
{
get { return DateTime.Now; }
}
}
}

Com isso em vigor, podemos usar o serviço em nosso controlador. Nesse caso, adicionamos alguma lógica para
ao método HomeController Index para exibir uma saudação ao usuário com base na hora do dia.

using ControllerDI.Interfaces;
using Microsoft.AspNetCore.Mvc;

namespace ControllerDI.Controllers
{
public class HomeController : Controller
{
private readonly IDateTime _dateTime;

public HomeController(IDateTime dateTime)


{
_dateTime = dateTime;
}

public IActionResult Index()


{
var serverTime = _dateTime.Now;
if (serverTime.Hour < 12)
{
ViewData["Message"] = "It's morning here - Good Morning!";
}
else if (serverTime.Hour < 17)
{
ViewData["Message"] = "It's afternoon here - Good Afternoon!";
}
else
{
ViewData["Message"] = "It's evening here - Good Evening!";
}
return View();
}
}
}

Se executarmos o aplicativo agora, provavelmente encontraremos um erro:

An unhandled exception occurred while processing the request.

InvalidOperationException: Unable to resolve service for type 'ControllerDI.Interfaces.IDateTime' while


attempting to activate 'ControllerDI.Controllers.HomeController'.
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type
requiredBy, Boolean isDefaultParameterRequired)

Esse erro ocorre quando não configuramos um serviço no método ConfigureServices em nossa classe Startup .
Para especificar que solicitações de IDateTime devem ser resolvidas usando uma instância de SystemDateTime ,
adicione a linha realçada à lista abaixo para seu método ConfigureServices :

public void ConfigureServices(IServiceCollection services)


{
// Add application services.
services.AddTransient<IDateTime, SystemDateTime>();
}

OBSERVAÇÃO
Esse serviço específico poderia ser implementado usando qualquer uma das várias opções diferentes de tempo de vida (
Transient , Scoped ou Singleton ). Consulte Injeção de dependência para entender como cada uma dessas opções de
escopo afetará o comportamento de seu serviço.
Depois que o serviço tiver sido configurado, executar o aplicativo e navegar para a home page deve exibir a
mensagem baseada em hora conforme o esperado:

DICA
Consulte Lógica do Test Controller para saber como solicitar dependências explicitamente http://deviq.com/explicit-
dependencies-principle/ nos controladores facilita a realização de testes no código.

A injeção de dependência interna do ASP.NET Core dá suporte a apenas um construtor para classes que solicitam
serviços. Se tiver mais de um construtor, você poderá receber uma exceção informando:

An unhandled exception occurred while processing the request.

InvalidOperationException: Multiple constructors accepting all given argument types have been found in type
'ControllerDI.Controllers.HomeController'. There should only be one applicable constructor.
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.FindApplicableConstructor(Type instanceType,
Type[] argumentTypes, ConstructorInfo& matchingConstructor, Nullable`1[]& parameterMap)

Como a mensagem de erro afirma, você pode corrigir esse problema tendo apenas um único construtor. Você
também pode substituir o suporte para injeção de dependência padrão por uma implementação de terceiros,
muitas das quais dão suporte a vários construtores.

Injeção de ação com FromServices


Às vezes, você não precisa de um serviço para mais de uma ação em seu controlador. Nesse caso, talvez faça
sentido injetar o serviço como um parâmetro do método de ação. Isso é feito marcando o parâmetro com o
atributo [FromServices] , conforme mostrado aqui:

public IActionResult About([FromServices] IDateTime dateTime)


{
ViewData["Message"] = "Currently on the server the time is " + dateTime.Now;

return View();
}

Acessando configurações de um controlador


Acessar definições de configuração ou do aplicativo de dentro de um controlador é um padrão comum. Esse
acesso deve usar o padrão Opções descrito na configuração. Geralmente, você não deve solicitar configurações
diretamente de seu controlador usando a injeção de dependência. Uma abordagem melhor é solicitar uma
instância de IOptions<T> , em que T é a classe de configuração de que você precisa.
Para trabalhar com o padrão de opções, você precisa criar uma classe que representa as opções, como esta:
namespace ControllerDI.Model
{
public class SampleWebSettings
{
public string Title { get; set; }
public int Updates { get; set; }
}
}

Em seguida, você precisa configurar o aplicativo para usar o modelo de opções e adicionar sua classe de
configuração à coleção de serviços em ConfigureServices :

public Startup(IHostingEnvironment env)


{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("samplewebsettings.json");
Configuration = builder.Build();
}

public IConfigurationRoot Configuration { get; set; }

// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?
LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
// Required to use the Options<T> pattern
services.AddOptions();

// Add settings from configuration


services.Configure<SampleWebSettings>(Configuration);

// Uncomment to add settings from code


//services.Configure<SampleWebSettings>(settings =>
//{
// settings.Updates = 17;
//});

services.AddMvc();

// Add application services.


services.AddTransient<IDateTime, SystemDateTime>();
}

OBSERVAÇÃO
Na lista acima, estamos configurando o aplicativo para ler as configurações de um arquivo no formato JSON. Você também
pode definir as configurações inteiramente no código, como é mostrado no código comentado acima. Consulte
Configuração para ver outras opções de configuração.

Após ter especificado um objeto de configuração fortemente tipado (nesse caso, SampleWebSettings ) e tê-lo
adicionado à coleção de serviços, você pode solicitá-lo de qualquer método de Ação ou Controlador solicitando
uma instância de IOptions<T> (nesse caso, IOptions<SampleWebSettings> ). O código a seguir mostra como
alguém solicitaria as configurações de um controlador:
public class SettingsController : Controller
{
private readonly SampleWebSettings _settings;

public SettingsController(IOptions<SampleWebSettings> settingsOptions)


{
_settings = settingsOptions.Value;
}

public IActionResult Index()


{
ViewData["Title"] = _settings.Title;
ViewData["Updates"] = _settings.Updates;
return View();
}
}

Seguir o padrão Opções permite que as definições e configurações sejam dissociadas umas das outras e garante
que o controlador esteja seguindo a separação de preocupações, uma vez que ele não precisa saber como nem
onde encontrar as informações de configuração. Isso facilita a realização de teste de unidade no controlador
usando a Lógica do Test Controller, porque não há nenhuma adesão estática ou instanciação direta das classes de
configuração dentro da classe do controlador.
Testando a lógica do controlador no ASP.NET Core
08/02/2018 • 27 min to read • Edit Online

Por Steve Smith


Os controladores em aplicativos ASP.NET MVC devem ser pequenos e estar voltados para interesses da interface
do usuário. Controladores grandes que lidam com interesses não referentes à interface do usuário são mais difíceis
de serem testados e mantidos.
Exibir ou baixar a amostra do GitHub

Testando os controladores
Os controladores são uma parte central de qualquer aplicativo ASP.NET Core MVC. Assim, você deve estar
confiante de que eles se comportam conforme o esperado para o aplicativo. Testes automatizados podem fornecer
essa confiança e podem detectar erros antes que eles atinjam a produção. É importante evitar colocar
responsabilidades desnecessárias dentro dos controladores e garantir que o teste tenha como foco somente as
responsabilidades do controlador.
A lógica do controlador deve ser mínima e não estar voltada para a lógica de negócios ou interesses de
infraestrutura (por exemplo, acesso a dados). Teste a lógica do controlador, não a estrutura. Teste como o
controlador se comporta de acordo com as entradas válidas ou inválidas. Teste as respostas do controlador de
acordo com o resultado da operação de negócios executada por ele.
Responsabilidades típicas do controlador:
Verificar ModelState.IsValid .
Retornar uma resposta de erro se ModelState for inválido.
Recuperar uma entidade de negócios da persistência.
Executar uma ação na entidade de negócios.
Salvar a entidade de negócios para persistência.
Retornar um IActionResult apropriado.

Teste de unidade
O teste de unidade envolve o teste de uma parte de um aplicativo de teste em isolamento de sua infraestrutura e
suas dependências. Ao executar o teste de unidade na lógica do controlador, somente o conteúdo de uma única
ação é testada, não o comportamento de suas dependências ou da própria estrutura. Ao executar o teste de
unidade nas ações do controlador, concentre-se apenas em seu comportamento. Um teste de unidade do
controlador evita itens como filtros, roteamento ou associação de modelos. Ao se concentrarem no teste de apenas
uma coisa, os testes de unidade geralmente são simples de serem escritos e rápidos de serem executados. Um
conjunto bem escrito de testes de unidade pode ser executado com frequência sem muita sobrecarga. No entanto,
os testes de unidade não detectam problemas na interação entre componentes, que é a finalidade dos testes de
integração.
Se você estiver escrevendo filtros personalizados, rotas, etc., execute o teste de unidade neles, mas não como parte
dos testes em uma ação específica do controlador. Eles devem ser testados em isolamento.
DICA
Crie e execute testes de unidade com o Visual Studio.

Para demonstrar o teste de unidade, examine o controlador a seguir. Ele exibe uma lista de sessões de debate e
permite que novas sessões de debate sejam criadas com um POST:
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using TestingControllersSample.ViewModels;

namespace TestingControllersSample.Controllers
{
public class HomeController : Controller
{
private readonly IBrainstormSessionRepository _sessionRepository;

public HomeController(IBrainstormSessionRepository sessionRepository)


{
_sessionRepository = sessionRepository;
}

public async Task<IActionResult> Index()


{
var sessionList = await _sessionRepository.ListAsync();

var model = sessionList.Select(session => new StormSessionViewModel()


{
Id = session.Id,
DateCreated = session.DateCreated,
Name = session.Name,
IdeaCount = session.Ideas.Count
});

return View(model);
}

public class NewSessionModel


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

[HttpPost]
public async Task<IActionResult> Index(NewSessionModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
else
{
await _sessionRepository.AddAsync(new BrainstormSession()
{
DateCreated = DateTimeOffset.Now,
Name = model.SessionName
});
}

return RedirectToAction(actionName: nameof(Index));


}
}
}

O controlador está seguindo o princípio de dependências explícitas, esperando receber da injeção de dependência
uma instância de IBrainstormSessionRepository . Isso facilita muito o teste com o uso de uma estrutura de objeto
fictício, como o Moq. O método HTTP GET Index não tem nenhum loop ou branch e chama apenas um método.
Para testar este método Index , precisamos verificar se um ViewResult for retornado, com um ViewModel do
método List do repositório.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Moq;
using TestingControllersSample.Controllers;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using TestingControllersSample.ViewModels;
using Xunit;

namespace TestingControllersSample.Tests.UnitTests
{
public class HomeControllerTests
{
[Fact]
public async Task Index_ReturnsAViewResult_WithAListOfBrainstormSessions()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.ListAsync()).Returns(Task.FromResult(GetTestSessions()));
var controller = new HomeController(mockRepo.Object);

// Act
var result = await controller.Index();

// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsAssignableFrom<IEnumerable<StormSessionViewModel>>(
viewResult.ViewData.Model);
Assert.Equal(2, model.Count());
}

private List<BrainstormSession> GetTestSessions()


{
var sessions = new List<BrainstormSession>();
sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 2),
Id = 1,
Name = "Test One"
});
sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 1),
Id = 2,
Name = "Test Two"
});
return sessions;
}
}
}

O método HomeController HTTP POST Index (mostrado acima) deve verificar se:
O método de ação retorna um ViewResult de Solicitação Inválida com os dados apropriados quando
ModelState.IsValid é false

O método Add no repositório é chamado e um RedirectToActionResult é retornado com os argumentos


corretos quando ModelState.IsValid é verdadeiro.
O estado de modelo inválido pode ser testado com a adição de erros usando AddModelError , conforme mostrado
no primeiro teste abaixo.

[Fact]
public async Task IndexPost_ReturnsBadRequestResult_WhenModelStateIsInvalid()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.ListAsync()).Returns(Task.FromResult(GetTestSessions()));
var controller = new HomeController(mockRepo.Object);
controller.ModelState.AddModelError("SessionName", "Required");
var newSession = new HomeController.NewSessionModel();

// Act
var result = await controller.Index(newSession);

// Assert
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
Assert.IsType<SerializableError>(badRequestResult.Value);
}

[Fact]
public async Task IndexPost_ReturnsARedirectAndAddsSession_WhenModelStateIsValid()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.AddAsync(It.IsAny<BrainstormSession>()))
.Returns(Task.CompletedTask)
.Verifiable();
var controller = new HomeController(mockRepo.Object);
var newSession = new HomeController.NewSessionModel()
{
SessionName = "Test Name"
};

// Act
var result = await controller.Index(newSession);

// Assert
var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
Assert.Null(redirectToActionResult.ControllerName);
Assert.Equal("Index", redirectToActionResult.ActionName);
mockRepo.Verify();
}

O primeiro teste confirma que quando ModelState não é válido, o mesmo ViewResult é retornado como para uma
solicitação GET . Observe que o teste não tenta passar um modelo inválido. Isso não funcionará de qualquer forma,
pois a associação de modelos não está em execução (embora um teste de integração use a associação de modelos
de exercícios). Nesse caso, a associação de modelos não está sendo testada. Esses testes de unidade estão testando
apenas o que o código faz no método de ação.
O segundo teste verifica que quando ModelState é válido, um novo BrainstormSession é adicionado (por meio do
repositório) e o método retorna um RedirectToActionResult com as propriedades esperadas. Chamadas fictícias
que não são chamadas são normalmente ignoradas, mas a chamada a Verifiable no final da chamada de
instalação permite que ele seja verificada no teste. Isso é feito com a chamada a mockRepo.Verify , que não será
aprovado no teste se o método esperado não tiver sido chamado.
OBSERVAÇÃO
A biblioteca do Moq usada nesta amostra facilita a combinação de simulações verificáveis ou "estritas" com simulações não
verificáveis (também chamadas de simulações "flexíveis" ou stubs). Saiba mais sobre como personalizar o comportamento de
Simulação com o Moq.

Outro controlador no aplicativo exibe informações relacionadas a uma sessão de debate específica. Ele inclui uma
lógica para lidar com valores de ID inválidos:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.ViewModels;

namespace TestingControllersSample.Controllers
{
public class SessionController : Controller
{
private readonly IBrainstormSessionRepository _sessionRepository;

public SessionController(IBrainstormSessionRepository sessionRepository)


{
_sessionRepository = sessionRepository;
}

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


{
if (!id.HasValue)
{
return RedirectToAction(actionName: nameof(Index), controllerName: "Home");
}

var session = await _sessionRepository.GetByIdAsync(id.Value);


if (session == null)
{
return Content("Session not found.");
}

var viewModel = new StormSessionViewModel()


{
DateCreated = session.DateCreated,
Name = session.Name,
Id = session.Id
};

return View(viewModel);
}
}
}

A ação do controlador tem três casos a serem testados, um para cada instrução return :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Moq;
using TestingControllersSample.Controllers;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using TestingControllersSample.ViewModels;
using Xunit;
namespace TestingControllersSample.Tests.UnitTests
{
public class SessionControllerTests
{
[Fact]
public async Task IndexReturnsARedirectToIndexHomeWhenIdIsNull()
{
// Arrange
var controller = new SessionController(sessionRepository: null);

// Act
var result = await controller.Index(id: null);

// Assert
var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
Assert.Equal("Home", redirectToActionResult.ControllerName);
Assert.Equal("Index", redirectToActionResult.ActionName);
}

[Fact]
public async Task IndexReturnsContentWithSessionNotFoundWhenSessionNotFound()
{
// Arrange
int testSessionId = 1;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.Returns(Task.FromResult((BrainstormSession)null));
var controller = new SessionController(mockRepo.Object);

// Act
var result = await controller.Index(testSessionId);

// Assert
var contentResult = Assert.IsType<ContentResult>(result);
Assert.Equal("Session not found.", contentResult.Content);
}

[Fact]
public async Task IndexReturnsViewResultWithStormSessionViewModel()
{
// Arrange
int testSessionId = 1;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.Returns(Task.FromResult(GetTestSessions().FirstOrDefault(s => s.Id == testSessionId)));
var controller = new SessionController(mockRepo.Object);

// Act
var result = await controller.Index(testSessionId);

// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsType<StormSessionViewModel>(viewResult.ViewData.Model);
Assert.Equal("Test One", model.Name);
Assert.Equal(2, model.DateCreated.Day);
Assert.Equal(testSessionId, model.Id);
}

private List<BrainstormSession> GetTestSessions()


{
var sessions = new List<BrainstormSession>();
sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 2),
Id = 1,
Name = "Test One"
});
sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 1),
Id = 2,
Name = "Test Two"
});
return sessions;
}
}
}

O aplicativo expõe a funcionalidade como uma API Web (uma lista de ideias associadas a uma sessão de debate e
um método para adicionar novas ideias a uma sessão):

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using TestingControllersSample.ClientModels;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;

namespace TestingControllersSample.Api
{
[Route("api/ideas")]
public class IdeasController : Controller
{
private readonly IBrainstormSessionRepository _sessionRepository;

public IdeasController(IBrainstormSessionRepository sessionRepository)


{
_sessionRepository = sessionRepository;
}

[HttpGet("forsession/{sessionId}")]
public async Task<IActionResult> ForSession(int sessionId)
{
var session = await _sessionRepository.GetByIdAsync(sessionId);
if (session == null)
{
return NotFound(sessionId);
}

var result = session.Ideas.Select(idea => new IdeaDTO()


{
Id = idea.Id,
Name = idea.Name,
Description = idea.Description,
DateCreated = idea.DateCreated
}).ToList();

return Ok(result);
}

[HttpPost("create")]
public async Task<IActionResult> Create([FromBody]NewIdeaModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

var session = await _sessionRepository.GetByIdAsync(model.SessionId);


if (session == null)
{
return NotFound(model.SessionId);
}
var idea = new Idea()
{
DateCreated = DateTimeOffset.Now,
Description = model.Description,
Name = model.Name
};
session.AddIdea(idea);

await _sessionRepository.UpdateAsync(session);

return Ok(session);
}
}
}

O método ForSession retorna uma lista de tipos IdeaDTO . Evite retornar as entidades de domínio de negócios
diretamente por meio de chamadas à API, pois, com frequência, elas incluem mais dados do que o cliente de API
exige e acoplam desnecessariamente o modelo de domínio interno do aplicativo à API exposta externamente. O
mapeamento entre entidades de domínio e os tipos que você retornará de forma eletrônica pode ser feito
manualmente (usando um LINQ Select , conforme mostrado aqui) ou uma biblioteca como o AutoMapper
Os testes de unidade para os métodos de API Create e ForSession :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Moq;
using TestingControllersSample.Api;
using TestingControllersSample.ClientModels;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using Xunit;

namespace TestingControllersSample.Tests.UnitTests
{
public class ApiIdeasControllerTests
{
[Fact]
public async Task Create_ReturnsBadRequest_GivenInvalidModel()
{
// Arrange & Act
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
controller.ModelState.AddModelError("error","some error");

// Act
var result = await controller.Create(model: null);

// Assert
Assert.IsType<BadRequestObjectResult>(result);
}

[Fact]
public async Task Create_ReturnsHttpNotFound_ForInvalidSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.Returns(Task.FromResult((BrainstormSession)null));
var controller = new IdeasController(mockRepo.Object);

// Act
var result = await controller.Create(new NewIdeaModel());
var result = await controller.Create(new NewIdeaModel());

// Assert
Assert.IsType<NotFoundObjectResult>(result);
}

[Fact]
public async Task Create_ReturnsNewlyCreatedIdeaForSession()
{
// Arrange
int testSessionId = 123;
string testName = "test name";
string testDescription = "test description";
var testSession = GetTestSession();
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.Returns(Task.FromResult(testSession));
var controller = new IdeasController(mockRepo.Object);

var newIdea = new NewIdeaModel()


{
Description = testDescription,
Name = testName,
SessionId = testSessionId
};
mockRepo.Setup(repo => repo.UpdateAsync(testSession))
.Returns(Task.CompletedTask)
.Verifiable();

// Act
var result = await controller.Create(newIdea);

// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var returnSession = Assert.IsType<BrainstormSession>(okResult.Value);
mockRepo.Verify();
Assert.Equal(2, returnSession.Ideas.Count());
Assert.Equal(testName, returnSession.Ideas.LastOrDefault().Name);
Assert.Equal(testDescription, returnSession.Ideas.LastOrDefault().Description);
}

private BrainstormSession GetTestSession()


{
var session = new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 2),
Id = 1,
Name = "Test One"
};

var idea = new Idea() { Name = "One" };


session.AddIdea(idea);
return session;
}
}
}

Conforme mencionado anteriormente, para testar o comportamento do método quando ModelState é inválido,
adicione um erro de modelo ao controlador como parte do teste. Não tente testar a validação ou a associação de
modelos nos testes de unidade – teste apenas o comportamento do método de ação quando houver um valor
ModelState específico.

O segundo teste depende do repositório retornar nulo. Portanto, o repositório fictício é configurado para retornar
nulo. Não é necessário criar um banco de dados de teste (em memória ou de outra forma) e construir uma consulta
que retornará esse resultado – isso pode ser feito em uma única instrução, conforme mostrado.
O último teste verifica se o método Update do repositório é chamado. Como fizemos anteriormente, a simulação é
chamada com Verifiable e, em seguida, o método Verify do repositório fictício é chamado para confirmar se o
método verificável foi executado. Não é responsabilidade do teste de unidade garantir que o método Update salva
os dados; isso pode ser feito com um teste de integração.

Teste de integração
O teste de integração é feito para garantir que módulos separados dentro do aplicativo funcionem corretamente
juntos. Em geral, qualquer coisa que você possa testar com um teste de unidade, também pode testar com um teste
de integração, mas o contrário não é verdadeiro. No entanto, os testes de integração tendem a ser muito mais
lentos do que os testes de unidade. Portanto, é melhor testar tudo o que você puder com testes de unidade e usar
testes de integração para cenários que envolvem vários colaboradores.
Embora eles ainda possam ser úteis, objetos fictícios raramente são usados em testes de integração. Em um teste
de unidade, objetos fictícios são uma maneira eficiente de controlar como os colaboradores fora da unidade que
está sendo testada devem se comportar para fins do teste. Em um teste de integração, colaboradores reais são
usados para confirmar que todo o subsistema funciona junto corretamente.
Estado do aplicativo
Uma consideração importante ao executar testes de integração é como configurar o estado do aplicativo. Os testes
precisam ser executados de forma independente entre si e, portanto, cada teste deve ser iniciado com o aplicativo
em um estado conhecido. Se o aplicativo não usa um banco de dados ou não tem nenhuma persistência, isso pode
não ser um problema. No entanto, a maioria dos aplicativos do mundo real persiste seu estado para algum tipo de
armazenamento de dados. Portanto, as modificações feitas por um teste podem afetar outro teste, a menos que o
armazenamento de dados seja redefinido. Usando o TestServer interno, é muito simples hospedar aplicativos
ASP.NET Core em nossos testes de integração, mas isso não necessariamente permite acesso aos dados que serão
usados por ele. Caso esteja usando um banco de dados real, uma abordagem é conectar o aplicativo a um banco de
dados de teste, que pode ser acessado pelos testes, e garantir que ele seja redefinido para um estado conhecido
antes da execução de cada teste.
Neste aplicativo de exemplo, estou usando o suporte de InMemoryDatabase do Entity Framework Core e,
portanto, não consigo me conectar a ele em meu projeto de teste. Em vez disso, exponho um método
InitializeDatabase da classe Startup do aplicativo, que chamo quando o aplicativo é iniciado se ele está no
ambiente Development . Meus testes de integração automaticamente se beneficiam com isso, desde que eles
definam o ambiente como Development . Não preciso me preocupar com a redefinição do banco de dados, pois o
InMemoryDatabase é redefinido sempre que o aplicativo é reiniciado.
A classe Startup :

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using TestingControllersSample.Infrastructure;

namespace TestingControllersSample
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(
optionsBuilder => optionsBuilder.UseInMemoryDatabase("InMemoryDb"));

services.AddMvc();

services.AddScoped<IBrainstormSessionRepository,
EFStormSessionRepository>();
}

public void Configure(IApplicationBuilder app,


IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
var repository = app.ApplicationServices.GetService<IBrainstormSessionRepository>();
InitializeDatabaseAsync(repository).Wait();
}

app.UseStaticFiles();

app.UseMvcWithDefaultRoute();
}

public async Task InitializeDatabaseAsync(IBrainstormSessionRepository repo)


{
var sessionList = await repo.ListAsync();
if (!sessionList.Any())
{
await repo.AddAsync(GetTestSession());
}
}

public static BrainstormSession GetTestSession()


{
var session = new BrainstormSession()
{
Name = "Test Session 1",
DateCreated = new DateTime(2016, 8, 1)
};
var idea = new Idea()
{
DateCreated = new DateTime(2016, 8, 1),
Description = "Totally awesome idea",
Name = "Awesome idea"
};
session.AddIdea(idea);
return session;
}
}
}

Você verá o método GetTestSession que costuma ser usado nos testes de integração abaixo.
Acessando exibições
Cada classe de teste de integração configura o TestServer que executará o aplicativo ASP.NET Core. Por padrão, o
TestServer hospeda o aplicativo Web na pasta em que ele está em execução – nesse caso, a pasta do projeto de
teste. Portanto, quando você tentar testar ações do controlador que retornam ViewResult , poderá receber este
erro:

The view 'Index' wasn't found. The following locations were searched:
(list of locations)

Para corrigir esse problema, você precisa configurar a raiz de conteúdo do servidor, para que ela possa localizar as
exibições para o projeto que está sendo testado. Isso é feito por uma chamada a UseContentRoot na classe
TestFixture , conforme mostrado abaixo:

using System;
using System.IO;
using System.Net.Http;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;

namespace TestingControllersSample.Tests.IntegrationTests
{
/// <summary>
/// A test fixture which hosts the target project (project we wish to test) in an in-memory server.
/// </summary>
/// <typeparam name="TStartup">Target project's startup type</typeparam>
public class TestFixture<TStartup> : IDisposable
{
private readonly TestServer _server;

public TestFixture()
: this(Path.Combine("src"))
{
}

protected TestFixture(string relativeTargetProjectParentDir)


{
var startupAssembly = typeof(TStartup).GetTypeInfo().Assembly;
var contentRoot = GetProjectPath(relativeTargetProjectParentDir, startupAssembly);

var builder = new WebHostBuilder()


.UseContentRoot(contentRoot)
.ConfigureServices(InitializeServices)
.UseEnvironment("Development")
.UseStartup(typeof(TStartup));

_server = new TestServer(builder);

Client = _server.CreateClient();
Client.BaseAddress = new Uri("http://localhost");
}

public HttpClient Client { get; }

public void Dispose()


{
Client.Dispose();
_server.Dispose();
}

protected virtual void InitializeServices(IServiceCollection services)


{
var startupAssembly = typeof(TStartup).GetTypeInfo().Assembly;

// Inject a custom application part manager.


// Overrides AddMvcCore() because it uses TryAdd().
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new AssemblyPart(startupAssembly));
manager.FeatureProviders.Add(new ControllerFeatureProvider());
manager.FeatureProviders.Add(new ViewComponentFeatureProvider());

services.AddSingleton(manager);
}
/// <summary>
/// Gets the full path to the target project that we wish to test
/// </summary>
/// <param name="projectRelativePath">
/// The parent directory of the target project.
/// e.g. src, samples, test, or test/Websites
/// </param>
/// <param name="startupAssembly">The target project's assembly.</param>
/// <returns>The full path to the target project.</returns>
private static string GetProjectPath(string projectRelativePath, Assembly startupAssembly)
{
// Get name of the target project which we want to test
var projectName = startupAssembly.GetName().Name;

// Get currently executing test project path


var applicationBasePath = System.AppContext.BaseDirectory;

// Find the path to the target project


var directoryInfo = new DirectoryInfo(applicationBasePath);
do
{
directoryInfo = directoryInfo.Parent;

var projectDirectoryInfo = new DirectoryInfo(Path.Combine(directoryInfo.FullName,


projectRelativePath));
if (projectDirectoryInfo.Exists)
{
var projectFileInfo = new FileInfo(Path.Combine(projectDirectoryInfo.FullName, projectName,
$"{projectName}.csproj"));
if (projectFileInfo.Exists)
{
return Path.Combine(projectDirectoryInfo.FullName, projectName);
}
}
}
while (directoryInfo.Parent != null);

throw new Exception($"Project root could not be located using the application root
{applicationBasePath}.");
}
}
}

A classe TestFixture é responsável por configurar e criar o TestServer , configurar um HttpClient para se
comunicar com o TestServer . Cada um dos testes de integração usa a propriedade Client para se conectar ao
servidor de teste e fazer uma solicitação.
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;

namespace TestingControllersSample.Tests.IntegrationTests
{
public class HomeControllerTests : IClassFixture<TestFixture<TestingControllersSample.Startup>>
{
private readonly HttpClient _client;

public HomeControllerTests(TestFixture<TestingControllersSample.Startup> fixture)


{
_client = fixture.Client;
}

[Fact]
public async Task ReturnsInitialListOfBrainstormSessions()
{
// Arrange - get a session known to exist
var testSession = Startup.GetTestSession();

// Act
var response = await _client.GetAsync("/");

// Assert
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
Assert.Contains(testSession.Name, responseString);
}

[Fact]
public async Task PostAddsNewBrainstormSession()
{
// Arrange
string testSessionName = Guid.NewGuid().ToString();
var data = new Dictionary<string, string>();
data.Add("SessionName", testSessionName);
var content = new FormUrlEncodedContent(data);

// Act
var response = await _client.PostAsync("/", content);

// Assert
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("/", response.Headers.Location.ToString());
}
}
}

No primeiro teste acima, a responseString armazena o HTML real renderizado de View, que pode ser
inspecionado para confirmar se ele contém os resultados esperados.
O segundo teste constrói um POST de formulário com um nome de sessão exclusivo e executa POST dele para o
aplicativo, verificando, em seguida, se o redirecionamento esperado é retornado.
Métodos de API
Se o aplicativo expõe APIs Web, é uma boa ideia fazer com que os testes automatizados confirmem se elas são
executadas como esperado. O TestServer interno facilita o teste de APIs Web. Se os métodos de API estiverem
usando a associação de modelos, você sempre deverá verificar ModelState.IsValid e os testes de integração são o
melhor lugar para confirmar se a validação do modelo está funcionando corretamente.
O seguinte conjunto de testes é direcionado ao método Create na classe IdeasController mostrada acima:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using TestingControllersSample.ClientModels;
using TestingControllersSample.Core.Model;
using Xunit;

namespace TestingControllersSample.Tests.IntegrationTests
{
public class ApiIdeasControllerTests : IClassFixture<TestFixture<TestingControllersSample.Startup>>
{
internal class NewIdeaDto
{
public NewIdeaDto(string name, string description, int sessionId)
{
Name = name;
Description = description;
SessionId = sessionId;
}

public string Name { get; set; }


public string Description { get; set; }
public int SessionId { get; set; }
}

private readonly HttpClient _client;

public ApiIdeasControllerTests(TestFixture<TestingControllersSample.Startup> fixture)


{
_client = fixture.Client;
}

[Fact]
public async Task CreatePostReturnsBadRequestForMissingNameValue()
{
// Arrange
var newIdea = new NewIdeaDto("", "Description", 1);

// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);

// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}

[Fact]
public async Task CreatePostReturnsBadRequestForMissingDescriptionValue()
{
// Arrange
var newIdea = new NewIdeaDto("Name", "", 1);

// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);

// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}

[Fact]
public async Task CreatePostReturnsBadRequestForSessionIdValueTooSmall()
{
// Arrange
// Arrange
var newIdea = new NewIdeaDto("Name", "Description", 0);

// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);

// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}

[Fact]
public async Task CreatePostReturnsBadRequestForSessionIdValueTooLarge()
{
// Arrange
var newIdea = new NewIdeaDto("Name", "Description", 1000001);

// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);

// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}

[Fact]
public async Task CreatePostReturnsNotFoundForInvalidSession()
{
// Arrange
var newIdea = new NewIdeaDto("Name", "Description", 123);

// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);

// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}

[Fact]
public async Task CreatePostReturnsCreatedIdeaWithCorrectInputs()
{
// Arrange
var testIdeaName = Guid.NewGuid().ToString();
var newIdea = new NewIdeaDto(testIdeaName, "Description", 1);

// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);

// Assert
response.EnsureSuccessStatusCode();
var returnedSession = await response.Content.ReadAsJsonAsync<BrainstormSession>();
Assert.Equal(2, returnedSession.Ideas.Count);
Assert.Contains(testIdeaName, returnedSession.Ideas.Select(i => i.Name).ToList());
}

[Fact]
public async Task ForSessionReturnsNotFoundForBadSessionId()
{
// Arrange & Act
var response = await _client.GetAsync("/api/ideas/forsession/500");

// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}

[Fact]
public async Task ForSessionReturnsIdeasForValidSessionId()
{
// Arrange
var testSession = Startup.GetTestSession();

// Act
// Act
var response = await _client.GetAsync("/api/ideas/forsession/1");

// Assert
response.EnsureSuccessStatusCode();
var ideaList = JsonConvert.DeserializeObject<List<IdeaDTO>>(
await response.Content.ReadAsStringAsync());
var firstIdea = ideaList.First();
Assert.Equal(testSession.Ideas.First().Name, firstIdea.Name);
}
}
}

Ao contrário dos testes de integração de ações que retornam exibições HTML, os métodos de API Web que
retornam resultados podem normalmente ser desserializados como objetos fortemente tipados, como mostra o
último teste acima. Nesse caso, o teste desserializa o resultado para uma instância BrainstormSession e confirma se
a ideia foi corretamente adicionada à sua coleção de ideias.
Você encontrará outros exemplos de testes de integração no projeto de exemplo deste artigo.
Tópicos avançados sobre o ASP.NET Core MVC
10/04/2018 • 1 min to read • Edit Online

Trabalhar com o modelo de aplicativo


Filtros
Áreas
Partes do aplicativo
Associação de modelos personalizada
Trabalhando com o modelo de aplicativo
08/02/2018 • 18 min to read • Edit Online

Por Steve Smith


O ASP.NET Core MVC define um modelo de aplicativo que representa os componentes de um aplicativo MVC.
Leia e manipule esse modelo para modificar o comportamento de elementos MVC. Por padrão, o MVC segue
algumas convenções para determinar quais classes são consideradas controladores, quais métodos nessas classes
são ações e qual o comportamento de parâmetros e do roteamento. Personalize esse comportamento de acordo
com as necessidades do aplicativo criando suas próprias convenções e aplicando-as globalmente ou como
atributos.

Modelos e provedores
O modelo de aplicativo ASP.NET Core MVC inclui interfaces abstratas e classes de implementação concreta que
descrevem um aplicativo MVC. Esse modelo é o resultado da descoberta do MVC de controladores, ações,
parâmetros de ação, rotas e filtros do aplicativo de acordo com as convenções padrão. Trabalhando com o modelo
de aplicativo, você pode modificar o aplicativo para seguir convenções diferentes do comportamento padrão do
MVC. Os parâmetros, os nomes, as rotas e os filtros são todos usados como dados de configuração para ações e
controladores.
O Modelo de Aplicativo ASP.NET Core MVC tem a seguinte estrutura:
ApplicationModel
Controladores (ControllerModel)
Ações (ActionModel)
Parâmetros (ParameterModel)
Cada nível do modelo tem acesso a uma coleção Properties comum e níveis inferiores podem acessar e substituir
valores de propriedade definidos por níveis mais altos na hierarquia. As propriedades são persistidas nas
ActionDescriptor.Properties quando as ações são criadas. Em seguida, quando uma solicitação está sendo
manipulada, as propriedades que uma convenção adicionou ou modificou podem ser acessadas por meio de
ActionContext.ActionDescriptor.Properties . O uso de propriedades é uma ótima maneira de configurar filtros,
associadores de modelos, etc., por ação.

OBSERVAÇÃO
A coleção ActionDescriptor.Properties não é thread-safe (para gravações) após a conclusão da inicialização do aplicativo.
Convenções são a melhor maneira de adicionar dados com segurança a essa coleção.

IApplicationModelProvider
O ASP.NET Core MVC carrega o modelo de aplicativo usando um padrão de provedor, definido pela interface
IApplicationModelProvider. Esta seção aborda alguns dos detalhes de implementação interna de como funciona
esse provedor. Este é um tópico avançado – a maioria dos aplicativos que utiliza o modelo de aplicativo deve fazer
isso trabalhando com convenções.
Implementações da interface IApplicationModelProvider "encapsulam" umas às outras, com cada implementação
chamando OnProvidersExecuting em ordem crescente com base em sua propriedade Order . O método
OnProvidersExecuted é então chamado em ordem inversa. A estrutura define vários provedores:
Primeiro ( Order=-1000 ):
DefaultApplicationModelProvider

Em seguida ( Order=-990 ):
AuthorizationApplicationModelProvider
CorsApplicationModelProvider

OBSERVAÇÃO
A ordem na qual dois provedores com o mesmo valor para Order são chamados não é definida e, portanto, não se deve
depender dela.

OBSERVAÇÃO
IApplicationModelProvider é um conceito avançado a ser estendido pelos autores da estrutura. Em geral, aplicativos
devem usar convenções e estruturas devem usar provedores. A principal diferença é que os provedores sempre são
executados antes das convenções.

O DefaultApplicationModelProvider estabelece muitos dos comportamentos padrão usados pelo ASP.NET Core
MVC. Suas responsabilidades incluem:
Adicionar filtros globais ao contexto
Adicionar controladores ao contexto
Adicionar métodos de controlador públicos como ações
Adicionar parâmetros de método de ação ao contexto
Aplicar a rota e outros atributos
Alguns comportamentos internos são implementados pelo DefaultApplicationModelProvider . Esse provedor é
responsável pela construção do ControllerModel , que, por sua vez, referencia instâncias ActionModel ,
PropertyModel e ParameterModel . A classe DefaultApplicationModelProvider é um detalhe de implementação de
estrutura interna que pode e será alterado no futuro.
O AuthorizationApplicationModelProvider é responsável por aplicar o comportamento associado aos atributos
AuthorizeFilter e AllowAnonymousFilter . Saiba mais sobre esses atributos.
O CorsApplicationModelProvider implementa o comportamento associado ao IEnableCorsAttribute , ao
IDisableCorsAttribute e ao DisableCorsAuthorizationFilter . Saiba mais sobre o CORS.

Convenções
O modelo de aplicativo define abstrações de convenção que fornecem uma maneira simples de personalizar o
comportamento dos modelos em vez de substituir o modelo ou o provedor inteiro. Essas abstrações são a maneira
recomendada para modificar o comportamento do aplicativo. As convenções fornecem uma maneira de escrever
código que aplicará personalizações de forma dinâmica. Enquanto os filtros fornecem um meio de modificar o
comportamento da estrutura, as personalizações permitem que você controle como todo o aplicativo está
conectado.
As seguintes convenções estão disponíveis:
IApplicationModelConvention
IControllerModelConvention
IActionModelConvention
IParameterModelConvention

As convenções são aplicadas por sua adição às opções do MVC ou pela implementação de Attribute s e sua
aplicação a controladores, ações ou parâmetros de ação (semelhante a Filters ). Ao contrário dos filtros, as
convenções são executadas apenas quando o aplicativo é iniciado, não como parte de cada solicitação.
Amostra: modificando o ApplicationModel
A convenção a seguir é usada para adicionar uma propriedade ao modelo de aplicativo.

using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
public class ApplicationDescription : IApplicationModelConvention
{
private readonly string _description;

public ApplicationDescription(string description)


{
_description = description;
}

public void Apply(ApplicationModel application)


{
application.Properties["description"] = _description;
}
}
}

As convenções de modelo de aplicativo são aplicadas como opções quando o MVC é adicionado em
ConfigureServices em Startup .

public void ConfigureServices(IServiceCollection services)


{
services.AddMvc(options =>
{
options.Conventions.Add(new ApplicationDescription("My Application Description"));
options.Conventions.Add(new NamespaceRoutingConvention());
//options.Conventions.Add(new IdsMustBeInRouteParameterModelConvention());
});
}

As propriedades são acessíveis na coleção de propriedades ActionDescriptor nas ações do controlador:

public class AppModelController : Controller


{
public string Description()
{
return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
}
}

Amostra: modificando a descrição de ControllerModel


Como no exemplo anterior, o modelo de controlador também pode ser modificado para incluir propriedades
personalizadas. Elas substituirão as propriedades existentes com o mesmo nome especificado no modelo de
aplicativo. O seguinte atributo de convenção adiciona uma descrição no nível do controlador:
using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
public class ControllerDescriptionAttribute : Attribute, IControllerModelConvention
{
private readonly string _description;

public ControllerDescriptionAttribute(string description)


{
_description = description;
}

public void Apply(ControllerModel controllerModel)


{
controllerModel.Properties["description"] = _description;
}
}
}

Essa convenção é aplicada como um atributo em um controlador.

[ControllerDescription("Controller Description")]
public class DescriptionAttributesController : Controller
{
public string Index()
{
return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
}

A propriedade "description" é acessada da mesma maneira como nos exemplos anteriores.


Amostra: modificando a descrição de ActionModel
Uma convenção de atributo separada pode ser aplicada a ações individuais, substituindo o comportamento já
aplicado no nível do aplicativo ou do controlador.

using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
public class ActionDescriptionAttribute : Attribute, IActionModelConvention
{
private readonly string _description;

public ActionDescriptionAttribute(string description)


{
_description = description;
}

public void Apply(ActionModel actionModel)


{
actionModel.Properties["description"] = _description;
}
}
}

A aplicação disso a uma ação no controlador do exemplo anterior demonstra como ela substitui a convenção no
nível do controlador:
[ControllerDescription("Controller Description")]
public class DescriptionAttributesController : Controller
{
public string Index()
{
return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
}

[ActionDescription("Action Description")]
public string UseActionDescriptionAttribute()
{
return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
}
}

Amostra: modificando o ParameterModel


A convenção a seguir pode ser aplicada a parâmetros de ação para modificar seu BindingInfo . A convenção a
seguir exige que o parâmetro seja um parâmetro de rota; outras possíveis origens da associação (como valores de
cadeia de caracteres de consulta) são ignoradas.

using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace AppModelSample.Conventions
{
public class MustBeInRouteParameterModelConvention : Attribute, IParameterModelConvention
{
public void Apply(ParameterModel model)
{
if (model.BindingInfo == null)
{
model.BindingInfo = new BindingInfo();
}
model.BindingInfo.BindingSource = BindingSource.Path;
}
}
}

O atributo pode ser aplicado a qualquer parâmetro de ação:

public class ParameterModelController : Controller


{
// Will bind: /ParameterModel/GetById/123
// WON'T bind: /ParameterModel/GetById?id=123
public string GetById([MustBeInRouteParameterModelConvention]int id)
{
return $"Bound to id: {id}";
}
}

Amostra: modificando o nome de ActionModel


A convenção a seguir modifica o ActionModel para atualizar o nome da ação ao qual ele é aplicado. O novo nome
é fornecido como um parâmetro para o atributo. Esse novo nome é usado pelo roteamento e, portanto, isso
afetará a rota usada para acessar esse método de ação.
using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
public class CustomActionNameAttribute : Attribute, IActionModelConvention
{
private readonly string _actionName;

public CustomActionNameAttribute(string actionName)


{
_actionName = actionName;
}

public void Apply(ActionModel actionModel)


{
// this name will be used by routing
actionModel.ActionName = _actionName;
}
}
}

Esse atributo é aplicado a um método de ação no HomeController :

// Route: /Home/MyCoolAction
[CustomActionName("MyCoolAction")]
public string SomeName()
{
return ControllerContext.ActionDescriptor.ActionName;
}

Mesmo que o nome do método seja SomeName , o atributo substitui a convenção do MVC de uso do nome do
método e substitui o nome da ação por MyCoolAction . Portanto, a rota usada para acessar essa ação é
/Home/MyCoolAction .

OBSERVAÇÃO
Esse exemplo é basicamente o mesmo que usar o atributo ActionName interno.

Amostra: convenção de roteamento personalizada


Use uma IApplicationModelConvention para personalizar como funciona o roteamento. Por exemplo, a seguinte
convenção incorporará namespaces dos Controladores em suas rotas, substituindo . no namespace por / na
rota:
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System.Linq;

namespace AppModelSample.Conventions
{
public class NamespaceRoutingConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
var hasAttributeRouteModels = controller.Selectors
.Any(selector => selector.AttributeRouteModel != null);

if (!hasAttributeRouteModels
&& controller.ControllerName.Contains("Namespace")) // affect one controller in this
sample
{
// Replace the . in the namespace with a / to create the attribute route
// Ex: MySite.Admin namespace will correspond to MySite/Admin attribute route
// Then attach [controller], [action] and optional {id?} token.
// [Controller] and [action] is replaced with the controller and action
// name to generate the final template
controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel()
{
Template = controller.ControllerType.Namespace.Replace('.', '/') +
"/[controller]/[action]/{id?}"
};
}
}

// You can continue to put attribute route templates for the controller actions depending on the
way you want them to behave
}
}
}

A convenção é adicionada como uma opção na classe Startup.

public void ConfigureServices(IServiceCollection services)


{
services.AddMvc(options =>
{
options.Conventions.Add(new ApplicationDescription("My Application Description"));
options.Conventions.Add(new NamespaceRoutingConvention());
//options.Conventions.Add(new IdsMustBeInRouteParameterModelConvention());
});
}

DICA
Adicione convenções ao middleware acessando MvcOptions com
services.Configure<MvcOptions>(c => c.Conventions.Add(YOURCONVENTION));

Esta amostra aplica essa convenção às rotas que não estão usando o roteamento de atributo, nas quais o
controlador tem "Namespace" em seu nome. O seguinte controlador demonstra essa convenção:
using Microsoft.AspNetCore.Mvc;

namespace AppModelSample.Controllers
{
public class NamespaceRoutingController : Controller
{
// using NamespaceRoutingConvention
// route: /AppModelSample/Controllers/NamespaceRouting/Index
public string Index()
{
return "This demonstrates namespace routing.";
}
}
}

Uso do modelo de aplicativo em WebApiCompatShim


O ASP.NET Core MVC usa um conjunto diferente de convenções da API Web ASP.NET 2. Usando convenções
personalizadas, você pode modificar o comportamento de um aplicativo ASP.NET Core MVC para que ele seja
consistente com o comportamento de um aplicativo de API Web. A Microsoft fornece o WebApiCompatShim
especificamente para essa finalidade.

OBSERVAÇÃO
Saiba mais sobre como migrar da API Web ASP.NET.

Para usar o Shim de Compatibilidade de API Web, você precisa adicionar o pacote ao projeto e, em seguida,
adicionar as convenções ao MVC chamando AddWebApiConventions em Startup :

services.AddMvc().AddWebApiConventions();

As convenções fornecidas pelo shim são aplicadas apenas às partes do aplicativo que tiveram determinados
atributos aplicados a elas. Os seguintes quatro atributos são usados para controlar quais controladores devem ter
suas convenções modificadas pelas convenções do shim:
UseWebApiActionConventionsAttribute
UseWebApiOverloadingAttribute
UseWebApiParameterConventionsAttribute
UseWebApiRoutesAttribute
Convenções de ação
O UseWebApiActionConventionsAttribute é usado para mapear o método HTTP para ações com base em seu nome
(por exemplo, Get será mapeado para HttpGet ). Ele se aplica somente a ações que não usam o roteamento de
atributo.
Sobrecarga
O UseWebApiOverloadingAttribute é usado para aplicar a convenção WebApiOverloadingApplicationModelConvention .
Essa convenção adiciona uma OverloadActionConstraint ao processo de seleção de ação, o que limita as ações de
candidato àquelas para as quais a solicitação atende a todos os parâmetros não opcionais.
Convenções de parâmetro
O UseWebApiParameterConventionsAttribute é usado para aplicar a convenção de ação
WebApiParameterConventionsApplicationModelConvention . Essa convenção especifica que tipos simples usados como
parâmetros de ação são associados por meio do URI por padrão, enquanto tipos complexos são associados por
meio do corpo da solicitação.
Rotas
O UseWebApiRoutesAttribute controla se a convenção de controlador WebApiApplicationModelConvention é aplicada.
Quando habilitada, essa convenção é usada para adicionar suporte de áreas à rota.
Além de um conjunto de convenções, o pacote de compatibilidade inclui uma classe base
System.Web.Http.ApiController que substitui aquela fornecida pela API Web. Isso permite que os controladores
escritos para a API Web e que herdam de seu ApiController funcionem como foram criados, enquanto são
executados no ASP.NET Core MVC. Essa classe base de controlador é decorada com todos os atributos
UseWebApi* listados acima. O ApiController expõe propriedades, métodos e tipos de resultado compatíveis com
aqueles encontrados na API Web.

Usando o ApiExplorer para documentar o aplicativo


O modelo de aplicativo expõe uma propriedade ApiExplorer em cada nível, que pode ser usada para percorrer a
estrutura do aplicativo. Isso pode ser usado para gerar páginas da Ajuda para as APIs Web usando ferramentas
como o Swagger. A propriedade ApiExplorer expõe uma propriedade IsVisible que pode ser definida para
especificar quais partes do modelo do aplicativo devem ser expostas. Defina essa configuração usando uma
convenção:

using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
public class EnableApiExplorerApplicationConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
application.ApiExplorer.IsVisible = true;
}
}
}

Usando essa abordagem (e convenções adicionais, se necessário), você pode habilitar ou desabilitar a visibilidade
da API em qualquer nível no aplicativo.
Filtros
08/02/2018 • 37 min to read • Edit Online

Por Tom Dykstra e Steve Smith


Filtros no ASP.NET Core MVC permitem executar código antes ou depois de determinados estágios do
pipeline de processamento de solicitações.
Filtros internos lidam com tarefas como autorização (impedindo o acesso a recursos para os quais o
usuário não está autorizado), garantir que todas as solicitações usem HTTPS e cache de respostas (fazendo
um curto-circuito no pipeline de solicitações para retornar uma resposta em cache).
É possível criar filtros personalizados para lidar com questões relacionadas a características transversais de
seu aplicativo. Sempre que você quiser evitar a duplicação de código entre ações, filtros são a solução. Por
exemplo, é possível consolidar o código de tratamento de erro em um filtro de exceção.
Exibir ou baixar amostra do GitHub.

Como os filtros funcionam?


Os filtros são executados dentro do pipeline de invocação de ações do MVC, às vezes chamado de pipeline
de filtros. O pipeline de filtros é executado após o MVC selecionar a ação a ser executada.

Tipos de filtro
Cada tipo de filtro é executado em um estágio diferente no pipeline de filtros.
Filtros de autorização são executados primeiro e são usados para determinar se o usuário atual tem
autorização para a solicitação atual. Eles podem fazer um curto-circuito do pipeline quando uma
solicitação não é autorizada.
Filtros de recurso são os primeiros a lidar com uma solicitação após a autorização. Eles podem
executar código antes do restante do pipeline de filtros e após o restante do pipeline ser concluído.
Esses filtros são úteis para implementar o cache ou para, de alguma forma, fazer o curto-circuito do
pipeline de filtros por motivos de desempenho. Como são executados antes da associação de
modelos, eles são úteis para qualquer coisa que precise influenciá-la.
Filtros de ação podem executar código imediatamente antes e depois de um método de ação
individual ser chamado. Eles podem ser usados para manipular os argumentos passados para uma
ação, bem como o resultado da ação.
Filtros de exceção são usados para aplicar políticas globais para exceções sem tratamento que
ocorrem antes que qualquer coisa tenha sido gravada no corpo da resposta.
Filtros de resposta podem executar código imediatamente antes e depois da execução de resultados
de ações individuais. Eles são executados somente quando o método de ação foi executado com
êxito e são úteis para lógicas que precisam delimitar a execução formatador ou modo de exibição.
O diagrama a seguir mostra como esses tipos de filtro interagem no pipeline de filtros.

Implementação
Os filtros dão suporte a implementações síncronas e assíncronas por meio de diferentes definições de
interface. Escolha a variante síncrona ou assíncrona dependendo do tipo de tarefa que você precisa
executar.
Filtros síncronos que podem executar código antes e depois do estágio do pipeline definem os métodos
OnStageExecuting e OnStageExecuted. Por exemplo, OnActionExecuting é chamado antes que o método de
ação seja chamado e OnActionExecuted é chamado após o método de ação retornar.
#region snippet1
using FiltersSample.Helper;
using Microsoft.AspNetCore.Mvc.Filters;

namespace FiltersSample.Filters
{
#region snippet_ActionFilter
public class SampleActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// do something before the action executes
}

public void OnActionExecuted(ActionExecutedContext context)


{
// do something after the action executes
}
}
#endregion
}
#endregion

Filtros assíncronos definem um único método OnStageExecutionAsync. Esse método usa um delegado
FilterTypeExecutionDelegate, que executa o estágio de pipeline do filtro. Por exemplo,
ActionExecutionDelegate chama o método de ação e você pode executar código antes e depois de chamá-
lo.

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;

namespace FiltersSample.Filters
{
public class SampleAsyncActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
// do something before the action executes
var resultContext = await next();
// do something after the action executes; resultContext.Result will be set
}
}
}

É possível implementar interfaces para vários estágios do filtro em uma única classe. Por exemplo, a classe
abstrata ActionFilterAttribute implementa IActionFilter e IResultFilter , bem como seus equivalentes
assíncronos.

OBSERVAÇÃO
Implemente ou a versão assíncrona ou a versão síncrona de uma interface de filtro, não ambas. Primeiro, a estrutura
verifica se o filtro implementa a interface assíncrona e, se for esse o caso, a chama. Caso contrário, ela chama os
métodos da interface síncrona. Se você implementasse as duas interfaces em uma classe, somente o método
assíncrono seria chamado. Ao usar classes abstratas como ActionFilterAttribute, você substituiria apenas os métodos
síncronos ou o método assíncrono para cada tipo de filtro.

IFilterFactory
IFilterFactory implementa IFilter . Portanto, uma instância IFilterFactory pode ser usada como uma
instância IFilter em qualquer parte do pipeline de filtro. Quando se prepara para invocar o filtro, a
estrutura tenta convertê-lo em um IFilterFactory . Se essa conversão for bem-sucedida, o método
CreateInstance será chamado para criar a instância IFilter que será invocada. Isso fornece um design
muito flexível, uma vez que o pipeline de filtro preciso não precisa ser definido explicitamente quando o
aplicativo é iniciado.
Você pode implementar IFilterFactory em suas próprias implementações de atributo como outra
abordagem à criação de filtros:

public class AddHeaderWithFactoryAttribute : Attribute, IFilterFactory


{
// Implement IFilterFactory
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return new InternalAddHeaderFilter();
}

private class InternalAddHeaderFilter : IResultFilter


{
public void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add(
"Internal", new string[] { "Header Added" });
}

public void OnResultExecuted(ResultExecutedContext context)


{
}
}

public bool IsReusable


{
get
{
return false;
}
}
}

Atributos de filtro internos


A estrutura inclui filtros internos baseados em atributos que você pode organizar em subclasses e
personalizar. Por exemplo, o seguinte Filtro de resultado adiciona um cabeçalho à resposta.
using Microsoft.AspNetCore.Mvc.Filters;

namespace FiltersSample.Filters
{
public class AddHeaderAttribute : ResultFilterAttribute
{
private readonly string _name;
private readonly string _value;

public AddHeaderAttribute(string name, string value)


{
_name = name;
_value = value;
}

public override void OnResultExecuting(ResultExecutingContext context)


{
context.HttpContext.Response.Headers.Add(
_name, new string[] { _value });
base.OnResultExecuting(context);
}
}
}

Os atributos permitem que os filtros aceitem argumentos, conforme mostrado no exemplo acima. Você
adicionaria esse atributo a um método de ação ou controlador e especificaria o nome e o valor do
cabeçalho HTTP:

[AddHeader("Author", "Steve Smith @ardalis")]


public class SampleController : Controller
{
public IActionResult Index()
{
return Content("Examine the headers using developer tools.");
}

[ShortCircuitingResourceFilter]
public IActionResult SomeResource()
{
return Content("Successful access to resource - header should be set.");
}

O resultado da ação Index é mostrado abaixo – os cabeçalhos de resposta são exibidos na parte inferior
direita.
Várias interfaces de filtro têm atributos correspondentes que podem ser usados como classes base para
implementações personalizadas.
Atributos de filtro:
ActionFilterAttribute
ExceptionFilterAttribute
ResultFilterAttribute
FormatFilterAttribute
ServiceFilterAttribute
TypeFilterAttribute

TypeFilterAttribute e ServiceFilterAttribute são explicados posteriormente neste artigo.

Escopos e ordem de execução dos filtros


Um filtro pode ser adicionado ao pipeline com um de três escopos. É possível adicionar um filtro a um
método de ação específico ou a uma classe de controlador usando um atributo. Também é possível
registrar um filtro globalmente (para todos os controladores e ações) adicionando-o à coleção
MvcOptions.Filters no método ConfigureServices na classe Startup :

public void ConfigureServices(IServiceCollection services)


{
services.AddMvc(options =>
{
options.Filters.Add(new AddHeaderAttribute("GlobalAddHeader",
"Result filter added to MvcOptions.Filters")); // an instance
options.Filters.Add(typeof(SampleActionFilter)); // by type
options.Filters.Add(new SampleGlobalActionFilter()); // an instance
});

services.AddScoped<AddHeaderFilterWithDi>();
}

Ordem padrão de execução


Quando há vários filtros para um determinado estágio do pipeline, o escopo determina a ordem padrão de
execução dos filtros. Filtros globais circundam filtros de classe, que, por sua vez, circundam filtros de
método. Isso é, às vezes, chamado de aninhamento de "bonecas russas", pois cada aumento de escopo é
encapsulado em torno do escopo anterior, como uma matriosca. Normalmente, você obtém o
comportamento de substituição desejado sem precisar determinar uma ordem explicitamente.
Como resultado desse aninhamento, o código posterior dos filtros é executado na ordem inversa do código
anterior. A sequência é semelhante a esta:
O código anterior de filtros aplicados globalmente
O código anterior de filtros aplicados a controladores
O código anterior de filtros aplicados a métodos de ação
O código posterior de filtros aplicados a métodos de ação
O código posterior de filtros aplicados a controladores
O código posterior de filtros aplicados globalmente
Este é um exemplo que ilustra a ordem na qual métodos de filtro são chamados para filtros de ação
síncronos.

SEQUÊNCIA ESCOPO DO FILTRO MÉTODO DO FILTRO

1 Global OnActionExecuting

2 Controlador OnActionExecuting

3 Método OnActionExecuting

4 Método OnActionExecuted

5 Controlador OnActionExecuted

6 Global OnActionExecuted

Esta sequência mostra que o filtro do método está aninhado no filtro do controlador e que o filtro do
controlador está aninhado no filtro global. Para colocar de outra forma, se você estiver dentro do método
OnStageExecutionAsync de um filtro assíncrono, todos os filtros com escopo mais estreito serão
executados enquanto seu código estiver na pilha.

OBSERVAÇÃO
Cada controlador que herda da classe base Controller inclui os métodos OnActionExecuting e
OnActionExecuted . Esses métodos encapsulam os filtros que são executados para uma determinada ação:
OnActionExecuting é chamado antes de qualquer um dos filtros e OnActionExecuted é chamado após todos os
filtros.

Substituindo a ordem padrão


É possível substituir a sequência padrão de execução implementando IOrderedFilter . Essa interface expõe
uma propriedade Order que tem precedência sobre o escopo para determinar a ordem de execução. Um
filtro com um valor mais baixo de Order terá seu código anterior executado antes que um filtro com um
valor mais alto de Order . Um filtro com um valor mais baixo de Order terá seu código posterior executado
depois que um filtro com um valor mais alto de Order . Você pode definir a propriedade Order usando um
parâmetro de construtor:
[MyFilter(Name = "Controller Level Attribute", Order=1)]

Se você tiver os mesmos três filtros de ação mostrados no exemplo anterior, mas definir a propriedade
Order dos filtros de controlador e global como 1 e 2 respectivamente, a ordem de execução será invertida.

SEQUÊNCIA ESCOPO DO FILTRO PROPRIEDADE ORDER MÉTODO DO FILTRO

1 Método 0 OnActionExecuting

2 Controlador 1 OnActionExecuting

3 Global 2 OnActionExecuting

4 Global 2 OnActionExecuted

5 Controlador 1 OnActionExecuted

6 Método 0 OnActionExecuted

A propriedade Order tem precedência sobre o escopo ao determinar a ordem na qual os filtros serão
executados. Os filtros são classificados primeiro pela ordem e o escopo é usado para desempatar. Todos os
filtros internos implementam IOrderedFilter e definem o valor padrão de Order como 0, de forma que o
escopo determina a ordem, a menos que você defina Order como um valor diferente de zero.

Cancelamento e curto-circuito
Você pode fazer um curto-circuito no pipeline de filtros a qualquer momento, definindo a propriedade
Result no parâmetro context fornecido ao método do filtro. Por exemplo, o filtro de recurso a seguir
impede que o resto do pipeline seja executado.

using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace FiltersSample.Filters
{
public class ShortCircuitingResourceFilterAttribute : Attribute,
IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.Result = new ContentResult()
{
Content = "Resource unavailable - header should not be set"
};
}

public void OnResourceExecuted(ResourceExecutedContext context)


{
}
}
}

No código a seguir, os filtros ShortCircuitingResourceFilter e AddHeader têm como destino o método de


ação SomeResource . No entanto, como ShortCircuitingResourceFilter é executado primeiro (porque ele é
um filtro de recurso e AddHeader é um filtro de ação) e faz o curto-circuito do resto do pipeline, o filtro
AddHeader nunca é executado para a ação SomeResource . Esse comportamento seria o mesmo se os dois
filtros fossem aplicados no nível do método de ação, desde que ShortCircuitingResourceFilter fosse
executado primeiro (devido ao seu tipo de filtro ou ao uso explícito da propriedade Order , por exemplo).

[AddHeader("Author", "Steve Smith @ardalis")]


public class SampleController : Controller
{
public IActionResult Index()
{
return Content("Examine the headers using developer tools.");
}

[ShortCircuitingResourceFilter]
public IActionResult SomeResource()
{
return Content("Successful access to resource - header should be set.");
}

Injeção de dependência
Filtros podem ser adicionados por tipo ou por instância. Se você adicionar uma instância, ela será usada
para cada solicitação. Se você adicionar um tipo, ele será ativado pelo tipo, o que significa que uma
instância será criada para cada solicitação e as dependências de construtor serão populadas pela DI (injeção
de dependência). Adicionar um filtro por tipo é equivalente a
filters.Add(new TypeFilterAttribute(typeof(MyFilter))) .

Filtros que são implementados como atributos e adicionados diretamente a classes de controlador ou
métodos de ação não podem ter dependências de construtor fornecidas pela DI (injeção de dependência).
Isso ocorre porque os atributos precisam ter os parâmetros de construtor fornecidos quando eles são
aplicados. Essa é uma limitação do funcionamento dos atributos.
Se seus filtros tiverem dependências que você precisa acessar da DI, há várias abordagens com suporte. É
possível aplicar o filtro a um método de ação ou classe usando uma das opções a seguir:
ServiceFilterAttribute
TypeFilterAttribute
IFilterFactory implementado em seu atributo

OBSERVAÇÃO
Uma dependência que talvez você queira obter da DI é um agente. No entanto, evite criar e usar filtros apenas para
fins de registro em log, pois é possível que os recursos de registro em log da estrutura interna já forneçam o que
você precisa. Se você for adicionar o registro em log a seus filtros, ele deve se concentrar em questões referentes ao
domínio de negócios ou ao comportamento específico de seu filtro, em vez de ações do MVC ou outros eventos da
estrutura.

ServiceFilterAttribute
Um ServiceFilter recupera uma instância do filtro da DI. Adicione o filtro ao contêiner em
ConfigureServices e faça uma referência a ele em um atributo ServiceFilter
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add(new AddHeaderAttribute("GlobalAddHeader",
"Result filter added to MvcOptions.Filters")); // an instance
options.Filters.Add(typeof(SampleActionFilter)); // by type
options.Filters.Add(new SampleGlobalActionFilter()); // an instance
});

services.AddScoped<AddHeaderFilterWithDi>();
}

[ServiceFilter(typeof(AddHeaderFilterWithDi))]
public IActionResult Index()
{
return View();
}

Usar ServiceFilter sem registrar o tipo de filtro gera uma exceção:

System.InvalidOperationException: No service for type


'FiltersSample.Filters.AddHeaderFilterWithDI' has been registered.

ServiceFilterAttribute implementa , que expõe um único método para criar uma instância
IFilterFactory
de IFilter . No caso de ServiceFilterAttribute , o método CreateInstance da interface IFilterFactory é
implementado para carregar o tipo especificado do contêiner de serviços (DI).
TypeFilterAttribute
TypeFilterAttribute é muito semelhante a ServiceFilterAttribute (e também implementa
IFilterFactory ), mas seu tipo não é resolvido diretamente do contêiner de DI. Em vez disso, ele cria uma
instância do tipo usando Microsoft.Extensions.DependencyInjection.ObjectFactory .
Devido a essa diferença, os tipos que são referenciados usando o TypeFilterAttribute não precisam ser
registrados com o contêiner primeiro (mas eles ainda terão suas dependências atendidas pelo contêiner).
Além disso, TypeFilterAttribute também pode aceitar argumentos de construtor para o tipo em questão.
O exemplo a seguir demonstra como passar argumentos para um tipo usando TypeFilterAttribute :

[TypeFilter(typeof(AddHeaderAttribute),
Arguments = new object[] { "Author", "Steve Smith (@ardalis)" })]
public IActionResult Hi(string name)
{
return Content($"Hi {name}");
}

Se tiver um filtro que não requer nenhum argumento, mas que tem dependências de construtor que
precisam ser atendidas pela DI, você poderá usar seu próprio atributo nomeado em classes e métodos em
vez de [TypeFilter(typeof(FilterType))] ). O filtro a seguir mostra como isso pode ser implementado:
public class SampleActionFilterAttribute : TypeFilterAttribute
{
public SampleActionFilterAttribute():base(typeof(SampleActionFilterImpl))
{
}

private class SampleActionFilterImpl : IActionFilter


{
private readonly ILogger _logger;
public SampleActionFilterImpl(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
}

public void OnActionExecuting(ActionExecutingContext context)


{
_logger.LogInformation("Business action starting...");
// perform some business logic work

public void OnActionExecuted(ActionExecutedContext context)


{
// perform some business logic work
_logger.LogInformation("Business action completed.");
}
}
}

Esse filtro pode ser aplicado a classes ou métodos usando a sintaxe [SampleActionFilter] , em vez de
precisar usar [TypeFilter] ou [ServiceFilter] .

Filtros de autorização
Filtros de autorização controlam o acesso aos métodos de ação e são os primeiros filtros a serem
executados dentro do pipeline de filtros. Eles têm apenas um método anterior, diferente da maioria dos
filtros que dão suporte a métodos anteriores e posteriores. Você deve escrever somente um filtro de
autorização personalizado se estiver escrevendo sua própria estrutura de autorização. Prefira configurar
suas políticas de autorização ou escrever uma política de autorização personalizada em vez de escrever um
filtro personalizado. A implementação do filtro interno é responsável somente por chamar o sistema de
autorização.
Observe que você não deve gerar exceções dentro de filtros de autorização, uma vez que nada tratará a
exceção (filtros de exceção não as tratarão). Em vez disso, emita um desafio ou encontre outra maneira.
Saiba mais sobre Autorização.

Filtros de recurso
Filtros de recurso implementam a interface IResourceFilter ou IAsyncResourceFilter e sua execução
encapsula a maior parte do pipeline de filtros. (Somente Filtros de autorização são executados antes deles.)
Filtros de recurso são especialmente úteis se você precisa fazer o curto-circuito da maioria do trabalho que
uma solicitação está fazendo. Por exemplo, um filtro de cache poderá evitar o resto do pipeline se a
resposta já estiver no cache.
O filtro de recurso com curto-circuito mostrado anteriormente é um exemplo de filtro de recurso. Outro
exemplo é DisableFormValueModelBindingAttribute, que impede que a associação de modelos acesse os
dados do formulário. Ele é útil para casos em que você sabe que vai receber uploads de arquivos grandes e
quer que o formulário seja lido na memória.
Filtros de ação
Filtros de ação implementam a interface IActionFilter ou IAsyncActionFilter e sua execução envolve a
execução de métodos de ação.
Veja um exemplo de filtro de ação:

public class SampleActionFilter : IActionFilter


{
public void OnActionExecuting(ActionExecutingContext context)
{
// do something before the action executes
}

public void OnActionExecuted(ActionExecutedContext context)


{
// do something after the action executes
}
}

ActionExecutingContext fornece as seguintes propriedades:


ActionArguments – permite manipular as entradas para a ação.
Controller – permite manipular a instância do controlador.
Result – defini-lo faz um curto-circuito da execução do método de ação e dos filtros de ação
posteriores. Apresentar uma exceção também impete a execução do método de ação e dos filtros
posteriores, mas isso é tratado como uma falha, e não como um resultado bem-sucedido.
ActionExecutedContext fornece Controller e Result , bem como as seguintes propriedades:
Canceled – será verdadeiro se a execução da ação tiver sofrido curto-circuito por outro filtro.
Exception – não será nulo se a ação ou um filtro de ação posterior tiver apresentado uma exceção.
Definir essa propriedade como nula “manipula” uma exceção efetivamente e Result será executado
como se tivesse sido retornado do método de ação normalmente.
Para um IAsyncActionFilter , uma chamada para ActionExecutionDelegate executa qualquer filtro de ação
posterior e o método da ação, retornando um ActionExecutedContext . Para fazer um curto-circuito, atribua
ActionExecutingContext.Result a uma instância de resultado e não chame o ActionExecutionDelegate .

A estrutura fornece um ActionFilterAttribute abstrato que você pode colocar em uma subclasse.
Você pode usar um filtro de ação para validar automaticamente o estado do modelo e retornar erros se o
estado for inválido:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace FiltersSample.Filters
{
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}
}

O método OnActionExecuted é executado depois do método de ação e pode ver e manipular os resultados
da ação por meio da propriedade ActionExecutedContext.Result . ActionExecutedContext.Canceled será
definido como verdadeiro se a execução da ação tiver sofrido curto-circuito por outro filtro.
ActionExecutedContext.Exception será definido como um valor não nulo se a ação ou um filtro de ação
posterior tiver apresentado uma exceção. Definir ActionExecutedContext.Exception como nulo “manipula”
uma exceção efetivamente e ActionExectedContext.Result será executado como se tivesse sido retornado
do método de ação normalmente.

Filtros de exceção
Filtros de exceção implementam a interface IExceptionFilter ou IAsyncExceptionFilter . Eles podem ser
usados para implementar políticas de tratamento de erro comuns para um aplicativo.
O exemplo de filtro de exceção a seguir usa uma exibição de erro de desenvolvedor personalizada para
exibir detalhes sobre exceções que ocorrem quando o aplicativo está em desenvolvimento:

public class CustomExceptionFilterAttribute : ExceptionFilterAttribute


{
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IModelMetadataProvider _modelMetadataProvider;

public CustomExceptionFilterAttribute(
IHostingEnvironment hostingEnvironment,
IModelMetadataProvider modelMetadataProvider)
{
_hostingEnvironment = hostingEnvironment;
_modelMetadataProvider = modelMetadataProvider;
}

public override void OnException(ExceptionContext context)


{
if (!_hostingEnvironment.IsDevelopment())
{
// do nothing
return;
}
var result = new ViewResult {ViewName = "CustomError"};
result.ViewData = new ViewDataDictionary(_modelMetadataProvider,context.ModelState);
result.ViewData.Add("Exception", context.Exception);
// TODO: Pass additional detailed data via ViewData
context.Result = result;
}
}
Filtros de exceção não têm dois eventos (para antes e depois); eles implementam somente OnException (ou
OnExceptionAsync ).

Filtros de exceção tratam exceções sem tratamento que ocorrem na criação do controlador, associação de
modelos, filtros de ação ou métodos de ação. Eles não capturam exceções que ocorrem em Filtros de
recurso, Filtros de resultado ou na Execução do resultado do MVC.
Para tratar uma exceção, defina a propriedade ExceptionContext.ExceptionHandled como verdadeiro ou
grave uma resposta. Isso interrompe a propagação da exceção. Observe que um filtro de exceção não pode
transformar uma exceção em um "sucesso". Somente um filtro de ação pode fazer isso.

OBSERVAÇÃO
No ASP.NET 1.1, a resposta não será enviada se você definir ExceptionHandled como verdadeiro e gravar uma
resposta. Nesse cenário, o ASP.NET Core 1.0 envia a resposta e o ASP.NET Core 1.1.2 retorna ao comportamento do
1.0. Para obter mais informações, consulte problema #5594 no repositório do GitHub.

Filtros de exceção são bons para interceptar exceções que ocorrem em ações do MVC, mas não são tão
flexíveis quanto o middleware de tratamento de erro. Dê preferência ao uso de middleware para o caso
geral e use filtros apenas quando precisar fazer o tratamento de erro de modo diferente, dependendo da
ação do MVC escolhida. Por exemplo, seu aplicativo pode ter métodos de ação para os pontos de
extremidade da API e para modos de exibição/HTML. Os pontos de extremidade da API podem retornar
informações de erro como JSON, enquanto as ações baseadas em modo de exibição podem retornar uma
página de erro como HTML.
A estrutura fornece um ExceptionFilterAttribute abstrato que você pode colocar em uma subclasse.

Filtros de resultado
Filtros de resultado implementam a interface IResultFilter ou IAsyncResultFilter e sua execução
envolve a execução de resultados da ação.
Veja um exemplo de um filtro de resultado que adiciona um cabeçalho HTTP.

public class AddHeaderFilterWithDi : IResultFilter


{
private ILogger _logger;
public AddHeaderFilterWithDi(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<AddHeaderFilterWithDi>();
}

public void OnResultExecuting(ResultExecutingContext context)


{
var headerName = "OnResultExecuting";
context.HttpContext.Response.Headers.Add(
headerName, new string[] { "ResultExecutingSuccessfully" });
_logger.LogInformation($"Header added: {headerName}");
}

public void OnResultExecuted(ResultExecutedContext context)


{
// Can't add to headers here because response has already begun.
}
}

O tipo de resultado que está sendo executado depende da ação em questão. Uma ação de MVC que
retorna um modo de exibição incluiria todo o processamento de Razor como parte do ViewResult em
execução. Um método de API pode executar alguma serialização como parte da execução do resultado.
Saiba mais sobre resultados de ação
Filtros de resultado são executados somente para resultados bem-sucedidos – quando a ação ou filtros da
ação produzem um resultado de ação. Filtros de resultado não são executados quando filtros de exceção
tratam uma exceção.
O método OnResultExecuting pode fazer o curto-circuito da execução do resultado da ação e dos filtros de
resultados posteriores definindo ResultExecutingContext.Cancel como verdadeiro. De modo geral, você
deve gravar no objeto de resposta ao fazer um curto-circuito para evitar gerar uma resposta vazia.
Apresentar uma exceção também impede a execução do resultado da ação e dos filtros posteriores, mas
isso é tratado como uma falha, e não como um resultado bem-sucedido.
Quando o método OnResultExecuted é executado, a resposta provavelmente foi enviada ao cliente e não
pode mais ser alterada (a menos que uma exceção tenha sido apresentada).
ResultExecutedContext.Canceled será definido como verdadeiro se a execução do resultado da ação tiver
sofrido curto-circuito por outro filtro.
ResultExecutedContext.Exception será definido como um valor não nulo se o resultado da ação ou um filtro
de resultado posterior tiver apresentado uma exceção. Definir Exception para como nulo “trata” uma
exceção com eficiência e impede que a exceção seja apresentada novamente pelo MVC posteriormente no
pipeline. Quando está tratando uma exceção em um filtro de resultado, talvez você não possa gravar dados
na resposta. Se o resultado da ação for apresentado durante sua execução e os cabeçalhos já tiverem sido
liberados para o cliente, não haverá nenhum mecanismo confiável para enviar um código de falha.
Para um IAsyncResultFilter , uma chamada para await next() no ResultExecutionDelegate executa
qualquer filtro de resultado posterior e o resultado da ação. Para fazer um curto-circuito, defina
ResultExecutingContext.Cancel para verdadeiro e não chame ResultExectionDelegate .

A estrutura fornece um ResultFilterAttribute abstrato que você pode colocar em uma subclasse. A classe
AddHeaderAttribute mostrada anteriormente é um exemplo de atributo de filtro de resultado.

Usando middleware no pipeline de filtros


Filtros de recursos funcionam como middleware, no sentido em que envolvem a execução de tudo o que
vem depois no pipeline. Mas os filtros diferem do middleware porque fazem parte do MVC, o que significa
que eles têm acesso ao contexto e a constructos do MVC.
No ASP.NET Core 1.1, você pode usar o middleware no pipeline de filtros. Talvez você queira fazer isso se
tiver um componente de middleware que precisa acessar dados de rota do MVC ou que precisa ser
executado somente para determinados controladores ou ações.
Para usar o middleware como um filtro, crie um tipo com um método Configure que especifica o
middleware que você deseja injetar no pipeline de filtros. Veja um exemplo que usa o middleware de
localização para estabelecer a cultura atual para uma solicitação:
public class LocalizationPipeline
{
public void Configure(IApplicationBuilder applicationBuilder)
{
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("fr")
};

var options = new RequestLocalizationOptions


{

DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US"),


SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
};
options.RequestCultureProviders = new[]
{ new RouteDataRequestCultureProvider() { Options = options } };

applicationBuilder.UseRequestLocalization(options);
}
}

Depois, você pode usar o MiddlewareFilterAttribute para executar o middleware para um controlador ou
ação selecionada ou para executá-lo globalmente:

[Route("{culture}/[controller]/[action]")]
[MiddlewareFilter(typeof(LocalizationPipeline))]
public IActionResult CultureFromRouteData()
{
return Content($"CurrentCulture:{CultureInfo.CurrentCulture.Name},"
+ "CurrentUICulture:{CultureInfo.CurrentUICulture.Name}");
}

Filtros de middleware são executados no mesmo estágio do pipeline de filtros que filtros de recurso, antes
da associação de modelos e depois do restante do pipeline.

Próximas ações
Para fazer experiências com filtros, baixe, teste e modifique o exemplo.
Áreas
08/02/2018 • 9 min to read • Edit Online

Por Dhananjay Kumar e Rick Anderson


Áreas são um recurso do ASP.NET MVC usado para organizar funcionalidades relacionadas em um grupo como
um namespace separado (para roteamento) e uma estrutura de pastas (para exibições). O uso de áreas cria uma
hierarquia para fins de roteamento, adicionando outro parâmetro de rota, area , a controller e action .
As áreas fornecem uma maneira de particionar um aplicativo Web ASP.NET Core MVC grande em
agrupamentos funcionais menores. Uma área é efetivamente uma estrutura MVC dentro de um aplicativo. Em
um projeto MVC, componentes lógicos como Modelo, Controlador e Exibição são mantidos em pastas
diferentes e o MVC usa convenções de nomenclatura para criar a relação entre esses componentes. Para um
aplicativo grande, pode ser vantajoso particionar o aplicativo em áreas de nível alto separadas de funcionalidade.
Por exemplo, um aplicativo de comércio eletrônico com várias unidades de negócios, como check-out, cobrança
e pesquisa, etc. Cada uma dessas unidades têm suas próprias exibições de componente lógico, controladores e
modelos. Nesse cenário, você pode usar Áreas para particionar fisicamente os componentes de negócios no
mesmo projeto.
Uma área pode ser definida como as menores unidades funcionais em um projeto do ASP.NET Core MVC com
seu próprio conjunto de controladores, exibições e modelos.
Considere o uso de Áreas em um projeto MVC quando:
O aplicativo é composto por vários componentes funcionais de alto nível que devem ser separados
logicamente
Você deseja particionar o projeto MVC para que cada área funcional possa ser trabalhada de forma
independente
Recursos de área:
Um aplicativo ASP.NET Core MVC pode ter qualquer quantidade de áreas
Cada área tem seus próprios controladores, modelos e exibições
Permite organizar projetos MVC grandes em vários componentes de alto nível que podem ser
trabalhados de forma independente
Dá suporte a vários controladores com o mesmo nome – desde que eles tenham áreas diferentes
Vamos dar uma olhada em um exemplo para ilustrar como as Áreas são criadas e usadas. Digamos que você
tenha um aplicativo de loja que tem dois agrupamentos distintos de controladores e exibições: Produtos e
Serviços. Uma estrutura de pastas comum para isso usando áreas do MVC tem a aparência mostrada abaixo:
Nome do projeto
Áreas
Produtos
Controladores
HomeController.cs
ManageController.cs
Exibições
Home
Index.cshtml
Gerenciar
Index.cshtml
Serviços
Controladores
HomeController.cs
Exibições
Home
Index.cshtml
Quando o MVC tenta renderizar uma exibição em uma Área, por padrão, ele tenta procurar nos seguintes locais:

/Areas/<Area-Name>/Views/<Controller-Name>/<Action-Name>.cshtml
/Areas/<Area-Name>/Views/Shared/<Action-Name>.cshtml
/Views/Shared/<Action-Name>.cshtml

Esses são os locais padrão que podem ser alterados por meio do AreaViewLocationFormats no
Microsoft.AspNetCore.Mvc.Razor.RazorViewEngineOptions .

Por exemplo, no código abaixo, em vez de ter o nome da pasta como 'Areas', ele foi alterado para 'Categories'.

services.Configure<RazorViewEngineOptions>(options =>
{
options.AreaViewLocationFormats.Clear();
options.AreaViewLocationFormats.Add("/Categories/{2}/Views/{1}/{0}.cshtml");
options.AreaViewLocationFormats.Add("/Categories/{2}/Views/Shared/{0}.cshtml");
options.AreaViewLocationFormats.Add("/Views/Shared/{0}.cshtml");
});

Uma coisa importante a ser observada é que a estrutura da pasta Views é a única que é considerada importante
aqui e, o conteúdo do restante das pastas como Controllers e Models não importa. Por exemplo, você não
precisa ter uma pasta Controllers e Models. Isso funciona porque o conteúdo de Controllers e Models é apenas
um código que é compilado em uma .dll, enquanto o conteúdo de Views não é, até que uma solicitação para essa
exibição seja feita.
Depois de definir a hierarquia de pastas, você precisa informar ao MVC que cada controlador está associado a
uma área. Faça isso decorando o nome do controlador com o atributo [Area] .
...
namespace MyStore.Areas.Products.Controllers
{
[Area("Products")]
public class HomeController : Controller
{
// GET: /Products/Home/Index
public IActionResult Index()
{
return View();
}

// GET: /Products/Home/Create
public IActionResult Create()
{
return View();
}
}
}

Configure uma definição de rota que funciona com as áreas recém-criadas. O artigo Roteamento para ações do
controlador apresenta detalhes sobre como criar definições de rota, incluindo o uso de rotas convencionais
versus rotas de atributo. Neste exemplo, usaremos uma rota convencional. Para fazer isso, abra o arquivo
Startup.cs e modifique-o adicionando a definição de rota nomeada areaRoute abaixo.

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

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

Navegando para http://<yourApp>/products , o método de ação Index do HomeController na área Products


será invocado.

Geração de link
Geração de links em uma ação dentro de um controlador baseado em área para outra ação no mesmo
controlador.
Digamos que o caminho da solicitação atual seja semelhante a /Products/Home/Create

Sintaxe de HtmlHelper: @Html.ActionLink("Go to Product's Home Page", "Index")

Sintaxe de TagHelper: <a asp-action="Index">Go to Product's Home Page</a>

Observe que não é necessário fornecer os valores 'area' e 'controller' aqui, pois já estão disponíveis no
contexto da solicitação atual. Esses tipos de valores são chamados valores ambient .
Geração de links em uma ação dentro de um controlador baseado em área para outra ação em outro
controlador
Digamos que o caminho da solicitação atual seja semelhante a /Products/Home/Create

Sintaxe de HtmlHelper: @Html.ActionLink("Go to Manage Products Home Page", "Index", "Manage")


Sintaxe de TagHelper:
<a asp-controller="Manage" asp-action="Index">Go to Manage Products Home Page</a>

Observe que aqui o valor de ambiente de uma "area" é usado, mas o valor de 'controller' é especificado
de forma explícita acima.
Geração de links em uma ação dentro de um controlador baseado em área para outra ação em outro
controlador e outra área.
Digamos que o caminho da solicitação atual seja semelhante a /Products/Home/Create

Sintaxe de HtmlHelper:
@Html.ActionLink("Go to Services Home Page", "Index", "Home", new { area = "Services" })

Sintaxe de TagHelper:
<a asp-area="Services" asp-controller="Home" asp-action="Index">Go to Services Home Page</a>

Observe que aqui nenhum valor de ambiente é usado.


Geração de links em uma ação dentro de um controlador baseado em área para outra ação em outro
controlador e não em uma área.
Sintaxe de HtmlHelper:
@Html.ActionLink("Go to Manage Products Home Page", "Index", "Home", new { area = "" })

Sintaxe de TagHelper:
<a asp-area="" asp-controller="Manage" asp-action="Index">Go to Manage Products Home Page</a>

Como queremos gerar links para uma ação do controlador não baseada em área, esvaziamos o valor de
ambiente para 'area' aqui.

Publicando áreas
Todos os arquivos *.cshtml e wwwroot/** são publicados na saída quando
<Project Sdk="Microsoft.NET.Sdk.Web"> é incluído no arquivo .csproj.
Partes do aplicativo no ASP.NET Core
08/02/2018 • 7 min to read • Edit Online

Exibir ou baixar código de exemplo (como baixar)


Uma Parte do aplicativo é uma abstração dos recursos de um aplicativo, da qual recursos de MVC como
controladores, componentes de exibição ou auxiliares de marca podem ser descobertos. Um exemplo de uma
parte do aplicativo é um AssemblyPart, que encapsula uma referência de assembly e expõe tipos e referências de
compilação. Provedores de recursos funcionam com as partes do aplicativo para preencher os recursos de um
aplicativo do ASP.NET Core MVC. O caso de uso principal das partes do aplicativo é permitir que você configure
seu aplicativo para descobrir (ou evitar o carregamento) recursos de MVC de um assembly.

Introdução às partes do aplicativo


Aplicativos de MVC carregam seus recursos das partes do aplicativo. Em particular, a classe AssemblyPart
representa uma parte de aplicativo que é apoiada por um assembly. Você pode usar essas classes para descobrir e
carregar recursos de MVC, como controladores, componentes de exibição, auxiliares de marca e fontes de
compilação do Razor. O ApplicationPartManager é responsável por controlar as partes do aplicativo e os
provedores de recursos disponíveis para o aplicativo MVC. Você pode interagir com o ApplicationPartManager em
Startup quando configura o MVC:

// create an assembly part from a class's assembly


var assembly = typeof(Startup).GetTypeInfo().Assembly;
services.AddMvc()
.AddApplicationPart(assembly);

// OR
var assembly = typeof(Startup).GetTypeInfo().Assembly;
var part = new AssemblyPart(assembly);
services.AddMvc()
.ConfigureApplicationPartManager(apm => p.ApplicationParts.Add(part));

Por padrão, o MVC pesquisa a árvore de dependência e localiza controladores (mesmo em outros assemblies).
Para carregar um assembly arbitrário (por exemplo, de um plug-in que não é referenciado em tempo de
compilação), você pode usar uma parte do aplicativo.
Você pode usar as partes do aplicativo para evitar pesquisar controladores em um determinado assembly ou local.
Você pode controlar quais partes (ou assemblies) ficam disponíveis para o aplicativo modificando a coleção
ApplicationParts do ApplicationPartManager . A ordem das entradas na coleção ApplicationParts não é
importante. É importante configurar totalmente o ApplicationPartManager antes de usá-lo para configurar serviços
no contêiner. Por exemplo, você deve configurar totalmente o ApplicationPartManager antes de invocar
AddControllersAsServices . Deixar de fazer isso significará que os controladores em partes do aplicativo
adicionadas depois da chamada de método não serão afetados (não serão registrados como serviços), o que pode
resultar em um comportamento incorreto de seu aplicativo.
Se tiver um assembly que contém controladores que você não deseja usar, remova-o do ApplicationPartManager :
services.AddMvc()
.ConfigureApplicationPartManager(p =>
{
var dependentLibrary = p.ApplicationParts
.FirstOrDefault(part => part.Name == "DependentLibrary");

if (dependentLibrary != null)
{
p.ApplicationParts.Remove(dependentLibrary);
}
})

Além do assembly de seu projeto e de seus assemblies dependentes, o ApplicationPartManager incluirá partes
para Microsoft.AspNetCore.Mvc.TagHelpers e Microsoft.AspNetCore.Mvc.Razor por padrão.

Provedores de recursos do aplicativo


Os Provedores de recursos do aplicativo examinam as partes do aplicativo e fornece recursos para essas partes.
Há provedores de recursos internos para os seguintes recursos de MVC:
Controladores
Referência de metadados
Auxiliares de marcação
Componentes da exibição
Provedores de recursos herdam de IApplicationFeatureProvider<T> , em que T é o tipo do recurso. Você pode
implementar seus próprios provedores de recursos para qualquer um dos tipos de recurso do MVC listados acima.
A ordem dos provedores de recursos na coleção ApplicationPartManager.FeatureProviders pode ser importante,
pois provedores posteriores podem reagir às ações tomadas por provedores anteriores.
Exemplo: recurso de controlador genérico
Por padrão, o ASP.NET Core MVC ignora controladores genéricos (por exemplo, SomeController<T> ). Este
exemplo usa um provedor de recursos de controlador que é executado depois do provedor padrão e adiciona
instâncias de controlador genérico a uma lista de tipos especificados (definidos em EntityTypes.Types ):

public class GenericControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>


{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
// This is designed to run after the default ControllerTypeProvider,
// so the list of 'real' controllers has already been populated.
foreach (var entityType in EntityTypes.Types)
{
var typeName = entityType.Name + "Controller";
if (!feature.Controllers.Any(t => t.Name == typeName))
{
// There's no 'real' controller for this entity, so add the generic version.
var controllerType = typeof(GenericController<>)
.MakeGenericType(entityType.AsType()).GetTypeInfo();
feature.Controllers.Add(controllerType);
}
}
}
}

Os tipos de entidade:
public static class EntityTypes
{
public static IReadOnlyList<TypeInfo> Types => new List<TypeInfo>()
{
typeof(Sprocket).GetTypeInfo(),
typeof(Widget).GetTypeInfo(),
};

public class Sprocket { }


public class Widget { }
}

O provedor de recursos é adicionado em Startup :

services.AddMvc()
.ConfigureApplicationPartManager(p =>
p.FeatureProviders.Add(new GenericControllerFeatureProvider()));

Por padrão, os nomes do controlador genérico usados para roteamento seriam no formato GenericController'1
[Widget] em vez de Widget. O atributo a seguir é usado para modificar o nome para que ele corresponda ao tipo
genérico usado pelo controlador:

using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System;

namespace AppPartsSample
{
// Used to set the controller name for routing purposes. Without this convention the
// names would be like 'GenericController`1[Widget]' instead of 'Widget'.
//
// Conventions can be applied as attributes or added to MvcOptions.Conventions.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class GenericControllerNameConvention : Attribute, IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
if (controller.ControllerType.GetGenericTypeDefinition() !=
typeof(GenericController<>))
{
// Not a GenericController, ignore.
return;
}

var entityType = controller.ControllerType.GenericTypeArguments[0];


controller.ControllerName = entityType.Name;
}
}
}

A classe GenericController :
using Microsoft.AspNetCore.Mvc;

namespace AppPartsSample
{
[GenericControllerNameConvention] // Sets the controller name based on typeof(T).Name
public class GenericController<T> : Controller
{
public IActionResult Index()
{
return Content($"Hello from a generic {typeof(T).Name} controller.");
}
}
}

O resultado, quando uma rota correspondente é solicitada:

Exemplo: exibir recursos disponíveis


Você pode iterar nos recursos preenchidos disponíveis para seu aplicativo solicitando um ApplicationPartManager
por meio da injeção de dependência e usando-o para preencher as instâncias dos recursos apropriados:
using AppPartsSample.ViewModels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewComponents;

namespace AppPartsSample.Controllers
{
public class FeaturesController : Controller
{
private readonly ApplicationPartManager _partManager;

public FeaturesController(ApplicationPartManager partManager)


{
_partManager = partManager;
}

public IActionResult Index()


{
var viewModel = new FeaturesViewModel();

var controllerFeature = new ControllerFeature();


_partManager.PopulateFeature(controllerFeature);
viewModel.Controllers = controllerFeature.Controllers.ToList();

var metaDataReferenceFeature = new MetadataReferenceFeature();


_partManager.PopulateFeature(metaDataReferenceFeature);
viewModel.MetadataReferences = metaDataReferenceFeature.MetadataReferences
.ToList();

var tagHelperFeature = new TagHelperFeature();


_partManager.PopulateFeature(tagHelperFeature);
viewModel.TagHelpers = tagHelperFeature.TagHelpers.ToList();

var viewComponentFeature = new ViewComponentFeature();


_partManager.PopulateFeature(viewComponentFeature);
viewModel.ViewComponents = viewComponentFeature.ViewComponents.ToList();

return View(viewModel);
}
}
}

Saída de exemplo:
Associação de modelos personalizada
08/02/2018 • 12 min to read • Edit Online

Por Steve Smith


A associação de modelos permite que as ações do controlador funcionem diretamente com tipos de modelo
(passados como argumentos de método), em vez de solicitações HTTP. O mapeamento entre os dados de
solicitação de entrada e os modelos de aplicativo é manipulado por associadores de modelos. Os desenvolvedores
podem estender a funcionalidade de associação de modelos interna implementando associadores de modelos
personalizados (embora, normalmente, você não precise escrever seu próprio provedor).
Exibir ou baixar a amostra do GitHub

Limitações dos associadores de modelos padrão


Os associadores de modelos padrão dão suporte à maioria dos tipos de dados comuns do .NET Core e devem
atender à maior parte das necessidades dos desenvolvedores. Eles esperam associar a entrada baseada em texto
da solicitação diretamente a tipos de modelo. Talvez seja necessário transformar a entrada antes de associá-la. Por
exemplo, quando você tem uma chave que pode ser usada para pesquisar dados de modelo. Use um associador
de modelos personalizado para buscar dados com base na chave.

Análise da associação de modelos


A associação de modelos usa definições específicas para os tipos nos quais opera. Um tipo simples é convertido
de uma única cadeia de caracteres na entrada. Um tipo complexo é convertido de vários valores de entrada. A
estrutura determina a diferença de acordo com a existência de um TypeConverter . Recomendamos que você crie
um conversor de tipo se tiver um mapeamento string -> SomeType simples que não exige recursos externos.
Antes de criar seu próprio associador de modelos personalizado, vale a pena analisar como os associadores de
modelos existentes são implementados. Considere o ByteArrayModelBinder, que pode ser usado para converter
cadeias de caracteres codificadas em Base64 em matrizes de bytes. As matrizes de bytes costumam ser
armazenadas como arquivos ou campos BLOB do banco de dados.
Trabalhando com o ByteArrayModelBinder
Cadeias de caracteres codificadas em Base64 podem ser usadas para representar dados binários. Por exemplo, a
imagem a seguir pode ser codificada como uma cadeia de caracteres.

Uma pequena parte da cadeia de caracteres codificada é mostrada na seguinte imagem:


Siga as instruções do LEIAME da amostra para converter a cadeia de caracteres codificada em Base64 em um
arquivo.
O ASP.NET Core MVC pode usar uma cadeia de caracteres codificada em Base64 e usar um
ByteArrayModelBinder para convertê-la em uma matriz de bytes. O ByteArrayModelBinderProvider que
implementa IModelBinderProvider mapeia argumentos byte[] para ByteArrayModelBinder :

public IModelBinder GetBinder(ModelBinderProviderContext context)


{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}

if (context.Metadata.ModelType == typeof(byte[]))
{
return new ByteArrayModelBinder();
}

return null;
}

Ao criar seu próprio associador de modelos personalizado, você pode implementar seu próprio tipo
IModelBinderProvider ou usar o ModelBinderAttribute.

O seguinte exemplo mostra como usar ByteArrayModelBinder para converter uma cadeia de caracteres codificada
em Base64 em um byte[] e salvar o resultado em um arquivo:
// POST: api/image
[HttpPost]
public void Post(byte[] file, string filename)
{
string filePath = Path.Combine(_env.ContentRootPath, "wwwroot/images/upload", filename);
if (System.IO.File.Exists(filePath)) return;
System.IO.File.WriteAllBytes(filePath, file);
}

Execute POST em uma cadeia de caracteres codificada em Base64 para esse método de API usando uma
ferramenta como o Postman:

Desde que o associador possa associar dados de solicitação a propriedades ou argumentos nomeados de forma
adequada, a associação de modelos terá êxito. O seguinte exemplo mostra como usar ByteArrayModelBinder com
um modelo de exibição:

[HttpPost("Profile")]
public void SaveProfile(ProfileViewModel model)
{
string filePath = Path.Combine(_env.ContentRootPath, "wwwroot/images/upload", model.FileName);
if (System.IO.File.Exists(model.FileName)) return;
System.IO.File.WriteAllBytes(filePath, model.File);
}

public class ProfileViewModel


{
public byte[] File { get; set; }
public string FileName { get; set; }
}

Amostra de associador de modelos personalizado


Nesta seção, implementaremos um associador de modelos personalizado que:
Converte dados de solicitação de entrada em argumentos de chave fortemente tipados.
Usa o Entity Framework Core para buscar a entidade associada.
Passa a entidade associada como um argumento para o método de ação.
A seguinte amostra usa o atributo ModelBinder no modelo Author :

using CustomModelBindingSample.Binders;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace CustomModelBindingSample.Data
{
[ModelBinder(BinderType = typeof(AuthorEntityBinder))]
public class Author
{
public int Id { get; set; }
public string Name { get; set; }
public string GitHub { get; set; }
public string Twitter { get; set; }
public string BlogUrl { get; set; }
}
}

No código anterior, o atributo ModelBinder especifica o tipo de IModelBinder que deve ser usado para associar
parâmetros de ação Author .
O AuthorEntityBinder é usado para associar um parâmetro Author buscando a entidade de uma fonte de dados
usando o Entity Framework Core e uma authorId :
public class AuthorEntityBinder : IModelBinder
{
private readonly AppDbContext _db;
public AuthorEntityBinder(AppDbContext db)
{
_db = db;
}

public Task BindModelAsync(ModelBindingContext bindingContext)


{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}

// Specify a default argument name if none is set by ModelBinderAttribute


var modelName = bindingContext.BinderModelName;
if (string.IsNullOrEmpty(modelName))
{
modelName = "authorId";
}

// Try to fetch the value of the argument by name


var valueProviderResult =
bindingContext.ValueProvider.GetValue(modelName);

if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}

bindingContext.ModelState.SetModelValue(modelName,
valueProviderResult);

var value = valueProviderResult.FirstValue;

// Check if the argument value is null or empty


if (string.IsNullOrEmpty(value))
{
return Task.CompletedTask;
}

int id = 0;
if (!int.TryParse(value, out id))
{
// Non-integer arguments result in model state errors
bindingContext.ModelState.TryAddModelError(
bindingContext.ModelName,
"Author Id must be an integer.");
return Task.CompletedTask;
}

// Model will be null if not found, including for


// out of range id values (0, -3, etc.)
var model = _db.Authors.Find(id);
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}

O seguinte código mostra como usar o AuthorEntityBinder em um método de ação:


[HttpGet("get/{authorId}")]
public IActionResult Get(Author author)
{
return Ok(author);
}

O atributo ModelBinder pode ser usado para aplicar o AuthorEntityBinder aos parâmetros que não usam
convenções padrão:

[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")]Author author)
{
if (author == null)
{
return NotFound();
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
return Ok(author);
}

Neste exemplo, como o nome do argumento não é o authorId padrão, ele é especificado no parâmetro com o
atributo ModelBinder . Observe que o controlador e o método de ação são simplificados, comparado à pesquisa da
entidade no método de ação. A lógica para buscar o autor usando o Entity Framework Core é movida para o
associador de modelos. Isso pode ser uma simplificação considerável quando há vários métodos associados ao
modelo do autor e pode ajudá-lo a seguir o princípio DRY.
Aplique o atributo ModelBinder a propriedades de modelo individuais (como em um viewmodel) ou a parâmetros
de método de ação para especificar um associador de modelos ou nome de modelo específico para apenas esse
tipo ou essa ação.
Implementando um ModelBinderProvider
Em vez de aplicar um atributo, você pode implementar IModelBinderProvider . É assim que os associadores de
estrutura interna são implementados. Quando você especifica o tipo no qual o associador opera, você especifica o
tipo de argumento que ele produz, não a entrada aceita pelo associador. O provedor de associador a seguir
funciona com o AuthorEntityBinder . Quando ele é adicionado à coleção do MVC de provedores, você não precisa
usar o atributo ModelBinder em Author ou parâmetros tipados Author .
using CustomModelBindingSample.Data;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System;

namespace CustomModelBindingSample.Binders
{
public class AuthorEntityBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}

if (context.Metadata.ModelType == typeof(Author))
{
return new BinderTypeModelBinder(typeof(AuthorEntityBinder));
}

return null;
}
}
}

Observação: o código anterior retorna um BinderTypeModelBinder . O BinderTypeModelBinder atua como um


alocador para associadores de modelos e fornece a DI (injeção de dependência). O AuthorEntityBinder exige
que a DI acesse o EF Core. Use BinderTypeModelBinder se o associador de modelos exigir serviços da DI.

Para usar um provedor de associador de modelos personalizado, adicione-o a ConfigureServices :

public void ConfigureServices(IServiceCollection services)


{
services.AddDbContext<AppDbContext>(options => options.UseInMemoryDatabase());

services.AddMvc(options =>
{
// add custom binder to beginning of collection
options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
});
}

Ao avaliar associadores de modelos, a coleção de provedores é examinada na ordem. O primeiro provedor que
retorna um associador é usado.
A imagem a seguir mostra os associadores de modelos padrão do depurador.
A adição do provedor ao final da coleção pode resultar na chamada a um associador de modelos interno antes
que o associador personalizado tenha uma oportunidade. Neste exemplo, o provedor personalizado é adicionado
ao início da coleção para garantir que ele é usado para argumentos de ação Author .

public void ConfigureServices(IServiceCollection services)


{
services.AddDbContext<AppDbContext>(options => options.UseInMemoryDatabase());

services.AddMvc(options =>
{
// add custom binder to beginning of collection
options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
});
}

Recomendações e melhores práticas


Associadores de modelos personalizados:
Não devem tentar definir códigos de status ou retornar resultados (por exemplo, 404 Não Encontrado). Se a
associação de modelos falhar, um filtro de ação ou uma lógica no próprio método de ação deverá resolver a
falha.
São muito úteis para eliminar código repetitivo e interesses paralelos de métodos de ação.
Normalmente, não devem ser usados para converter uma cadeia de caracteres em um tipo personalizado; um
TypeConverter geralmente é uma opção melhor.
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
Tópicos avançados sobre a API Web do ASP.NET
Core
28/04/2018 • 1 min to read • Edit Online

Formatadores personalizados
Formatar dados de resposta
Testar e depurar no ASP.NET Core
28/04/2018 • 1 min to read • Edit Online

Teste de unidade
Testes de integração
Testes de integração e unidade de Páginas Razor
Controladores de teste
Depurar código-fonte do ASP.NET Core 2.x
Depuração remota
Depuração de instantâneo
YouTube: diagnosticar problemas em aplicativos do ASP.NET Core
Testes de integração no núcleo do ASP.NET
10/04/2018 • 14 min to read • Edit Online

Por Steve Smith


Testes de integração garantem que componentes de um aplicativo funcionam corretamente quando montado
juntos. O ASP.NET Core é compatível com testes de integração por meio de estruturas de teste de unidade e um
host Web de testes interno que pode ser utilizado para lidar com solicitações sem sobrecarga de rede.
Exibir ou baixar código de exemplo (como baixar)

Introdução ao teste de integração


Testes de integração verificar que diferentes partes de um aplicativo funcionem corretamente. Ao contrário de
testes de unidade, testes de integração com frequência envolvem preocupações de infraestrutura de aplicativo,
como um banco de dados, sistema de arquivos, recursos de rede, ou web solicitações e respostas. Testes de
unidade usam falsificações ou objetos fictícios no lugar essas preocupações, mas a finalidade de testes de
integração é confirmar se o sistema está funcionando conforme o esperado com esses sistemas.
Testes de integração, porque praticados maior de segmentos de código e como eles se basear em elementos de
infraestrutura, tendem a ser ordens de magnitude menor do que testes de unidade. Portanto, é uma boa ideia
limitar quantos testes de integração que você escreve, especialmente se você pode testar o mesmo
comportamento com um teste de unidade.

OBSERVAÇÃO
Se algum comportamento pode ser testado usando um teste de unidade ou um teste de integração, prefira o teste de
unidade, já que ele é quase sempre ser mais rápido. Você pode ter dúzias ou centenas de testes de unidade com muitas
entradas diferentes, mas apenas uma série de testes de integração que cobrem os cenários mais importantes.

A lógica dentro de seus próprios métodos de teste é geralmente o domínio de testes de unidade. Testar o
funcionamento do seu aplicativo em sua estrutura, por exemplo, com ASP.NET Core, ou com um banco de dados
é onde os testes de integração entram em ação. Ele não tem muitos testes de integração para confirmar que você
é capaz de gravar uma linha para o banco de dados e lê-lo novamente. Não é necessário testar cada permutação
possíveis do seu código de acesso de dados - você precisa testar suficiente para que você a certeza de que seu
aplicativo está funcionando corretamente.

Integração de teste ASP.NET Core


Para obter configurou a testes de integração de execução, você precisará criar um projeto de teste, adicione uma
referência ao projeto web ASP.NET Core e instalar um executor de teste. Esse processo é descrito no testes de
unidade documentação, junto com instruções mais detalhadas sobre como executar testes e recomendações para
nomear suas classes de teste e testes.

OBSERVAÇÃO
Separe os testes de unidade e os testes de integração usando projetos diferentes. Isso ajuda a garantir que você não
acidentalmente introduzir problemas de infraestrutura em seus testes de unidade e permite que você escolher facilmente
qual conjunto de testes para executar.
O Host de teste
ASP.NET Core inclui um host de teste que pode ser adicionado a projetos de teste de integração e usado para
hospedar o ASP.NET Core aplicativos, solicitações de serviço de teste sem a necessidade de um host da web real.
O exemplo fornecido inclui um projeto de teste de integração que foi configurado para usar xUnit e o Host de
teste. Ele usa o Microsoft.AspNetCore.TestHost pacote NuGet.
Uma vez o Microsoft.AspNetCore.TestHost pacote está incluído no projeto, você poderá criar e configurar um
TestServer em seus testes. O teste a seguir mostra como verificar uma solicitação feita para a raiz de um site
retorna "Hello World!" e deve ser executado com êxito em relação ao padrão modelo de Web do ASP.NET Core
vazio criado pelo Visual Studio.

public class PrimeWebDefaultRequestShould


{
private readonly TestServer _server;
private readonly HttpClient _client;
public PrimeWebDefaultRequestShould()
{
// Arrange
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>());
_client = _server.CreateClient();
}

[Fact]
public async Task ReturnHelloWorld()
{
// Act
var response = await _client.GetAsync("/");
response.EnsureSuccessStatusCode();

var responseString = await response.Content.ReadAsStringAsync();

// Assert
Assert.Equal("Hello World!",
responseString);
}
}

Este teste é usando o padrão de organizar Act declaração. A etapa de organizar é feita no construtor, que cria
uma instância de TestServer . Configurado WebHostBuilder será usado para criar um TestHost ; neste exemplo, o
Configure método do sistema em teste ( SUT ) Startup classe é passada para o WebHostBuilder . Esse método
será usado para configurar o pipeline de solicitação do TestServer identicamente a como o servidor SUT deve
ser configurado.
Na parte do Act do teste, é feita uma solicitação para a TestServer instância para o caminho "/" e a resposta é
lido novamente em uma cadeia de caracteres. Essa cadeia de caracteres é comparada com a cadeia de caracteres
esperada de "Hello World!". Se elas corresponderem, o teste seja aprovado; Caso contrário, ele falhará.
Agora você pode adicionar alguns testes de integração adicionais para confirmar que o primo verificando
funcionalidade funciona por meio do aplicativo web:
public class PrimeWebCheckPrimeShould
{
private readonly TestServer _server;
private readonly HttpClient _client;
public PrimeWebCheckPrimeShould()
{
// Arrange
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>());
_client = _server.CreateClient();
}

private async Task<string> GetCheckPrimeResponseString(


string querystring = "")
{
var request = "/checkprime";
if(!string.IsNullOrEmpty(querystring))
{
request += "?" + querystring;
}
var response = await _client.GetAsync(request);
response.EnsureSuccessStatusCode();

return await response.Content.ReadAsStringAsync();


}

[Fact]
public async Task ReturnInstructionsGivenEmptyQueryString()
{
// Act
var responseString = await GetCheckPrimeResponseString();

// Assert
Assert.Equal("Pass in a number to check in the form /checkprime?5",
responseString);
}
[Fact]
public async Task ReturnPrimeGiven5()
{
// Act
var responseString = await GetCheckPrimeResponseString("5");

// Assert
Assert.Equal("5 is prime!",
responseString);
}

[Fact]
public async Task ReturnNotPrimeGiven6()
{
// Act
var responseString = await GetCheckPrimeResponseString("6");

// Assert
Assert.Equal("6 is NOT prime!",
responseString);
}
}

Observe que não é realmente está tentando testar a exatidão do verificador de número primo com esses testes,
mas em vez disso, que o aplicativo web está fazendo o que você espera. Você já tem cobertura de teste de
unidade que proporciona confiança em PrimeService , como você pode ver aqui:
Você pode aprender mais sobre os testes de unidade no testes de unidade artigo.
Integração de teste/Razor do Mvc
Projetos de teste que contêm modos de exibição Razor exigem <PreserveCompilationContext> ser definido como
true no . csproj arquivo:

<PreserveCompilationContext>true</PreserveCompilationContext>

Este elemento de projetos irá gerar um erro semelhante à seguinte:

Microsoft.AspNetCore.Mvc.Razor.Compilation.CompilationFailedException: 'One or more compilation failures


occurred:
ooebhccx.1bd(4,62): error CS0012: The type 'Attribute' is defined in an assembly that is not referenced. You
must add a reference to assembly 'netstandard, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=cc7b13ffcd2ddd51'.

Para usar o middleware de refatoração


Refatoração é o processo de alteração de código do aplicativo para melhorar seu design sem alterar seu
comportamento. Idealmente deve ser feito quando há um conjunto de passar os testes, pois esses ajuda garantir
o que comportamento do sistema permanece a mesma antes e após as alterações. Examinar a maneira na qual o
primo lógica de verificação é implementado no aplicativo da web Configure método, consulte:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.Run(async (context) =>


{
if (context.Request.Path.Value.Contains("checkprime"))
{
int numberToCheck;
try
{
numberToCheck = int.Parse(context.Request.QueryString.Value.Replace("?", ""));
var primeService = new PrimeService();
if (primeService.IsPrime(numberToCheck))
{
await context.Response.WriteAsync($"{numberToCheck} is prime!");
}
else
{
await context.Response.WriteAsync($"{numberToCheck} is NOT prime!");
}
}
catch
{
await context.Response.WriteAsync("Pass in a number to check in the form /checkprime?5");
}
}
else
{
await context.Response.WriteAsync("Hello World!");
}
});
}

Esse código funciona, mas está longe de como você deseja implementar esse tipo de funcionalidade em um
aplicativo ASP.NET Core, até mesmo simple como este é. Imagine que o Configure método seria semelhante, se
necessário para adicionar essa quantidade código para ele sempre que você adicionar outro ponto de
extremidade de URL!
Adição de uma opção a ser considerada MVC para o aplicativo e criando um controlador para lidar com a
verificação principal. No entanto, supondo que você atualmente não precisa de qualquer outra MVC
funcionalidade, que é um bit exageros.
No entanto, você pode tirar proveito do ASP.NET Core middleware, que nos ajudarão a encapsular o primo
verificação lógica em sua própria classe e obter melhor separação de preocupações no Configure método.
Para permitir que o caminho do middleware usa a ser especificado como um parâmetro para a classe de
middleware espera um RequestDelegate e um PrimeCheckerOptions instância em seu construtor. Se o caminho da
solicitação não corresponde ao que este middleware configurado para esperar, basta chamar o próximo
middleware da cadeia e não fazer nada. O restante do código de implementação que estava no Configure está
agora no Invoke método.

OBSERVAÇÃO
Desde que o middleware depende do PrimeService serviço, você também está solicitando uma instância do serviço com
o construtor. A estrutura oferecer esse serviço por meio de injeção de dependência, supondo que ele tiver sido configurado,
por exemplo, em ConfigureServices .
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using PrimeWeb.Services;
using System;
using System.Threading.Tasks;

namespace PrimeWeb.Middleware
{
public class PrimeCheckerMiddleware
{
private readonly RequestDelegate _next;
private readonly PrimeCheckerOptions _options;
private readonly PrimeService _primeService;

public PrimeCheckerMiddleware(RequestDelegate next,


PrimeCheckerOptions options,
PrimeService primeService)
{
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (primeService == null)
{
throw new ArgumentNullException(nameof(primeService));
}

_next = next;
_options = options;
_primeService = primeService;
}

public async Task Invoke(HttpContext context)


{
var request = context.Request;
if (!request.Path.HasValue ||
request.Path != _options.Path)
{
await _next.Invoke(context);
}
else
{
int numberToCheck;
if (int.TryParse(request.QueryString.Value.Replace("?", ""), out numberToCheck))
{
if (_primeService.IsPrime(numberToCheck))
{
await context.Response.WriteAsync($"{numberToCheck} is prime!");
}
else
{
await context.Response.WriteAsync($"{numberToCheck} is NOT prime!");
}
}
else
{
await context.Response.WriteAsync($"Pass in a number to check in the form
{_options.Path}?5");
}
}
}
}
}
Como este middleware atua como um ponto de extremidade da cadeia de delegado solicitação quando o
caminho corresponde, não há nenhuma chamada para _next.Invoke quando este middleware manipula a
solicitação.
Com este middleware em vigor e alguns útil métodos de extensão criados para facilitar a configuração, o
refatorado Configure método tem esta aparência:

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

app.UsePrimeChecker();

app.Run(async (context) =>


{
await context.Response.WriteAsync("Hello World!");
});
}
}

Seguindo essa refatoração estiver certo de que o aplicativo web ainda funciona como antes, desde que os testes
de integração estão passando.

OBSERVAÇÃO
É uma boa ideia para confirmar as alterações ao controle de origem depois de concluir uma refatoração e transmitir seus
testes. Se você está praticando desenvolvimento orientado por testes, considere a adição de confirmação para o ciclo de
vermelho-verde-refatorar.

Recursos
Teste de unidade
Middleware
Controladores de teste
Testes de unidade e a integração de páginas de
Razor em ASP.NET Core
10/04/2018 • 23 min to read • Edit Online

Por Luke Latham


ASP.NET Core dá suporte à unidade e testes de integração de aplicativos de páginas Razor. Teste a camada de
acesso a dados (DAL ), modelos e componentes de página integrada ajuda a garantir:
Partes de um aplicativo de páginas Razor funcionam independentemente e juntos como uma unidade durante
a construção do aplicativo.
Classes e métodos limitada a escopos de responsabilidade.
Existe documentação adicional sobre o comportamento do aplicativo.
Regressões, que são feitos por atualizações para o código de erros, são encontradas durante a implantação e
compilação automatizada.
Este tópico pressupõe que você tenha uma compreensão básica do Razor páginas de aplicativos, teste de
unidade e a integração de teste. Se você não estiver familiarizado com conceitos de teste ou de páginas Razor
aplicativos, consulte os tópicos a seguir:
Introdução a Páginas do Razor
Introdução a Páginas do Razor
Teste de unidade c# no .NET Core usando xUnit e teste dotnet
Testes de integração
Exibir ou baixar código de exemplo (como baixar)
O projeto de exemplo é composto de dois aplicativos:

APLICATIVO PASTA DO PROJETO DESCRIÇÃO

Aplicativo de mensagem src/RazorPagesTestingSample Permite que um usuário adicionar,


excluir um, exclua todas as e analisar as
mensagens.

Aplicativo de teste tests/RazorPagesTestingSample.Tests Usado para testar o aplicativo de


mensagem.
Testes de unidade: camada de
acesso a dados (DAL), modelo
de página de índice
Testes de integração: página de
índice

Os testes podem ser executados usando os recursos internos de teste de um IDE, como Visual Studio. Se usar
código do Visual Studio ou a linha de comando, execute o seguinte comando no prompt de comando no
tests/RazorPagesTestingSample.Tests pasta:

dotnet test
Organização de aplicativo de mensagem
O aplicativo de mensagem é um sistema de mensagem páginas Razor simples com as seguintes características:
A página de índice do aplicativo (Pages/Index.cshtml e Pages/Index.cshtml.cs) fornece uma interface do
usuário e a página de métodos de modelo para controlar a adição, exclusão e análise de mensagens (palavras
médias por mensagem) .
Uma mensagem é descrita pelo Message classe (Data/Message.cs) com duas propriedades: Id (chave) e
Text (mensagem ). O Text propriedade é necessária e limitada a 200 caracteres.
As mensagens são armazenadas usando banco de dados do Entity Framework na memória†.
O aplicativo contém uma camada de acesso de dados (DAL ) em sua classe de contexto de banco de dados,
AppDbContext ( Data/AppDbContext.cs). Os métodos DAL são marcados como virtual , que permite a
simulação de métodos para uso em testes.
Se o banco de dados está vazio na inicialização do aplicativo, o repositório de mensagens foi inicializado com
três mensagens. Essas propagado mensagens também são usados no teste.
†O tópico EF teste com InMemory, explica como usar um banco de dados na memória para testes com MSTest.
Este tópico usa o xUnit estrutura de teste. Conceitos de teste e implementações de teste em estruturas de teste
diferentes são semelhantes, mas não idêntica.
Embora o aplicativo não usa o padrão repositório e não é um exemplo efetivação do padrão de unidade de
trabalho (UoW ), páginas Razor dá suporte a esses padrões de desenvolvimento. Para obter mais informações,
consulte criar a camada de persistência de infraestrutura, Implementando o repositório e padrões de unidade de
trabalho em um aplicativo ASP.NET MVC, e controlador de teste lógica de (o exemplo implementa o padrão de
repositório).

Organização do aplicativo de teste


O aplicativo de teste é um aplicativo de console dentro de tests/RazorPagesTestingSample.Tests pasta:

PASTA DO APLICATIVO DE TESTE DESCRIÇÃO

IntegrationTests IndexPageTest.cs contém os testes de integração para


a página de índice.
TestFixture.cs cria o host de teste para testar o
aplicativo de mensagem.

UnitTests DataAccessLayerTest.cs contém testes de unidade


para a DAL.
IndexPageTest.cs contém testes de unidade para o
modelo de página de índice.

Utilitários Utilities.CS contém o:


TestingDbContextOptions método usado para criar
o novo banco de dados opções de contexto para cada
teste de unidade da DAL, para que o banco de dados
é redefinido para sua condição de linha de base para
cada teste.
GetRequestContentAsync método usado para
preparar o HttpClient e o conteúdo para
solicitações que são enviadas para o aplicativo de
mensagem durante o teste de integração.

A estrutura de teste é xUnit. O objeto do framework de simulação é Moq. Testes de integração são realizadas
usando o Host de teste do ASP.NET Core.

Camada de acesso a dados (DAL) de teste de unidade


O aplicativo de mensagem tem uma DAL com quatro métodos contidos no AppDbContext classe
(src/RazorPagesTestingSample/Data/AppDbContext.cs). Cada método tem um ou dois testes de unidade em que
o aplicativo de teste.

MÉTODO DAL FUNÇÃO

GetMessagesAsync Obtém um List<Message> do banco de dados classificado


pelo Text propriedade.

AddMessageAsync Adiciona um Message no banco de dados.

DeleteAllMessagesAsync Exclui todos os Message entradas do banco de dados.

DeleteMessageAsync Exclui um único Message do banco de dados por Id .

Testes de unidade da DAL requerem DbContextOptions ao criar um novo AppDbContext para cada teste. Uma
abordagem para criar o DbContextOptions para cada teste usar um DbContextOptionsBuilder:

var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()


.UseInMemoryDatabase("InMemoryDb");

using (var db = new AppDbContext(optionsBuilder.Options))


{
// Use the db here in the unit test.
}

O problema com essa abordagem é que cada teste recebe o banco de dados no estado em que o teste anterior a
deixou. Isso pode ser um problema ao tentar gravar testes de unidade atômica que não interfiram uns aos outros.
Para forçar o AppDbContext para usar um novo contexto de banco de dados para cada teste, forneça um
DbContextOptions instância com base em um novo provedor de serviço. O aplicativo de teste mostra como fazer
isso usando seu Utilities método de classe TestingDbContextOptions
(tests/RazorPagesTestingSample.Tests/Utilities/Utilities.cs):

public static DbContextOptions<AppDbContext> TestingDbContextOptions()


{
// Create a new service provider to create a new in-memory database.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();

// Create a new options instance using an in-memory database and


// IServiceProvider that the context should resolve all of its
// services from.
var builder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb")
.UseInternalServiceProvider(serviceProvider);

return builder.Options;
}

Usando o DbContextOptions na unidade de DAL testes permite que cada teste executado atomicamente com uma
instância de banco de dados atualizado:
using (var db = new AppDbContext(Utilities.TestingDbContextOptions()))
{
// Use the db here in the unit test.
}

Cada método de teste no DataAccessLayerTest classe (UnitTests/DataAccessLayerTest.cs) segue um padrão


semelhante organizar Act declaração:
1. Organizar: O banco de dados está configurado para o teste de e/ou o resultado esperado é definido.
2. Ação: O teste é executado.
3. Assert: Asserções são feitas para determinar se o resultado de teste for bem-sucedida.
Por exemplo, o DeleteMessageAsync método é responsável por remover uma única mensagem identificada por
seu Id (src/RazorPagesTestingSample/Data/AppDbContext.cs):

public async virtual Task DeleteMessageAsync(int id)


{
var message = await Messages.FindAsync(id);

if (message != null)
{
Messages.Remove(message);
await SaveChangesAsync();
}
}

Há dois testes para este método. Um teste verifica se o método exclui uma mensagem quando a mensagem está
presente no banco de dados. Os outros testes de método que o banco de dados não serão alteradas se a
mensagem Id para exclusão não existe. O DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound método é
mostrado abaixo:

[Fact]
public async Task DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound()
{
using (var db = new AppDbContext(Utilities.TestingDbContextOptions()))
{
// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();

// Act
await db.DeleteMessageAsync(recId);

// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(x => x.Id),
actualMessages.OrderBy(x => x.Id),
new Utilities.MessageComparer());
}
}

Primeiro, o método executa a etapa de organizar, onde a preparação para a etapa de ação ocorre. As mensagens
de propagação são obtidas e mantidas em seedMessages . As mensagens de propagação são salvos no banco de
dados. A mensagem com um Id de 1 está definido para exclusão. Quando o DeleteMessageAsync o método é
executado, as mensagens esperadas devem ter todas as mensagens, exceto aquele com um Id de 1 .O
expectedMessages variável representa esse resultado esperado.

// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();

O método atua: O DeleteMessageAsync método é executado, passando o recId de 1 :

// Act
await db.DeleteMessageAsync(recId);

Por fim, o método obtém o Messages do contexto e o compara a expectedMessages declarando que os dois são
iguais:

// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));

Para comparar os dois List<Message> são os mesmos:


As mensagens são ordenadas por Id .
Pares de mensagens são comparados no Text propriedade.

Um método de teste semelhante, DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound verifica o


resultado da tentativa de excluir uma mensagem que não existe. Nesse caso, as mensagens esperadas no banco
de dados devem ser iguais para as mensagens reais após o DeleteMessageAsync o método é executado. Não deve
haver nenhuma alteração ao conteúdo do banco de dados:

[Fact]
public async Task DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound()
{
using (var db = new AppDbContext(Utilities.TestingDbContextOptions()))
{
// Arrange
var expectedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(expectedMessages);
await db.SaveChangesAsync();
var recId = 4;

// Act
await db.DeleteMessageAsync(recId);

// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
}
}
Os métodos do modelo de página de teste de unidade
Outro conjunto de testes de unidade é responsável por testar os métodos do modelo de página. No aplicativo de
mensagem, os modelos de página de índice são encontrados no IndexModel classe em
src/RazorPagesTestingSample/Pages/Index.cshtml.cs.

MÉTODO DE MODELO DE PÁGINA FUNÇÃO

OnGetAsync Obtém as mensagens de DAL para a interface do usuário


usando o GetMessagesAsync método.

OnPostAddMessageAsync Se o ModelState é válido, chamadas AddMessageAsync


para adicionar uma mensagem para o banco de dados.

OnPostDeleteAllMessagesAsync Chamadas DeleteAllMessagesAsync para excluir todas as


mensagens no banco de dados.

OnPostDeleteMessageAsync Executa DeleteMessageAsync para excluir uma mensagem


com o Id especificado.

OnPostAnalyzeMessagesAsync Se uma ou mais mensagens no banco de dados, calcula o


número médio de palavras por mensagem.

Os métodos do modelo de página são testados usando sete testes o IndexPageTest classe
(tests/RazorPagesTestingSample.Tests/UnitTests/IndexPageTest.cs). Os testes de usam o padrão de Act Assert
organizar familiar. Esses testes se concentram em:
Determinando se os métodos seguem o comportamento correto quando o ModelState é inválido.
Confirmando os métodos geram corretas IActionResult .
Verificando se as atribuições de valor de propriedade são feitas corretamente.
Geralmente, esse grupo de testes simular os métodos da DAL para produzir os dados esperados para a etapa de
Act onde um método de modelo de página é executado. Por exemplo, o GetMessagesAsync método o
AppDbContext é simulada para produzir saída. Quando esse método é executado um método de modelo de
página, a simulação retorna o resultado. Os dados não vem do banco de dados. Isso cria condições de teste
previsível e confiável para usando a DAL nos testes de modelo de página.
O OnGetAsync_PopulatesThePageModel_WithAListOfMessages teste mostra como o GetMessagesAsync método é
simulado para o modelo de página:

var mockAppDbContext = new Mock<AppDbContext>(optionsBuilder.Options);


var expectedMessages = AppDbContext.GetSeedingMessages();
mockAppDbContext.Setup(
db => db.GetMessagesAsync()).Returns(Task.FromResult(expectedMessages));
var pageModel = new IndexModel(mockAppDbContext.Object);

Quando o OnGetAsync o método é executado na etapa Act, ele chama o modelo de página GetMessagesAsync
método.
Etapa de ação de teste de unidade (tests/RazorPagesTestingSample.Tests/UnitTests/IndexPageTest.cs):

// Act
await pageModel.OnGetAsync();
IndexPage modelo de página OnGetAsync método (src/RazorPagesTestingSample/Pages/Index.cshtml.cs):

public async Task OnGetAsync()


{
Messages = await _db.GetMessagesAsync();
}

O GetMessagesAsync método DAL não retorna o resultado para a chamada de método. A versão fictícias do
método retorna o resultado.
No Assert etapa, as mensagens reais ( actualMessages ) são atribuídos a partir do Messages propriedade do
modelo de página. Também é realizada uma verificação de tipo quando as mensagens forem atribuídas. As
mensagens esperadas e reais são comparadas por seus Text propriedades. O teste afirma que os dois
List<Message> instâncias contêm as mesmas mensagens.

// Assert
var actualMessages = Assert.IsAssignableFrom<List<Message>>(pageModel.Messages);
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));

Página criar outros testes neste grupo de objetos de modelo que incluem o DefaultHttpContext , o
ModelStateDictionary , uma ActionContext para estabelecer o PageContext , um ViewDataDictionary e um
PageContext . Eles são úteis para conduzir testes. Por exemplo, o aplicativo de mensagem estabelece um
ModelState erro com AddModelError para verificar se uma opção válida PageResult é retornado quando
OnPostAddMessageAsync é executado:
[Fact]
public async Task OnPostAddMessageAsync_ReturnsAPageResult_WhenModelStateIsInvalid()
{
// Arrange
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb");
var mockAppDbContext = new Mock<AppDbContext>(optionsBuilder.Options);
var expectedMessages = AppDbContext.GetSeedingMessages();
mockAppDbContext.Setup(db => db.GetMessagesAsync()).Returns(Task.FromResult(expectedMessages));
var httpContext = new DefaultHttpContext();
var modelState = new ModelStateDictionary();
var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(),
modelState);
var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>());
var pageContext = new PageContext(actionContext)
{
ViewData = viewData
};
var pageModel = new IndexModel(mockAppDbContext.Object)
{
PageContext = pageContext,
TempData = tempData,
Url = new UrlHelper(actionContext)
};
pageModel.ModelState.AddModelError("Message.Text", "The Text field is required.");

// Act
var result = await pageModel.OnPostAddMessageAsync();

// Assert
Assert.IsType<PageResult>(result);
}

Teste o aplicativo de integração


A integração de testes foco nos testes de componentes do aplicativo funcionam em conjunto. Testes de
integração são realizadas usando o Host de teste do ASP.NET Core. Processamento de ciclo de vida de resposta
de solicitação completa é testado. Esses testes afirmar que a página gera o código de status correto e Location
cabeçalho, se definido.
Uma exemplo do exemplo de teste de integração, verifica o resultado da solicitação de página de índice do
aplicativo de mensagem (tests/RazorPagesTestingSample.Tests/IntegrationTests/IndexPageTest.cs):

[Fact]
public async Task Request_ReturnsSuccess()
{
// Act
var response = await _client.GetAsync("/");

// Assert
response.EnsureSuccessStatusCode();
}

Não há nenhuma etapa de organizar. O GetAsync método é chamado no HttpClient para enviar uma solicitação
GET para o ponto de extremidade. O teste declara que o resultado é um código de status 200 Okey.
Todas as solicitações POST para o aplicativo de mensagem devem satisfazer a verificação de antiforgery é criada
automaticamente pelo aplicativo do antiforgery sistema de proteção de dados. Para organizar para solicitação
POST do teste, o aplicativo de teste deve:
1. Fazer uma solicitação para a página.
2. Analise o cookie antiforgery e o token de validação de solicitação da resposta.
3. Verifique a solicitação POST com a validação de cookie e solicitação antiforgery token em vigor.
O Post_AddMessageHandler_ReturnsRedirectToRoot método de teste:
Prepara uma mensagem e o HttpClient .
Faz uma solicitação POST para o aplicativo.
Verifica que a resposta é um redirecionamento para a página de índice.
Post_AddMessageHandler_ReturnsRedirectToRoot method
(tests/RazorPagesTestingSample.Tests/IntegrationTests/IndexPageTest.cs):

[Fact]
public async Task Post_AddMessageHandler_ReturnsRedirectToRoot()
{
// Arrange
var data = new Dictionary<string, string>()
{
{ "Message.Text", "Test message to add." }
};
var content = await Utilities.GetRequestContentAsync(_client, "/", data);

// Act
var response = await _client.PostAsync("?handler=AddMessage", content);

// Assert
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("/", response.Headers.Location.OriginalString);
}

O GetRequestContentAsync método utilitário gerencia Preparando o cliente com o cookie antiforgery e o token de
solicitação de verificação. Observe como o método recebe um IDictionary que permite que o método de teste
de chamada para transmitir dados para a solicitação codificar junto com o token de verificação de solicitação
(tests/RazorPagesTestingSample.Tests/Utilities/Utilities.cs):
public static async Task<FormUrlEncodedContent> GetRequestContentAsync(
HttpClient _client, string path, IDictionary<string, string> data)
{
// Make a request for the resource.
var getResponse = await _client.GetAsync(path);

// Set the response's antiforgery cookie on the HttpClient.


_client.DefaultRequestHeaders.Add("Cookie",
getResponse.Headers.GetValues("Set-Cookie"));

// Obtain the request verification token from the response.


// Any <form> element in the response contains a token, and
// they're all the same within a single response.
//
// This method uses Regex to parse the element and its value
// from the response markup. A better approach in a production
// app would be to use an HTML parser (for example,
// HtmlAgilityPack: http://html-agility-pack.net/).
var responseMarkup = await getResponse.Content.ReadAsStringAsync();
var regExp_RequestVerificationToken = new Regex(
"<input name=\"__RequestVerificationToken\" type=\"hidden\" value=\"(.*?)\" \\/>",
RegexOptions.Compiled);
var matches = regExp_RequestVerificationToken.Matches(responseMarkup);
// Group[1] represents the captured characters, represented
// by (.*?) in the Regex pattern string.
var token = matches?.FirstOrDefault().Groups[1].Value;

// Add the token to the form data for the request.


data.Add("__RequestVerificationToken", token);

return new FormUrlEncodedContent(data);


}

Testes de integração também podem passar dados inválidos para o aplicativo para testar o comportamento de
resposta do aplicativo. O aplicativo de mensagem limita o tamanho de mensagem para 200 caracteres
(src/RazorPagesTestingSample/Data/Message.cs):

public class Message


{
public int Id { get; set; }

[Required]
[DataType(DataType.Text)]
[StringLength(200, ErrorMessage = "There's a 200 character limit on messages. Please shorten your
message.")]
public string Text { get; set; }
}

O Post_AddMessageHandler_ReturnsSuccess_WhenMessageTextTooLong teste Message explicitamente passa em texto


com 201 caracteres "X". Isso resulta em um ModelState erro. A publicação não for redirecionada voltar à página
de índice. Ele retorna um Okey de 200 com um null Location cabeçalho
(tests/RazorPagesTestingSample.Tests/IntegrationTests/IndexPageTest.cs):
[Fact]
public async Task Post_AddMessageHandler_ReturnsSuccess_WhenMessageTextTooLong()
{
// Arrange
var data = new Dictionary<string, string>()
{
{ "Message.Text", new string('X', 201) }
};
var content = await Utilities.GetRequestContentAsync(_client, "/", data);

// Act
var response = await _client.PostAsync("?handler=AddMessage", content);

// Assert
// A ModelState failure returns to Page (200-OK) and doesn't redirect.
response.EnsureSuccessStatusCode();
Assert.Null(response.Headers.Location?.OriginalString);
}

Consulte também
Teste de unidade c# no .NET Core usando xUnit e teste dotnet
Testes de integração
Controladores de teste
O código de teste de unidade (Visual Studio)
xUnit.net
Guia de Introdução ao xUnit.net (.NET Core/ASP.NET núcleos)
Moq
Guia de início rápido Moq
Testando a lógica do controlador no ASP.NET Core
08/02/2018 • 27 min to read • Edit Online

Por Steve Smith


Os controladores em aplicativos ASP.NET MVC devem ser pequenos e estar voltados para interesses da
interface do usuário. Controladores grandes que lidam com interesses não referentes à interface do usuário são
mais difíceis de serem testados e mantidos.
Exibir ou baixar a amostra do GitHub

Testando os controladores
Os controladores são uma parte central de qualquer aplicativo ASP.NET Core MVC. Assim, você deve estar
confiante de que eles se comportam conforme o esperado para o aplicativo. Testes automatizados podem
fornecer essa confiança e podem detectar erros antes que eles atinjam a produção. É importante evitar colocar
responsabilidades desnecessárias dentro dos controladores e garantir que o teste tenha como foco somente as
responsabilidades do controlador.
A lógica do controlador deve ser mínima e não estar voltada para a lógica de negócios ou interesses de
infraestrutura (por exemplo, acesso a dados). Teste a lógica do controlador, não a estrutura. Teste como o
controlador se comporta de acordo com as entradas válidas ou inválidas. Teste as respostas do controlador de
acordo com o resultado da operação de negócios executada por ele.
Responsabilidades típicas do controlador:
Verificar ModelState.IsValid .
Retornar uma resposta de erro se ModelState for inválido.
Recuperar uma entidade de negócios da persistência.
Executar uma ação na entidade de negócios.
Salvar a entidade de negócios para persistência.
Retornar um IActionResult apropriado.

Teste de unidade
O teste de unidade envolve o teste de uma parte de um aplicativo de teste em isolamento de sua infraestrutura
e suas dependências. Ao executar o teste de unidade na lógica do controlador, somente o conteúdo de uma
única ação é testada, não o comportamento de suas dependências ou da própria estrutura. Ao executar o teste
de unidade nas ações do controlador, concentre-se apenas em seu comportamento. Um teste de unidade do
controlador evita itens como filtros, roteamento ou associação de modelos. Ao se concentrarem no teste de
apenas uma coisa, os testes de unidade geralmente são simples de serem escritos e rápidos de serem
executados. Um conjunto bem escrito de testes de unidade pode ser executado com frequência sem muita
sobrecarga. No entanto, os testes de unidade não detectam problemas na interação entre componentes, que é a
finalidade dos testes de integração.
Se você estiver escrevendo filtros personalizados, rotas, etc., execute o teste de unidade neles, mas não como
parte dos testes em uma ação específica do controlador. Eles devem ser testados em isolamento.
DICA
Crie e execute testes de unidade com o Visual Studio.

Para demonstrar o teste de unidade, examine o controlador a seguir. Ele exibe uma lista de sessões de debate e
permite que novas sessões de debate sejam criadas com um POST:
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using TestingControllersSample.ViewModels;

namespace TestingControllersSample.Controllers
{
public class HomeController : Controller
{
private readonly IBrainstormSessionRepository _sessionRepository;

public HomeController(IBrainstormSessionRepository sessionRepository)


{
_sessionRepository = sessionRepository;
}

public async Task<IActionResult> Index()


{
var sessionList = await _sessionRepository.ListAsync();

var model = sessionList.Select(session => new StormSessionViewModel()


{
Id = session.Id,
DateCreated = session.DateCreated,
Name = session.Name,
IdeaCount = session.Ideas.Count
});

return View(model);
}

public class NewSessionModel


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

[HttpPost]
public async Task<IActionResult> Index(NewSessionModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
else
{
await _sessionRepository.AddAsync(new BrainstormSession()
{
DateCreated = DateTimeOffset.Now,
Name = model.SessionName
});
}

return RedirectToAction(actionName: nameof(Index));


}
}
}

O controlador está seguindo o princípio de dependências explícitas, esperando receber da injeção de


dependência uma instância de IBrainstormSessionRepository . Isso facilita muito o teste com o uso de uma
estrutura de objeto fictício, como o Moq. O método HTTP GET Index não tem nenhum loop ou branch e chama
apenas um método. Para testar este método Index , precisamos verificar se um ViewResult for retornado, com
um ViewModel do método List do repositório.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Moq;
using TestingControllersSample.Controllers;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using TestingControllersSample.ViewModels;
using Xunit;

namespace TestingControllersSample.Tests.UnitTests
{
public class HomeControllerTests
{
[Fact]
public async Task Index_ReturnsAViewResult_WithAListOfBrainstormSessions()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.ListAsync()).Returns(Task.FromResult(GetTestSessions()));
var controller = new HomeController(mockRepo.Object);

// Act
var result = await controller.Index();

// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsAssignableFrom<IEnumerable<StormSessionViewModel>>(
viewResult.ViewData.Model);
Assert.Equal(2, model.Count());
}

private List<BrainstormSession> GetTestSessions()


{
var sessions = new List<BrainstormSession>();
sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 2),
Id = 1,
Name = "Test One"
});
sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 1),
Id = 2,
Name = "Test Two"
});
return sessions;
}
}
}

O método HomeController HTTP POST Index (mostrado acima) deve verificar se:
O método de ação retorna um ViewResult de Solicitação Inválida com os dados apropriados quando
ModelState.IsValid é false

O método Add no repositório é chamado e um RedirectToActionResult é retornado com os argumentos


corretos quando ModelState.IsValid é verdadeiro.
O estado de modelo inválido pode ser testado com a adição de erros usando AddModelError , conforme
mostrado no primeiro teste abaixo.

[Fact]
public async Task IndexPost_ReturnsBadRequestResult_WhenModelStateIsInvalid()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.ListAsync()).Returns(Task.FromResult(GetTestSessions()));
var controller = new HomeController(mockRepo.Object);
controller.ModelState.AddModelError("SessionName", "Required");
var newSession = new HomeController.NewSessionModel();

// Act
var result = await controller.Index(newSession);

// Assert
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
Assert.IsType<SerializableError>(badRequestResult.Value);
}

[Fact]
public async Task IndexPost_ReturnsARedirectAndAddsSession_WhenModelStateIsValid()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.AddAsync(It.IsAny<BrainstormSession>()))
.Returns(Task.CompletedTask)
.Verifiable();
var controller = new HomeController(mockRepo.Object);
var newSession = new HomeController.NewSessionModel()
{
SessionName = "Test Name"
};

// Act
var result = await controller.Index(newSession);

// Assert
var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
Assert.Null(redirectToActionResult.ControllerName);
Assert.Equal("Index", redirectToActionResult.ActionName);
mockRepo.Verify();
}

O primeiro teste confirma que quando ModelState não é válido, o mesmo ViewResult é retornado como para
uma solicitação GET . Observe que o teste não tenta passar um modelo inválido. Isso não funcionará de
qualquer forma, pois a associação de modelos não está em execução (embora um teste de integração use a
associação de modelos de exercícios). Nesse caso, a associação de modelos não está sendo testada. Esses testes
de unidade estão testando apenas o que o código faz no método de ação.
O segundo teste verifica que quando ModelState é válido, um novo BrainstormSession é adicionado (por meio
do repositório) e o método retorna um RedirectToActionResult com as propriedades esperadas. Chamadas
fictícias que não são chamadas são normalmente ignoradas, mas a chamada a Verifiable no final da chamada
de instalação permite que ele seja verificada no teste. Isso é feito com a chamada a mockRepo.Verify , que não
será aprovado no teste se o método esperado não tiver sido chamado.
OBSERVAÇÃO
A biblioteca do Moq usada nesta amostra facilita a combinação de simulações verificáveis ou "estritas" com simulações
não verificáveis (também chamadas de simulações "flexíveis" ou stubs). Saiba mais sobre como personalizar o
comportamento de Simulação com o Moq.

Outro controlador no aplicativo exibe informações relacionadas a uma sessão de debate específica. Ele inclui
uma lógica para lidar com valores de ID inválidos:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.ViewModels;

namespace TestingControllersSample.Controllers
{
public class SessionController : Controller
{
private readonly IBrainstormSessionRepository _sessionRepository;

public SessionController(IBrainstormSessionRepository sessionRepository)


{
_sessionRepository = sessionRepository;
}

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


{
if (!id.HasValue)
{
return RedirectToAction(actionName: nameof(Index), controllerName: "Home");
}

var session = await _sessionRepository.GetByIdAsync(id.Value);


if (session == null)
{
return Content("Session not found.");
}

var viewModel = new StormSessionViewModel()


{
DateCreated = session.DateCreated,
Name = session.Name,
Id = session.Id
};

return View(viewModel);
}
}
}

A ação do controlador tem três casos a serem testados, um para cada instrução return :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Moq;
using TestingControllersSample.Controllers;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using TestingControllersSample.ViewModels;
using Xunit;
namespace TestingControllersSample.Tests.UnitTests
{
public class SessionControllerTests
{
[Fact]
public async Task IndexReturnsARedirectToIndexHomeWhenIdIsNull()
{
// Arrange
var controller = new SessionController(sessionRepository: null);

// Act
var result = await controller.Index(id: null);

// Assert
var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
Assert.Equal("Home", redirectToActionResult.ControllerName);
Assert.Equal("Index", redirectToActionResult.ActionName);
}

[Fact]
public async Task IndexReturnsContentWithSessionNotFoundWhenSessionNotFound()
{
// Arrange
int testSessionId = 1;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.Returns(Task.FromResult((BrainstormSession)null));
var controller = new SessionController(mockRepo.Object);

// Act
var result = await controller.Index(testSessionId);

// Assert
var contentResult = Assert.IsType<ContentResult>(result);
Assert.Equal("Session not found.", contentResult.Content);
}

[Fact]
public async Task IndexReturnsViewResultWithStormSessionViewModel()
{
// Arrange
int testSessionId = 1;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.Returns(Task.FromResult(GetTestSessions().FirstOrDefault(s => s.Id == testSessionId)));
var controller = new SessionController(mockRepo.Object);

// Act
var result = await controller.Index(testSessionId);

// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsType<StormSessionViewModel>(viewResult.ViewData.Model);
Assert.Equal("Test One", model.Name);
Assert.Equal(2, model.DateCreated.Day);
Assert.Equal(testSessionId, model.Id);
}

private List<BrainstormSession> GetTestSessions()


{
var sessions = new List<BrainstormSession>();
sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 2),
Id = 1,
Name = "Test One"
});
sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 1),
Id = 2,
Name = "Test Two"
});
return sessions;
}
}
}

O aplicativo expõe a funcionalidade como uma API Web (uma lista de ideias associadas a uma sessão de debate
e um método para adicionar novas ideias a uma sessão):

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using TestingControllersSample.ClientModels;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;

namespace TestingControllersSample.Api
{
[Route("api/ideas")]
public class IdeasController : Controller
{
private readonly IBrainstormSessionRepository _sessionRepository;

public IdeasController(IBrainstormSessionRepository sessionRepository)


{
_sessionRepository = sessionRepository;
}

[HttpGet("forsession/{sessionId}")]
public async Task<IActionResult> ForSession(int sessionId)
{
var session = await _sessionRepository.GetByIdAsync(sessionId);
if (session == null)
{
return NotFound(sessionId);
}

var result = session.Ideas.Select(idea => new IdeaDTO()


{
Id = idea.Id,
Name = idea.Name,
Description = idea.Description,
DateCreated = idea.DateCreated
}).ToList();

return Ok(result);
}

[HttpPost("create")]
public async Task<IActionResult> Create([FromBody]NewIdeaModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

var session = await _sessionRepository.GetByIdAsync(model.SessionId);


if (session == null)
{
return NotFound(model.SessionId);
}
var idea = new Idea()
{
DateCreated = DateTimeOffset.Now,
Description = model.Description,
Name = model.Name
};
session.AddIdea(idea);

await _sessionRepository.UpdateAsync(session);

return Ok(session);
}
}
}

O método ForSession retorna uma lista de tipos IdeaDTO . Evite retornar as entidades de domínio de negócios
diretamente por meio de chamadas à API, pois, com frequência, elas incluem mais dados do que o cliente de
API exige e acoplam desnecessariamente o modelo de domínio interno do aplicativo à API exposta
externamente. O mapeamento entre entidades de domínio e os tipos que você retornará de forma eletrônica
pode ser feito manualmente (usando um LINQ Select , conforme mostrado aqui) ou uma biblioteca como o
AutoMapper
Os testes de unidade para os métodos de API Create e ForSession :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Moq;
using TestingControllersSample.Api;
using TestingControllersSample.ClientModels;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using Xunit;

namespace TestingControllersSample.Tests.UnitTests
{
public class ApiIdeasControllerTests
{
[Fact]
public async Task Create_ReturnsBadRequest_GivenInvalidModel()
{
// Arrange & Act
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
controller.ModelState.AddModelError("error","some error");

// Act
var result = await controller.Create(model: null);

// Assert
Assert.IsType<BadRequestObjectResult>(result);
}

[Fact]
public async Task Create_ReturnsHttpNotFound_ForInvalidSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.Returns(Task.FromResult((BrainstormSession)null));
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.Create(new NewIdeaModel());

// Assert
Assert.IsType<NotFoundObjectResult>(result);
}

[Fact]
public async Task Create_ReturnsNewlyCreatedIdeaForSession()
{
// Arrange
int testSessionId = 123;
string testName = "test name";
string testDescription = "test description";
var testSession = GetTestSession();
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.Returns(Task.FromResult(testSession));
var controller = new IdeasController(mockRepo.Object);

var newIdea = new NewIdeaModel()


{
Description = testDescription,
Name = testName,
SessionId = testSessionId
};
mockRepo.Setup(repo => repo.UpdateAsync(testSession))
.Returns(Task.CompletedTask)
.Verifiable();

// Act
var result = await controller.Create(newIdea);

// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var returnSession = Assert.IsType<BrainstormSession>(okResult.Value);
mockRepo.Verify();
Assert.Equal(2, returnSession.Ideas.Count());
Assert.Equal(testName, returnSession.Ideas.LastOrDefault().Name);
Assert.Equal(testDescription, returnSession.Ideas.LastOrDefault().Description);
}

private BrainstormSession GetTestSession()


{
var session = new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 2),
Id = 1,
Name = "Test One"
};

var idea = new Idea() { Name = "One" };


session.AddIdea(idea);
return session;
}
}
}

Conforme mencionado anteriormente, para testar o comportamento do método quando ModelState é inválido,
adicione um erro de modelo ao controlador como parte do teste. Não tente testar a validação ou a associação
de modelos nos testes de unidade – teste apenas o comportamento do método de ação quando houver um
valor ModelState específico.
O segundo teste depende do repositório retornar nulo. Portanto, o repositório fictício é configurado para
retornar nulo. Não é necessário criar um banco de dados de teste (em memória ou de outra forma) e construir
uma consulta que retornará esse resultado – isso pode ser feito em uma única instrução, conforme mostrado.
O último teste verifica se o método Update do repositório é chamado. Como fizemos anteriormente, a
simulação é chamada com Verifiable e, em seguida, o método Verify do repositório fictício é chamado para
confirmar se o método verificável foi executado. Não é responsabilidade do teste de unidade garantir que o
método Update salva os dados; isso pode ser feito com um teste de integração.

Teste de integração
O teste de integração é feito para garantir que módulos separados dentro do aplicativo funcionem
corretamente juntos. Em geral, qualquer coisa que você possa testar com um teste de unidade, também pode
testar com um teste de integração, mas o contrário não é verdadeiro. No entanto, os testes de integração
tendem a ser muito mais lentos do que os testes de unidade. Portanto, é melhor testar tudo o que você puder
com testes de unidade e usar testes de integração para cenários que envolvem vários colaboradores.
Embora eles ainda possam ser úteis, objetos fictícios raramente são usados em testes de integração. Em um
teste de unidade, objetos fictícios são uma maneira eficiente de controlar como os colaboradores fora da
unidade que está sendo testada devem se comportar para fins do teste. Em um teste de integração,
colaboradores reais são usados para confirmar que todo o subsistema funciona junto corretamente.
Estado do aplicativo
Uma consideração importante ao executar testes de integração é como configurar o estado do aplicativo. Os
testes precisam ser executados de forma independente entre si e, portanto, cada teste deve ser iniciado com o
aplicativo em um estado conhecido. Se o aplicativo não usa um banco de dados ou não tem nenhuma
persistência, isso pode não ser um problema. No entanto, a maioria dos aplicativos do mundo real persiste seu
estado para algum tipo de armazenamento de dados. Portanto, as modificações feitas por um teste podem
afetar outro teste, a menos que o armazenamento de dados seja redefinido. Usando o TestServer interno, é
muito simples hospedar aplicativos ASP.NET Core em nossos testes de integração, mas isso não
necessariamente permite acesso aos dados que serão usados por ele. Caso esteja usando um banco de dados
real, uma abordagem é conectar o aplicativo a um banco de dados de teste, que pode ser acessado pelos testes,
e garantir que ele seja redefinido para um estado conhecido antes da execução de cada teste.
Neste aplicativo de exemplo, estou usando o suporte de InMemoryDatabase do Entity Framework Core e,
portanto, não consigo me conectar a ele em meu projeto de teste. Em vez disso, exponho um método
InitializeDatabase da classe Startup do aplicativo, que chamo quando o aplicativo é iniciado se ele está no
ambiente Development . Meus testes de integração automaticamente se beneficiam com isso, desde que eles
definam o ambiente como Development . Não preciso me preocupar com a redefinição do banco de dados, pois
o InMemoryDatabase é redefinido sempre que o aplicativo é reiniciado.
A classe Startup :

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using TestingControllersSample.Infrastructure;

namespace TestingControllersSample
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(
optionsBuilder => optionsBuilder.UseInMemoryDatabase("InMemoryDb"));

services.AddMvc();

services.AddScoped<IBrainstormSessionRepository,
EFStormSessionRepository>();
}

public void Configure(IApplicationBuilder app,


IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
var repository = app.ApplicationServices.GetService<IBrainstormSessionRepository>();
InitializeDatabaseAsync(repository).Wait();
}

app.UseStaticFiles();

app.UseMvcWithDefaultRoute();
}

public async Task InitializeDatabaseAsync(IBrainstormSessionRepository repo)


{
var sessionList = await repo.ListAsync();
if (!sessionList.Any())
{
await repo.AddAsync(GetTestSession());
}
}

public static BrainstormSession GetTestSession()


{
var session = new BrainstormSession()
{
Name = "Test Session 1",
DateCreated = new DateTime(2016, 8, 1)
};
var idea = new Idea()
{
DateCreated = new DateTime(2016, 8, 1),
Description = "Totally awesome idea",
Name = "Awesome idea"
};
session.AddIdea(idea);
return session;
}
}
}

Você verá o método GetTestSession que costuma ser usado nos testes de integração abaixo.
Acessando exibições
Cada classe de teste de integração configura o TestServer que executará o aplicativo ASP.NET Core. Por
padrão, o TestServer hospeda o aplicativo Web na pasta em que ele está em execução – nesse caso, a pasta do
projeto de teste. Portanto, quando você tentar testar ações do controlador que retornam ViewResult , poderá
receber este erro:

The view 'Index' wasn't found. The following locations were searched:
(list of locations)

Para corrigir esse problema, você precisa configurar a raiz de conteúdo do servidor, para que ela possa localizar
as exibições para o projeto que está sendo testado. Isso é feito por uma chamada a UseContentRoot na classe
TestFixture , conforme mostrado abaixo:

using System;
using System.IO;
using System.Net.Http;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;

namespace TestingControllersSample.Tests.IntegrationTests
{
/// <summary>
/// A test fixture which hosts the target project (project we wish to test) in an in-memory server.
/// </summary>
/// <typeparam name="TStartup">Target project's startup type</typeparam>
public class TestFixture<TStartup> : IDisposable
{
private readonly TestServer _server;

public TestFixture()
: this(Path.Combine("src"))
{
}

protected TestFixture(string relativeTargetProjectParentDir)


{
var startupAssembly = typeof(TStartup).GetTypeInfo().Assembly;
var contentRoot = GetProjectPath(relativeTargetProjectParentDir, startupAssembly);

var builder = new WebHostBuilder()


.UseContentRoot(contentRoot)
.ConfigureServices(InitializeServices)
.UseEnvironment("Development")
.UseStartup(typeof(TStartup));

_server = new TestServer(builder);

Client = _server.CreateClient();
Client.BaseAddress = new Uri("http://localhost");
}

public HttpClient Client { get; }

public void Dispose()


{
Client.Dispose();
_server.Dispose();
}

protected virtual void InitializeServices(IServiceCollection services)


{
var startupAssembly = typeof(TStartup).GetTypeInfo().Assembly;

// Inject a custom application part manager.


// Overrides AddMvcCore() because it uses TryAdd().
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new AssemblyPart(startupAssembly));
manager.FeatureProviders.Add(new ControllerFeatureProvider());
manager.FeatureProviders.Add(new ViewComponentFeatureProvider());

services.AddSingleton(manager);
}
/// <summary>
/// Gets the full path to the target project that we wish to test
/// </summary>
/// <param name="projectRelativePath">
/// The parent directory of the target project.
/// e.g. src, samples, test, or test/Websites
/// </param>
/// <param name="startupAssembly">The target project's assembly.</param>
/// <returns>The full path to the target project.</returns>
private static string GetProjectPath(string projectRelativePath, Assembly startupAssembly)
{
// Get name of the target project which we want to test
var projectName = startupAssembly.GetName().Name;

// Get currently executing test project path


var applicationBasePath = System.AppContext.BaseDirectory;

// Find the path to the target project


var directoryInfo = new DirectoryInfo(applicationBasePath);
do
{
directoryInfo = directoryInfo.Parent;

var projectDirectoryInfo = new DirectoryInfo(Path.Combine(directoryInfo.FullName,


projectRelativePath));
if (projectDirectoryInfo.Exists)
{
var projectFileInfo = new FileInfo(Path.Combine(projectDirectoryInfo.FullName,
projectName, $"{projectName}.csproj"));
if (projectFileInfo.Exists)
{
return Path.Combine(projectDirectoryInfo.FullName, projectName);
}
}
}
while (directoryInfo.Parent != null);

throw new Exception($"Project root could not be located using the application root
{applicationBasePath}.");
}
}
}

A classe TestFixture é responsável por configurar e criar o TestServer , configurar um HttpClient para se
comunicar com o TestServer . Cada um dos testes de integração usa a propriedade Client para se conectar ao
servidor de teste e fazer uma solicitação.
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;

namespace TestingControllersSample.Tests.IntegrationTests
{
public class HomeControllerTests : IClassFixture<TestFixture<TestingControllersSample.Startup>>
{
private readonly HttpClient _client;

public HomeControllerTests(TestFixture<TestingControllersSample.Startup> fixture)


{
_client = fixture.Client;
}

[Fact]
public async Task ReturnsInitialListOfBrainstormSessions()
{
// Arrange - get a session known to exist
var testSession = Startup.GetTestSession();

// Act
var response = await _client.GetAsync("/");

// Assert
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
Assert.Contains(testSession.Name, responseString);
}

[Fact]
public async Task PostAddsNewBrainstormSession()
{
// Arrange
string testSessionName = Guid.NewGuid().ToString();
var data = new Dictionary<string, string>();
data.Add("SessionName", testSessionName);
var content = new FormUrlEncodedContent(data);

// Act
var response = await _client.PostAsync("/", content);

// Assert
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("/", response.Headers.Location.ToString());
}
}
}

No primeiro teste acima, a responseString armazena o HTML real renderizado de View, que pode ser
inspecionado para confirmar se ele contém os resultados esperados.
O segundo teste constrói um POST de formulário com um nome de sessão exclusivo e executa POST dele para
o aplicativo, verificando, em seguida, se o redirecionamento esperado é retornado.
Métodos de API
Se o aplicativo expõe APIs Web, é uma boa ideia fazer com que os testes automatizados confirmem se elas são
executadas como esperado. O TestServer interno facilita o teste de APIs Web. Se os métodos de API estiverem
usando a associação de modelos, você sempre deverá verificar ModelState.IsValid e os testes de integração
são o melhor lugar para confirmar se a validação do modelo está funcionando corretamente.
O seguinte conjunto de testes é direcionado ao método Create na classe IdeasController mostrada acima:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using TestingControllersSample.ClientModels;
using TestingControllersSample.Core.Model;
using Xunit;

namespace TestingControllersSample.Tests.IntegrationTests
{
public class ApiIdeasControllerTests : IClassFixture<TestFixture<TestingControllersSample.Startup>>
{
internal class NewIdeaDto
{
public NewIdeaDto(string name, string description, int sessionId)
{
Name = name;
Description = description;
SessionId = sessionId;
}

public string Name { get; set; }


public string Description { get; set; }
public int SessionId { get; set; }
}

private readonly HttpClient _client;

public ApiIdeasControllerTests(TestFixture<TestingControllersSample.Startup> fixture)


{
_client = fixture.Client;
}

[Fact]
public async Task CreatePostReturnsBadRequestForMissingNameValue()
{
// Arrange
var newIdea = new NewIdeaDto("", "Description", 1);

// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);

// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}

[Fact]
public async Task CreatePostReturnsBadRequestForMissingDescriptionValue()
{
// Arrange
var newIdea = new NewIdeaDto("Name", "", 1);

// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);

// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}

[Fact]
public async Task CreatePostReturnsBadRequestForSessionIdValueTooSmall()
{
// Arrange
// Arrange
var newIdea = new NewIdeaDto("Name", "Description", 0);

// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);

// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}

[Fact]
public async Task CreatePostReturnsBadRequestForSessionIdValueTooLarge()
{
// Arrange
var newIdea = new NewIdeaDto("Name", "Description", 1000001);

// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);

// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}

[Fact]
public async Task CreatePostReturnsNotFoundForInvalidSession()
{
// Arrange
var newIdea = new NewIdeaDto("Name", "Description", 123);

// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);

// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}

[Fact]
public async Task CreatePostReturnsCreatedIdeaWithCorrectInputs()
{
// Arrange
var testIdeaName = Guid.NewGuid().ToString();
var newIdea = new NewIdeaDto(testIdeaName, "Description", 1);

// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);

// Assert
response.EnsureSuccessStatusCode();
var returnedSession = await response.Content.ReadAsJsonAsync<BrainstormSession>();
Assert.Equal(2, returnedSession.Ideas.Count);
Assert.Contains(testIdeaName, returnedSession.Ideas.Select(i => i.Name).ToList());
}

[Fact]
public async Task ForSessionReturnsNotFoundForBadSessionId()
{
// Arrange & Act
var response = await _client.GetAsync("/api/ideas/forsession/500");

// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}

[Fact]
public async Task ForSessionReturnsIdeasForValidSessionId()
{
// Arrange
var testSession = Startup.GetTestSession();

// Act
// Act
var response = await _client.GetAsync("/api/ideas/forsession/1");

// Assert
response.EnsureSuccessStatusCode();
var ideaList = JsonConvert.DeserializeObject<List<IdeaDTO>>(
await response.Content.ReadAsStringAsync());
var firstIdea = ideaList.First();
Assert.Equal(testSession.Ideas.First().Name, firstIdea.Name);
}
}
}

Ao contrário dos testes de integração de ações que retornam exibições HTML, os métodos de API Web que
retornam resultados podem normalmente ser desserializados como objetos fortemente tipados, como mostra o
último teste acima. Nesse caso, o teste desserializa o resultado para uma instância BrainstormSession e
confirma se a ideia foi corretamente adicionada à sua coleção de ideias.
Você encontrará outros exemplos de testes de integração no projeto de exemplo deste artigo.
Solucionar problemas em projetos do ASP.NET Core
27/04/2018 • 3 min to read • Edit Online

Por Rick Anderson


Os links a seguir fornecem orientação para solução de problemas:
Solucionar problemas no ASP.NET Core no Serviço de Aplicativo do Azure
Solucionar problemas do ASP.NET Core no IIS
Referência de erros comuns para o Serviço de Aplicativo do Azure e o IIS com o ASP.NET Core
YouTube: Diagnosticar problemas em aplicativos do ASP.NET Core

Avisos do SDK do .NET core


Ambas as versões 32 e 64 bits do SDK do núcleo do .NET estão instaladas
No novo projeto caixa de diálogo para o ASP.NET Core, você poderá ver os seguintes avisos aparecem na parte
superior:

Both 32 and 64 bit versions of the .NET Core SDK are installed. Only templates from the 64 bit version(s)
installed at C:\Program Files\dotnet\sdk\" will be displayed.

Esse aviso aparece quando (x86) 32 bits e versões de 64 bits (x64) da .NET Core SDK estão instalados. Ambas as
versões podem ser instaladas os motivos comuns incluem:
Você baixou o instalador do SDK do .NET Core usando um computador de 32 bits, mas, em seguida, foi
copiado em e originalmente instalado em um computador de 64 bits.
O SDK de núcleo do .NET 32 bits foi instalado por outro aplicativo.
A versão incorreta foi baixada e instalada.
Desinstale o SDK de núcleo do .NET 32 bits para evitar este aviso. Desinstalar do painel de controle >
programas e recursos > desinstalar ou alterar um programa. Se você entender por que o aviso é exibido e
suas implicações, você pode ignorar o aviso.
O SDK do .NET Core é instalado em vários locais
No novo projeto caixa de diálogo do ASP.NET Core, você poderá ver os seguintes avisos aparecem na parte
superior:
O SDK do .NET Core é instalado em vários locais. Somente modelos a partir de SDK(s) instalado no ' C:\Program
Files\dotnet\sdk' será exibido.

Você está vendo esta mensagem porque você tem pelo menos uma instalação do SDK .NET Core em um diretório
fora do * C:\Program Files\dotnet\sdk*. Geralmente, isso acontece quando o SDK do .NET Core foi implantado em
um computador usando copiar/colar em vez do instalador MSI.
Desinstale o SDK de núcleo do .NET 32 bits para evitar este aviso. Desinstalar do painel de controle >
programas e recursos > desinstalar ou alterar um programa. Se você entender por que o aviso é exibido e
suas implicações, você pode ignorar o aviso.
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
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;Multipl
eActiveResultSets=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")},
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.

-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
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 Core e ao Entity Framework 6
14/02/2018 • 7 min to read • Edit Online

Por Paweł Grudzień, Damien Pontifex e Tom Dykstra


Este artigo mostra como usar o Entity Framework 6 em um aplicativo ASP.NET Core.

Visão geral
Para usar o Entity Framework 6, o projeto precisa ser compilado no .NET Framework, pois o Entity Framework 6
não dá suporte ao .NET Core. Caso precise de recursos de multiplataforma, faça upgrade para o Entity Framework
Core.
A maneira recomendada de usar o Entity Framework 6 em um aplicativo ASP.NET Core é colocar o contexto e as
classes de modelo do EF6 em um projeto de biblioteca de classes direcionado à estrutura completa. Adicione uma
referência à biblioteca de classes do projeto ASP.NET Core. Consulte a solução de exemplo do Visual Studio com
projetos EF6 e ASP.NET Core.
Não é possível colocar um contexto do EF6 em um projeto ASP.NET Core, pois projetos .NET Core não dão
suporte a todas as funcionalidades exigidas pelo EF6, como Enable-Migrations, que é obrigatória.
Seja qual for o tipo de projeto em que você localize o contexto do EF6, somente ferramentas de linha de comando
do EF6 funcionam com um contexto do EF6. Por exemplo, Scaffold-DbContext está disponível apenas no Entity
Framework Core. Caso precise fazer engenharia reversa de um banco de dados para um modelo do EF6, consulte
Usar o Code First para um banco de dados existente.

Referenciar a estrutura completa e o EF6 no projeto ASP.NET Core


O projeto ASP.NET Core precisa referenciar a estrutura .NET e o EF6. Por exemplo, o arquivo .csproj do projeto
ASP.NET Core será semelhante ao exemplo a seguir (somente as partes relevantes do arquivo são mostradas).

<PropertyGroup>
<TargetFramework>net452</TargetFramework>
<PreserveCompilationContext>true</PreserveCompilationContext>
<AssemblyName>MVCCore</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>MVCCore</PackageId>
</PropertyGroup>

Ao criar um novo projeto, use o modelo Aplicativo Web ASP.NET Core (.NET Framework).

Manipular as cadeias de conexão


As ferramentas de linha de comando do EF6 que você usará no projeto de biblioteca de classes do EF6 exigem um
construtor padrão para que possam criar uma instância do contexto. No entanto, provavelmente, você desejará
especificar a cadeia de conexão a ser usada no projeto ASP.NET Core, caso em que o construtor de contexto deve
ter um parâmetro que permita passar a cadeia de conexão. Veja um exemplo.
public class SchoolContext : DbContext
{
public SchoolContext(string connString) : base(connString)
{
}

Como o contexto do EF6 não tem um construtor sem parâmetros, o projeto do EF6 precisa fornecer uma
implementação de IDbContextFactory. As ferramentas de linha de comando do EF6 encontrarão e usarão essa
implementação para que possam criar uma instância do contexto. Veja um exemplo.

public class SchoolContextFactory : IDbContextFactory<SchoolContext>


{
public SchoolContext Create()
{
return new EF6.SchoolContext("Server=
(localdb)\\mssqllocaldb;Database=EF6MVCCore;Trusted_Connection=True;MultipleActiveResultSets=true");
}
}

Nesse código de exemplo, a implementação IDbContextFactory passa uma cadeia de conexão embutida em
código. Essa é a cadeia de conexão que será usada pelas ferramentas de linha de comando. Recomendamos
implementar uma estratégia para garantir que a biblioteca de classes use a mesma cadeia de conexão usada pelo
aplicativo de chamada. Por exemplo, você pode obter o valor de uma variável de ambiente em ambos os projetos.

Configurar a injeção de dependência no projeto ASP.NET Core


No arquivo Startup.cs do projeto Core, configure o contexto do EF6 para DI (injeção de dependência) em
ConfigureServices . Os objetos de contexto do EF devem ser delimitados em escopo a um tempo de vida por
solicitação.

public void ConfigureServices(IServiceCollection services)


{
// Add framework services.
services.AddMvc();
services.AddScoped<SchoolContext>(_ => new
SchoolContext(Configuration.GetConnectionString("DefaultConnection")));
}

Em seguida, você pode obter uma instância do contexto nos controladores usando a DI. O código é semelhante ao
que você escreverá para um contexto do EF Core:

public class StudentsController : Controller


{
private readonly SchoolContext _context;

public StudentsController(SchoolContext context)


{
_context = context;
}

Aplicativo de exemplo
Para obter um aplicativo de exemplo funcional, consulte a solução de exemplo do Visual Studio que acompanha
este artigo.
Esta amostra pode ser criada do zero pelas seguintes etapas no Visual Studio:
Crie uma solução.
Adicionar Novo Projeto > Web > Aplicativo Web ASP.NET Core (.NET Framework)
Adicionar Novo Projeto > Área de Trabalho Clássica do Windows > Biblioteca de Classes (.NET
Framework)
No PMC (Console do Gerenciador de Pacotes) dos dois projetos, execute o comando
Install-Package Entityframework .

No projeto de biblioteca de classes, crie classes de modelo de dados e uma classe de contexto, bem como
uma implementação de IDbContextFactory .
No PMC do projeto de biblioteca de classes, execute os comandos Enable-Migrations e
Add-Migration Initial . Caso tenha definido o projeto ASP.NET Core como o projeto de inicialização,
adicione -StartupProjectName EF6 a esses comandos.
No projeto Core, adicione uma referência de projeto ao projeto de biblioteca de classes.
No projeto Core, em Startup.cs, registre o contexto para DI.
No projeto Core, em appsettings.json, adicione a cadeia de conexão.
No projeto Core, adicione um controlador e exibições para verificar se é possível ler e gravar dados.
(Observe que o scaffolding do ASP.NET Core MVC não funcionará com o contexto do EF6 referenciado da
biblioteca de classes.)

Resumo
Este artigo forneceu diretrizes básicas para usar o Entity Framework 6 em um aplicativo ASP.NET Core.

Recursos adicionais
Entity Framework – configuração baseada em código
Armazenamento do Azure no ASP.NET Core
10/04/2018 • 1 min to read • Edit Online

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 no ASP.NET
Core
27/02/2018 • 1 min to read • Edit Online

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
TypeScript
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
Use o Gulp no núcleo do ASP.NET
04/05/2018 • 18 min to read • Edit Online

Por Erik Reitan, Scott Addie, Daniel Roth, e Shayne Boyer


Em um aplicativo web moderna típico, o processo de compilação pode:
Agrupar e minificada arquivos JavaScript e CSS.
Execute ferramentas para chamar as tarefas de empacotamento e minimização antes de cada compilação.
Compilar menos ou SASS arquivos CSS.
Compile arquivos CoffeeScript ou TypeScript para JavaScript.
Um executor de tarefas é uma ferramenta que automatiza a essas tarefas de rotina de desenvolvimento e muito
mais. O Visual Studio fornece suporte interno para dois executores de tarefas comuns de baseados em
JavaScript: Gulp e Grunt.

gulp
Gulp é um baseados em JavaScript streaming compilação Kit de ferramentas do código do lado do cliente. Ele
costuma ser usado para transmitir arquivos do lado do cliente por meio de uma série de processos quando um
evento específico é acionado em um ambiente de compilação. Por exemplo, o Gulp pode ser usado para
automatizar empacotamento e minimização ou a limpeza de um ambiente de desenvolvimento antes de uma
nova compilação.
Um conjunto de tarefas Gulp é definido em gulpfile.js. O JavaScript a seguir inclui módulos de Gulp e especifica
os caminhos de arquivo a ser referenciado de tarefas disponível em breve:

/// <binding Clean='clean' />


"use strict";

var gulp = require("gulp"),


rimraf = require("rimraf"),
concat = require("gulp-concat"),
cssmin = require("gulp-cssmin"),
uglify = require("gulp-uglify");

var paths = {
webroot: "./wwwroot/"
};

paths.js = paths.webroot + "js/**/*.js";


paths.minJs = paths.webroot + "js/**/*.min.js";
paths.css = paths.webroot + "css/**/*.css";
paths.minCss = paths.webroot + "css/**/*.min.css";
paths.concatJsDest = paths.webroot + "js/site.min.js";
paths.concatCssDest = paths.webroot + "css/site.min.css";

O código acima Especifica quais módulos de nó são necessários. O require função importa cada módulo, para
que as tarefas dependentes podem utilizar seus recursos. Cada um dos módulos importados é atribuída a uma
variável. Os módulos podem estar localizados por nome ou caminho. Neste exemplo, os módulos denominado
gulp , rimraf , gulp-concat , gulp-cssmin , e gulp-uglify são recuperados por nome. Além disso, uma série de
caminhos são criadas para que os locais de arquivos CSS e JavaScript podem ser reutilizados e referência de
tarefas. A tabela a seguir fornece descrições dos módulos incluídos no gulpfile.js.
NOME DO MÓDULO DESCRIÇÃO

gulp O sistema de compilação streaming Gulp. Para obter mais


informações, consulte gulp.

rimraf Um módulo de exclusão do nó. Para obter mais informações,


consulte rimraf.

gulp concat Um módulo que concatena arquivos com base em caractere


de nova linha do sistema operacional. Para obter mais
informações, consulte gulp concat.

gulp cssmin Um módulo que minimiza arquivos CSS. Para obter mais
informações, consulte gulp cssmin.

uglify gulp Um módulo que minimiza . js arquivos. Para obter mais


informações, consulte uglify gulp.

Depois que os módulos necessários são importados, as tarefas podem ser especificadas. Aqui, há seis tarefas
registrado, representado pelo código a seguir:

gulp.task("clean:js", function (cb) {


rimraf(paths.concatJsDest, cb);
});

gulp.task("clean:css", function (cb) {


rimraf(paths.concatCssDest, cb);
});

gulp.task("clean", ["clean:js", "clean:css"]);

gulp.task("min:js", function () {
return gulp.src([paths.js, "!" + paths.minJs], { base: "." })
.pipe(concat(paths.concatJsDest))
.pipe(uglify())
.pipe(gulp.dest("."));
});

gulp.task("min:css", function () {
return gulp.src([paths.css, "!" + paths.minCss])
.pipe(concat(paths.concatCssDest))
.pipe(cssmin())
.pipe(gulp.dest("."));
});

gulp.task("min", ["min:js", "min:css"]);

A tabela a seguir fornece uma explicação das tarefas especificado no código acima:

NOME DA TAREFA DESCRIÇÃO

Limpar: js Uma tarefa que usa o módulo de exclusão do nó rimraf para


remover a versão minimizada do arquivo site.js.

Limpar: css Uma tarefa que usa o módulo de exclusão do nó rimraf para
remover a versão minimizada do arquivo site.css.

Limpar Uma tarefa que chama o clean:js tarefa, seguida de


clean:css tarefa.
NOME DA TAREFA DESCRIÇÃO

min:js Uma tarefa que minimiza e concatena todos os arquivos. js na


pasta js. A. min.js arquivos serão excluídos.

min:CSS Uma tarefa que minimiza e concatena todos os arquivos. CSS


dentro da pasta de css. A. min.css arquivos serão excluídos.

min Uma tarefa que chama o min:js tarefa, seguida de


min:css tarefa.

Execução de tarefas padrão


Se você ainda não criou um novo aplicativo Web, crie um novo projeto de aplicativo Web ASP.NET no Visual
Studio.
1. Adicione um novo arquivo JavaScript ao seu projeto e denomine- gulpfile.js, em seguida, copie o código a
seguir.
/// <binding Clean='clean' />
"use strict";

var gulp = require("gulp"),


rimraf = require("rimraf"),
concat = require("gulp-concat"),
cssmin = require("gulp-cssmin"),
uglify = require("gulp-uglify");

var paths = {
webroot: "./wwwroot/"
};

paths.js = paths.webroot + "js/**/*.js";


paths.minJs = paths.webroot + "js/**/*.min.js";
paths.css = paths.webroot + "css/**/*.css";
paths.minCss = paths.webroot + "css/**/*.min.css";
paths.concatJsDest = paths.webroot + "js/site.min.js";
paths.concatCssDest = paths.webroot + "css/site.min.css";

gulp.task("clean:js", function (cb) {


rimraf(paths.concatJsDest, cb);
});

gulp.task("clean:css", function (cb) {


rimraf(paths.concatCssDest, cb);
});

gulp.task("clean", ["clean:js", "clean:css"]);

gulp.task("min:js", function () {
return gulp.src([paths.js, "!" + paths.minJs], { base: "." })
.pipe(concat(paths.concatJsDest))
.pipe(uglify())
.pipe(gulp.dest("."));
});

gulp.task("min:css", function () {
return gulp.src([paths.css, "!" + paths.minCss])
.pipe(concat(paths.concatCssDest))
.pipe(cssmin())
.pipe(gulp.dest("."));
});

gulp.task("min", ["min:js", "min:css"]);

2. Abra o Package. JSON arquivo (Adicionar se não há) e adicione o seguinte.

{
"devDependencies": {
"gulp": "3.9.1",
"gulp-concat": "2.6.1",
"gulp-cssmin": "0.1.7",
"gulp-uglify": "2.0.1",
"rimraf": "2.6.1"
}
}

3. Em Solution Explorer, clique com botão direito gulpfile.jse selecione Explorador do Executador de
tarefas.
Explorador do Executador de tarefas mostra a lista de tarefas de Gulp. (Talvez você precise clicar o
atualização botão que aparece à esquerda do nome do projeto.)

IMPORTANTE
O Explorador do Executador de tarefas item de menu de contexto será exibida apenas se gulpfile.js está no
diretório raiz do projeto.

4. Sob tarefas na Explorador do Executador de tarefas, clique com botão direito limpae selecione
executar no menu pop-up.

Explorador do Executador de tarefas criará uma nova guia chamada limpa e execute a tarefa de
limpeza, conforme definido em gulpfile.js.
5. Com o botão direito do limpa de tarefas e selecione associações > antes de criar.

O antes de criar associação configura a tarefa de limpeza para executar automaticamente antes de cada
build do projeto.
As associações que você configurar o com Explorador do Executador de tarefas são armazenadas na forma de
um comentário na parte superior da sua gulpfile.js e entrarão em vigor somente no Visual Studio. É uma
alternativa que não requer o Visual Studio configurar a execução automática de tarefas de vez em seu . csproj
arquivo. Por exemplo, colocar isso em seu . csproj arquivo:

<Target Name="MyPreCompileTarget" BeforeTargets="Build">


<Exec Command="gulp clean" />
</Target>

Agora que a tarefa de limpeza é executada quando você executar o projeto no Visual Studio ou de um prompt de
comando usando o dotnet executar comando (execute npm install primeiro).

Definir e executar uma nova tarefa


Para definir uma nova tarefa Gulp, modificar gulpfile.js.
1. Adicione o seguinte JavaScript ao final da gulpfile.js:

gulp.task("first", function () {
console.log('first task! <-----');
});

Essa tarefa é denominada first , e simplesmente exibe uma cadeia de caracteres.


2. Salvar gulpfile.js.
3. Em Solution Explorer, clique com botão direito gulpfile.jse selecione Explorador do Executador de
tarefas.
4. Em Explorador do Executador de tarefas, clique com botão direito primeiroe selecione executar.
O texto de saída é exibido. Para obter exemplos com base em cenários comuns, consulte Gulp receitas.

Definindo e tarefas em execução em uma série


Quando você executa várias tarefas, as tarefas executadas simultaneamente por padrão. No entanto, se você
precisar executar tarefas em uma ordem específica, você deve especificar quando cada tarefa é concluída, bem
como quais tarefas dependentes na conclusão de outra tarefa.
1. Para definir uma série de tarefas a serem executadas em ordem, substitua o first tarefas que você
adicionou acima na gulpfile.js com o seguinte:

gulp.task("series:first", function () {
console.log('first task! <-----');
});

gulp.task("series:second", ["series:first"], function () {


console.log('second task! <-----');
});

gulp.task("series", ["series:first", "series:second"], function () {});

Agora você tem três tarefas: series:first , series:second , e series . O series:second tarefa inclui um
segundo parâmetro que especifica uma matriz de tarefas a ser executado e concluído antes do
series:second tarefa será executada. Conforme especificado no código acima, somente o series:first
tarefa deve ser concluída antes do series:second tarefa será executada.
2. Salvar gulpfile.js.
3. Em Solution Explorer, clique com botão direito gulpfile.js e selecione Explorador do Executador de
tarefas se ainda não estiver aberto.
4. Em Explorador do Executador de tarefas, clique com botão direito série e selecione executar.
IntelliSense
O IntelliSense oferece conclusão de código, descrições de parâmetro e outros recursos para aumentar a
produtividade e diminuir os erros. Gulp tarefas são escritas em JavaScript; Portanto, o IntelliSense pode fornecer
assistência durante o desenvolvimento. Conforme você trabalha com JavaScript, IntelliSense listará os objetos,
funções, propriedades e parâmetros que estão disponíveis com base em seu contexto atual. Selecione uma opção
de codificação na lista pop-up fornecida pelo IntelliSense para concluir o código.

Para obter mais informações sobre o IntelliSense, consulte JavaScript IntelliSense.

Ambientes de desenvolvimento, teste e produção


Quando o Gulp é usado para otimizar os arquivos do lado do cliente para produção e preparo, os arquivos
processados são salvos em um local de preparo e produção. O cshtml arquivo usa o ambiente marca auxiliar
para fornecer duas versões diferentes de arquivos CSS. É uma versão dos arquivos CSS para o desenvolvimento
e a outra versão é otimizada para preparação e produção. No Visual Studio de 2017, quando você altera o
ASPNETCORE_ENVIRONMENT variável de ambiente Production , Visual Studio criará o aplicativo Web e um
link para os arquivos CSS minimizados. A marcação a seguir mostra o ambiente marca auxiliares que contém as
marcações de link para o Development CSS arquivos e o minimizada Staging, Production arquivos CSS.
<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>

Alternar entre ambientes


Para alternar entre a compilação para ambientes diferentes, modifique o ASPNETCORE_ENVIRONMENT valor
da variável de ambiente.
1. Em Explorador do Executador de tarefas, verifique o min tarefa foi definida para executar antes de
criar.
2. Em Solution Explorer, clique no nome do projeto e selecione propriedades.
A folha de propriedades para o aplicativo Web é exibida.
3. Clique na guia Depurar.
4. Definir o valor de : ambiente de hospedagem variável de ambiente Production .
5. Pressione F5 para executar o aplicativo em um navegador.
6. Na janela do navegador, a página e selecione Exibir código-fonte para exibir o HTML da página.
Observe que os links de folha de estilos apontam para os arquivos CSS minimizados.
7. Feche o navegador para interromper o aplicativo Web.
8. No Visual Studio, retorne a folha de propriedades para o aplicativo Web e altere o : ambiente de
hospedagem de volta para a variável de ambiente Development .
9. Pressione F5 para executar o aplicativo em um navegador novamente.
10. Na janela do navegador, a página e selecione Exibir código-fonte para ver o HTML da página.
Observe que os links de folha de estilos apontam para as versões unminified dos arquivos CSS.
Para obter mais informações relacionadas a ambientes em ASP.NET Core, consulte trabalhar com vários
ambientes.

Detalhes da tarefa e o módulo


Uma tarefa Gulp está registrada com um nome de função. É possível especificar dependências se outras tarefas
devem ser executadas antes da tarefa atual. Funções adicionais permitem que você execute e observar as tarefas
de Gulp, bem como definir a origem (src) e de destino (dest) dos arquivos que está sendo modificados. Estas são
as funções de API Gulp primárias:

FUNÇÃO GULP SINTAXE DESCRIÇÃO

tarefa gulp.task(name[, deps], fn) { } O task função cria uma tarefa. O


name parâmetro define o nome da
tarefa. O deps parâmetro contém
uma matriz de tarefas a serem
concluídas antes de executa essa tarefa.
O fn parâmetro representa uma
função de retorno de chamada que
executa as operações da tarefa.

Inspecionar gulp.watch(glob [, opts], tasks) O watch função monitora arquivos e


{ } executa tarefas quando ocorre uma
alteração de arquivo. O glob
parâmetro é um string ou array
que determina quais arquivos assistir. O
opts parâmetro fornece observando
as opções de arquivo adicionais.

src gulp.src(globs[, options]) { } O src função fornece os arquivos que


correspondem os valores glob. O
glob parâmetro é um string ou
array que determina quais arquivos
para leitura. O options parâmetro
fornece outras opções de arquivo.

dest gulp.dest(path[, options]) { } O dest função define um local para o


qual os arquivos podem ser gravados.
O path parâmetro é uma cadeia de
caracteres ou uma função que
determina a pasta de destino. O
options parâmetro é um objeto que
especifica as opções de pasta de saída.

Para obter informações de referência de API Gulp, consulte Gulp documentos API.

Gulp receitas
A comunidade de Gulp fornece Gulp receitas. Essas receitas consistem em tarefas de Gulp para abordar cenários
comuns.

Recursos adicionais
Documentação de gulp
Empacotamento e minimização no núcleo do ASP.NET
Use o assistente no núcleo do ASP.NET
Use o assistente no núcleo do ASP.NET
10/04/2018 • 17 min to read • Edit Online

Por Noel arroz


Pesado é um executor de tarefas do JavaScript que automatiza minimização de script, a compilação TypeScript,
ferramentas de "pano" de qualidade do código, pré-processadores de CSS e praticamente qualquer tarefa
repetitiva que precisa fazer para dar suporte ao desenvolvimento de cliente. Pesado tem suporte total no Visual
Studio, embora os modelos de projeto do ASP.NET usam Gulp por padrão (consulte usar Gulp).
Este exemplo usa um projeto vazio do ASP.NET Core como ponto de partida, para mostrar como automatizar o
processo de compilação do cliente desde o início.
O exemplo concluído limpa o diretório de implantação de destino, combina arquivos JavaScript, verifica a
qualidade do código, condensa o conteúdo do arquivo JavaScript e implanta para a raiz do seu aplicativo web.
Usaremos os seguintes pacotes:
Assistente de: pacote de executor de tarefas a pesado.
Limpeza de Contribuidor pesado: um plug-in que remove os arquivos ou diretórios.
Assistente de Contribuidor de jshint: um plug-in que analisa a qualidade do código JavaScript.
Assistente de Contribuidor de concat: um plug-in que une os arquivos em um único arquivo.
uglify pesado-Contribuidor: um plug-in que minimiza o JavaScript para reduzir o tamanho.
Observação de Contribuidor pesado: um plug-in que observa a atividade de arquivos.

Preparando o aplicativo
Para começar, configure um novo aplicativo web vazio e adicionar arquivos de exemplo de TypeScript. Arquivos
typeScript são compilados automaticamente para JavaScript usando as configurações do Visual Studio e serão
nosso matérias-primas para processar usando o assistente.
1. No Visual Studio, crie um novo ASP.NET Web Application .
2. No novo projeto ASP.NET caixa de diálogo, selecione o ASP.NET Core vazio modelo e clique no botão
Okey.
3. No Gerenciador de soluções, revise a estrutura do projeto. O \src pasta inclui vazio wwwroot e
Dependencies nós.
4. Adicionar uma nova pasta chamada TypeScript para o diretório do projeto.
5. Antes de adicionar todos os arquivos, certifique-se de que o Visual Studio tem a opção ' Compilar ao
salvar ' para arquivos TypeScript check. Navegue até ferramentas > opções > Editor de texto >
Typescript > Projeto:

6. Clique com botão direito do TypeScript diretório e selecione Adicionar > Novo Item no menu de
contexto. Selecione o arquivo JavaScript item e nomeie o arquivo Tastes.ts (Observe o *extensão. TS ).
Copie a linha de código do TypeScript abaixo no arquivo (quando você salva, um novo Tastes.js arquivo
aparecerá com a origem de JavaScript).

enum Tastes { Sweet, Sour, Salty, Bitter }

7. Adicionar um segundo arquivo para o TypeScript diretório e nomeie-o Food.ts . Copie o código abaixo
para o arquivo.
class Food {
constructor(name: string, calories: number) {
this._name = name;
this._calories = calories;
}

private _name: string;


get Name() {
return this._name;
}

private _calories: number;


get Calories() {
return this._calories;
}

private _taste: Tastes;


get Taste(): Tastes { return this._taste }
set Taste(value: Tastes) {
this._taste = value;
}
}

Configurando NPM
Em seguida, configure NPM para baixar pesado e tarefas do assistente.
1. No Gerenciador de soluções, clique com o botão direito e selecione Adicionar > Novo Item no menu de
contexto. Selecione o arquivo de configuração NPM item, deixe o nome padrão, Package. JSONe clique
no adicionar botão.
2. No Package. JSON arquivo, dentro de devDependencies chaves de objeto, digite "pesado". Selecione
grunt o Intellisense de lista e pressione a tecla Enter. Visual Studio será colocada entre aspas no nome do
pacote pesado e adicionar dois-pontos. À direita dos dois pontos, selecione a versão estável mais recente
do pacote da parte superior da lista do Intellisense (pressione Ctrl-Space se o Intellisense não aparece).

OBSERVAÇÃO
Usa NPM controle de versão semântico para organizar as dependências. Controle de versão semântico, também
conhecido como SemVer, identifica os pacotes com o esquema de numeração .. . IntelliSense simplifica o controle de
versão semântico, mostrando apenas algumas opções comuns. O item superior na lista do Intellisense (0.4.5 no
exemplo acima) é considerado a versão estável mais recente do pacote. O símbolo de acento circunflexo (^)
corresponde a mais recente versão principal e o til () co rresp o n de a versão secu n dária mais recen te. Consulte o referência
de analisador NPM semver versão como um guia para a expressividade completa que fornece SemVer.

3. Adicionar mais dependências carregar pesadom-Contribuidor -* pacotes para limpa, jshint, concat, uglifye
inspecionar conforme mostrado no exemplo a seguir. As versões não precisam coincidir com o exemplo.
"devDependencies": {
"grunt": "0.4.5",
"grunt-contrib-clean": "0.6.0",
"grunt-contrib-jshint": "0.11.0",
"grunt-contrib-concat": "0.5.1",
"grunt-contrib-uglify": "0.8.0",
"grunt-contrib-watch": "0.6.1"
}

4. Salve o Package. JSON arquivo.


Baixarão os pacotes para cada item devDependencies, juntamente com todos os arquivos que requer que cada
pacote. Você pode encontrar os arquivos de pacote no node_modules diretório, permitindo que o Mostrar todos
os arquivos botão no Gerenciador de soluções.

OBSERVAÇÃO
Se você precisar, você pode restaurar manualmente as dependências no Gerenciador de soluções clicando em
Dependencies\NPM e selecionando o restaurar pacotes opção de menu.

Assistente de configuração
Pesado estiver configurado para usar um manifesto chamado Gruntfile.js que define, carrega e registra as tarefas
que podem ser executadas manualmente ou configuradas para ser executado automaticamente com base em
eventos no Visual Studio.
1. Clique com o botão direito e selecione Adicionar > Novo Item. Selecione o arquivo de configuração
Grunt opção, deixe o nome padrão, Gruntfile.jse clique no adicionar botão.
O código inicial inclui uma definição de módulo e o grunt.initConfig() método. O initConfig() é usado
para definir opções para cada pacote, e o restante do módulo serão carregadas e registrar tarefas.

module.exports = function (grunt) {


grunt.initConfig({
});
};

2. Dentro de initConfig() método, adicionar opções para o clean conforme mostrado no exemplo de
tarefa Gruntfile.js abaixo. A tarefa de limpeza aceita uma matriz de cadeias de caracteres de diretório. Essa
tarefa remove arquivos de wwwroot/lib e o diretório temp/inteiro.

module.exports = function (grunt) {


grunt.initConfig({
clean: ["wwwroot/lib/*", "temp/"],
});
};

3. Abaixo do método initConfig(), adicione uma chamada para grunt.loadNpmTasks() . Isso fará a tarefa
executável do Visual Studio.

grunt.loadNpmTasks("grunt-contrib-clean");

4. Salvar Gruntfile.js. O arquivo deve ser semelhante a captura de tela abaixo.

5. Clique com botão direito Gruntfile.js e selecione Explorador do Executador de tarefas no menu de
contexto. A janela Explorador do Executador de tarefas será aberto.

6. Verifique clean mostra em tarefas no Explorador do Executador de tarefas.

7. A tarefa de limpeza e selecione executar no menu de contexto. Uma janela de comando exibe o progresso
da tarefa.
OBSERVAÇÃO
Não existem arquivos ou diretórios para limpar ainda. Se desejar, você pode criá-las manualmente no Gerenciador
de soluções e, em seguida, executar a tarefa de limpeza como um teste.

8. No método initConfig(), adicione uma entrada para concat usando o código abaixo.
O src matriz de propriedade lista arquivos combinar, na ordem em que eles devem ser combinados. O
dest propriedade atribui o caminho para o arquivo combinado que é produzido.

concat: {
all: {
src: ['TypeScript/Tastes.js', 'TypeScript/Food.js'],
dest: 'temp/combined.js'
}
},

OBSERVAÇÃO
O all propriedade no código acima é o nome de um destino. Destinos são usados em algumas tarefas pesado
para permitir que vários ambientes de desenvolvimento. Você pode exibir os destinos internos usando o Intellisense
ou atribuir seu próprio.

9. Adicionar o jshint tarefas usando o código abaixo.


O utilitário de qualidade do código jshint é executado em todos os arquivos JavaScript encontrado no
diretório temp.

jshint: {
files: ['temp/*.js'],
options: {
'-W069': false,
}
},

OBSERVAÇÃO
A opção "-W069" é um erro gerado pelo jshint quando colchete de JavaScript usa a sintaxe para atribuir uma
propriedade em vez da notação de ponto, ou seja, Tastes["Sweet"] em vez de Tastes.Sweet . A opção desativa
o aviso para permitir que o restante do processo para continuar.

10. Adicionar o uglify tarefas usando o código abaixo.


A tarefa minimiza o combined.js arquivo encontrado no diretório temp e cria o arquivo de resultado no
wwwroot/lib seguindo a convenção de nomenclatura padrão <nome de arquivo>. min.js.

uglify: {
all: {
src: ['temp/combined.js'],
dest: 'wwwroot/lib/combined.min.js'
}
},

11. Sob o grunt.loadNpmTasks() de chamada que carrega a limpeza de Contribuidor pesado, incluem a
mesma chamada para jshint, concat e uglify usando o código abaixo.

grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');

12. Salvar Gruntfile.js. O arquivo deve ser algo como o exemplo a seguir.

13. Observe que a lista de tarefas do Gerenciador de executor inclui clean , concat , jshint e uglify
tarefas. Execute cada tarefa na ordem e observar os resultados no Gerenciador de soluções. Cada tarefa
deve ser executada sem erros.

A tarefa concat cria um novo combined.js de arquivo e o coloca na pasta temp. A tarefa de jshint
simplesmente executa e não produz saída. A tarefa uglify cria um novo combined.min.js de arquivo e o
coloca na wwwroot/lib. Após a conclusão, a solução deve ser semelhante a captura de tela abaixo:

OBSERVAÇÃO
Para obter mais informações sobre as opções para cada pacote, visite https://www.npmjs.com/ e o nome do pacote
na caixa de pesquisa na página principal de pesquisa. Por exemplo, você pode pesquisar o pacote limpeza de
Contribuidor pesado para obter um link de documentação que explica a todos os seus parâmetros.

Todos juntos agora


Use o Assistente de registerTask() método para executar uma série de tarefas em uma determinada sequência.
Por exemplo, para executar o exemplo etapas acima na ordem limpa -> concat -> jshint -> uglify, adicione o
código abaixo para o módulo. O código deve ser adicionado no mesmo nível como as chamadas
loadNpmTasks(), fora initConfig.

grunt.registerTask("all", ['clean', 'concat', 'jshint', 'uglify']);

A nova tarefa aparece no Explorador do Executador de tarefas em tarefas de Alias. Pode-se com o botão direito e
executá-lo como faria com outras tarefas. O all tarefa executará clean , concat , jshint e uglify , em ordem.

Monitorando alterações
Um watch tarefa fica de olho em arquivos e diretórios. O relógio dispara tarefas automaticamente se detectar
alterações. Adicione o código abaixo para initConfig para observar as alterações *no diretório TypeScript. js. Se
um arquivo JavaScript for alterado, watch executará o all tarefa.

watch: {
files: ["TypeScript/*.js"],
tasks: ["all"]
}
Adicionar uma chamada para loadNpmTasks() para mostrar o watch tarefa no Explorador do Executador de
tarefas.

grunt.loadNpmTasks('grunt-contrib-watch');

Clique na tarefa de inspeção no Explorador do Executador de tarefas e selecione Executar no menu de contexto. A
janela de comando que mostra a execução de tarefa watch exibirá um "Aguardando..." . Abra um dos arquivos
TypeScript, adicione um espaço e, em seguida, salve o arquivo. Isso disparar a tarefa de inspeção e disparar
outras tarefas executadas em ordem. Captura de tela abaixo mostra uma exemplo de execução.

Associação a eventos do Visual Studio


A menos que você deseja iniciar manualmente as tarefas toda vez que você trabalha no Visual Studio, você pode
associar tarefas a serem antes de criar, depois de criar, limpar, e Projeto aberto eventos.
Vamos associar watch para que ele seja executado sempre que o Visual Studio abrirá. No Explorador do
Executador de tarefas, a tarefa de inspeção e selecione associações > Abrir projeto no menu de contexto.

Descarregar e recarregar o projeto. Quando o projeto é carregado novamente, a tarefa de inspeção iniciará a
execução automaticamente.

Resumo
Pesado é um executor de tarefa avançada que pode ser usado para automatizar a maioria das tarefas de
compilação do cliente. Pesado aproveita NPM para fornecer seus pacotes e recursos de ferramentas de
integração com o Visual Studio. Explorador do Executador de tarefas do Visual Studio detecta alterações nos
arquivos de configuração e fornece uma interface conveniente para executar tarefas, exibir tarefas em execução e
associar tarefas a eventos do Visual Studio.
Recursos adicionais
Usar o Gulp
Gerenciar pacotes do lado do cliente com Bower no
ASP.NET Core
04/05/2018 • 10 min to read • Edit Online

Por Rick Anderson, Noel arroz, e Scott Addie

IMPORTANTE
Enquanto Bower é mantida, seus mantenedores recomendam usando uma solução diferente. O Gerenciador de biblioteca
(LibMan abreviada) é o novo sistema de gerenciamento de conteúdo estático do lado do cliente do Visual Studio. Yarn com
Webpack é uma alternativa popular para a qual instruções de migração estão disponíveis.

Bower chama a mesmo "Um Gerenciador de pacotes para a web". No ecossistema do .NET, ele preenche essa
lacuna deixado pela incapacidade do NuGet para entregar os arquivos de conteúdo estáticos. Para projetos do
ASP.NET Core, esses arquivos estáticos são inerentes a bibliotecas de cliente como jQuery e inicialização. Para
bibliotecas .NET, você usar NuGet Gerenciador de pacotes.
Processo de compilação de projetos novos criados com os modelos de projeto do ASP.NET Core configurar lado
do cliente. jQuery e Bootstrap são instalados, e tem suporte em Bower.
Pacotes do lado do cliente são listados no bower. JSON arquivo. Configura os modelos de projeto do ASP.NET
Core bower. JSON com inicialização, validação jQuery e jQuery.
Neste tutorial, vamos adicionar suporte para fonte Awesome. Bower pacotes podem ser instalados com o
gerenciar pacotes de Bower interface do usuário ou manualmente o bower. JSON arquivo.
Instalação por meio de gerenciar Bower pacotes da interface do usuário
Criar um novo aplicativo Web do ASP.NET Core com o aplicativo Web do ASP.NET Core (.NET Core)
modelo. Selecione aplicativo Web e nenhuma autenticação.
Clique com botão direito no projeto no Gerenciador de soluções e selecione gerenciar pacotes de Bower
(como alternativa no menu principal, projeto > gerenciar pacotes Bower).
No Bower: <nome do projeto> janela, clique na guia de "Procurar" e, em seguida, filtre a lista de pacotes
inserindo font-awesome na caixa de pesquisa:
Confirme se o "Salvar alterações em bower. JSON" caixa de seleção é marcada. Selecione uma versão na
lista suspensa e clique no instalar botão. O saída janela mostra os detalhes da instalação.
Instalação manual em bower. JSON
Abra o bower. JSON e adicione "fonte o incríveis" para as dependências. IntelliSense mostra os pacotes
disponíveis. Quando um pacote é selecionado, as versões disponíveis são exibidas. As imagens abaixo são mais
antigas e não coincidir com o que você vê.

Bower usa controle de versão semântico para organizar as dependências. Controle de versão semântico, também
conhecido como SemVer, identifica os pacotes com o esquema de numeração <principal >.< secundária >. <patch
>. IntelliSense simplifica o controle de versão semântico, mostrando apenas algumas opções comuns. O item
superior na lista do IntelliSense (4.6.3 no exemplo acima) é considerado a versão estável mais recente do pacote.
O símbolo de acento circunflexo (^) corresponde a mais recente versão principal e o til () corresponde a versão secundária
mais recente.

Salve o bower. JSON arquivo. O Visual Studio inspeciona o bower. JSON arquivo para que as alterações. Ao
salvar, o bower instalar comando é executado. Consulte a janela de saída npm/Bower modo de exibição para o
comando executado.
Abra o bowerrc de arquivos em bower. JSON. O directory está definida como wwwroot/lib que indica o local
Bower instalará os ativos de pacote.

{
"directory": "wwwroot/lib"
}

Você pode usar a caixa de pesquisa no Gerenciador de soluções para localizar e exibir o pacote incrível de fonte.
Abra o exibições \ compartilhadas_cshtml de arquivo e adicione o arquivo CSS incrível de fonte para o ambiente
auxiliar de marca para Development . No Gerenciador de soluções, arrastar e soltar fonte awesome.css dentro de
<environment names="Development"> elemento.

<environment names="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
<link href="~/lib/font-awesome/css/font-awesome.css" rel="stylesheet" />
</environment>

Um aplicativo de produção, você adicionaria fonte awesome.min.css para o auxiliar de marca de ambiente para
Staging,Production .

Substitua o conteúdo do Views\Home\About.cshtml arquivo Razor com a seguinte marcação:

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

<div class="list-group">
<a class="list-group-item" href="#"><i class="fa fa-home fa-fw" aria-hidden="true"></i>&nbsp; Home</a>
<a class="list-group-item" href="#"><i class="fa fa-book fa-fw" aria-hidden="true"></i>&nbsp; Library</a>
<a class="list-group-item" href="#"><i class="fa fa-pencil fa-fw" aria-hidden="true"></i>&nbsp;
Applications</a>
<a class="list-group-item" href="#"><i class="fa fa-cog fa-fw" aria-hidden="true"></i>&nbsp; Settings</a>
</div>

Execute o aplicativo e navegue até o modo de exibição sobre para verificar o funcionamento do pacote incrível de
fonte.

Explorando o processo de compilação do lado do cliente


A maioria dos modelos de projeto do ASP.NET Core já estão configurados para usar o Bower. Este passo a passo
próxima começa com um projeto vazio do ASP.NET Core e adiciona cada pedaço manualmente, portanto você
pode ter uma ideia de como Bower é usado em um projeto. Você pode ver o que acontece com a estrutura do
projeto e o tempo de execução de saída como cada alteração de configuração é feita.
As etapas gerais para usar o processo de compilação do lado do cliente com Bower são:
Defina pacotes usados em seu projeto.
Pacotes de referência de suas páginas da web.
Definir pacotes
Depois que você listar pacotes no bower. JSON arquivo, o Visual Studio irá baixá-los. O exemplo a seguir usa
Bower para carregar jQuery e inicialização para o wwwroot pasta.
Criar um novo aplicativo Web do ASP.NET Core com o aplicativo Web do ASP.NET Core (.NET Core)
modelo. Selecione o vazio modelo de projeto e clique em Okey.
No Gerenciador de soluções, clique com botão direito no projeto > Adicionar Novo Item e selecione
Bower arquivo de configuração. Observação: A bowerrc arquivo também é adicionado.
Abra bower. JSON, adicione jquery e inicialização para o dependencies seção. Resultante bower. JSON
arquivo se parecerá com o exemplo a seguir. As versões serão alterado ao longo do tempo e podem não
corresponder a imagem a seguir.

{
"name": "asp.net",
"private": true,
"dependencies": {
"jquery": "3.1.1",
"bootstrap": "3.3.7"
}
}

Salve o bower. JSON arquivo.


Verifique se o projeto inclui o bootstrap e jQuery diretórios wwwroot/lib. Bower usa o bowerrc arquivo
para instalar os ativos em wwwroot/lib.
Observação: A interface do usuário "Gerenciar pacotes de Bower" fornece uma alternativa à edição de
arquivos manual.
Habilitar arquivos estáticos
Adicionar o Microsoft.AspNetCore.StaticFiles pacote NuGet para o projeto.
Habilitar arquivos estáticos sejam atendidos com o middleware de arquivo estático. Adicionar uma chamada
para UseStaticFiles para o Configure método Startup .

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;

public class Startup


{
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles();

app.Run(async (context) =>


{
await context.Response.WriteAsync("Hello World!");
});
}
}

Pacotes de referência
Nesta seção, você criará uma página HTML para verificar se que ele pode acessar os pacotes implantados.
Adicionar uma nova página HTML chamada Index.html para o wwwroot pasta. Observação: Você deve
adicionar o arquivo HTML para o wwwroot pasta. Por padrão, o conteúdo estático não pode ser servido
fora wwwroot. Consulte trabalhar com arquivos estáticos para obter mais informações.
Substitua o conteúdo do index com a seguinte marcação:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Bower Example</title>
<link href="lib/bootstrap/dist/css/bootstrap.css" rel="stylesheet" />
</head>
<body>
<div class="jumbotron">
<h1>Using the jumbotron style</h1>
<p>
<a class="btn btn-primary btn-lg" role="button">Stateful button</a>
</p>
</div>
<script src="lib/jquery/dist/jquery.js"></script>
<script src="lib/bootstrap/dist/js/bootstrap.js"></script>
<script>
$(".btn").click(function () {
$(this).text('loading')
.delay(1000)
.queue(function () {
$(this).text('reset');
$(this).dequeue();
});
});
</script>
</body>

</html>

Execute o aplicativo e navegue até http://localhost:<port>/Index.html . Como alternativa, com Index.html


aberta, pressione Ctrl+Shift+W . Verifique se que o estilo de jumbotron é aplicado, o código jQuery
responde quando o botão é clicado e que o botão inicialização muda de estado.
Crie sites lindos, respondendo com inicialização e
ASP.NET Core
27/04/2018 • 22 min to read • Edit Online

Por Steve Smith


Bootstrap atualmente é a estrutura da web mais popular de desenvolvimento de aplicativos web responsivo. Ele
oferece uma série de recursos e benefícios que podem melhorar a experiência dos usuários ao seu site, se você for
um iniciante no front-end design e desenvolvimento ou de um especialista. Bootstrap é implantado como um
conjunto de arquivos CSS e JavaScript e foi projetada para ajudar a dimensionar seu site ou aplicativo com
eficiência de telefones para tablets para áreas de trabalho.

Introdução
Há várias maneiras para começar a Bootstrap. Se você estiver iniciando um novo aplicativo web no Visual Studio,
você pode escolher o modelo de início padrão para o ASP.NET Core, em cujo caso o Bootstrap virá pré-instalados:

A adição do bootstrap para um projeto ASP.NET Core é simplesmente uma questão de adicioná-la no bower.
JSON como uma dependência:

{
"name": "asp.net",
"private": true,
"dependencies": {
"bootstrap": "3.3.6",
"jquery": "2.2.0",
"jquery-validation": "1.14.0",
"jquery-validation-unobtrusive": "3.2.6"
}
}

Essa é a maneira recomendada para adicionar o bootstrap para um projeto do ASP.NET Core.
Você também pode instalar o bootstrap usando um dos vários gerenciadores de pacotes, como Bower, npm ou
NuGet. Em cada caso, o processo é essencialmente o mesmo:
Bower

bower install bootstrap

npm

npm install bootstrap

NuGet

Install-Package bootstrap

OBSERVAÇÃO
A maneira recomendada para instalar dependências de cliente como o bootstrap no ASP.NET Core é por meio do Bower
(usando bower. JSON, conforme mostrado acima). O uso do npm/NuGet são mostrados para demonstrar como Bootstrap
pode ser facilmente adicionada a outros tipos de aplicativos web, incluindo versões anteriores do ASP.NET.

Se você estiver fazendo referência a suas próprias versões locais do bootstrap, você precisará fazer referência a
eles em todas as páginas que irá usá-lo. Em produção, você deve fazer referência bootstrap usando uma CDN. No
modelo de site ASP.NET padrão, o cshtml arquivo assim como este:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - WebApplication1</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.6/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>
<div 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">WebApplication1</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>
@await Html.PartialAsync("_LoginPartial")
</div>
</div>
</div>
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>&copy; 2016 - WebApplication1</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">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/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">
</script>
<script src="~/js/site.min.js" asp-append-version="true"></script>
</environment>

@RenderSection("scripts", required: false)


</body>
</html>

OBSERVAÇÃO
Se você estiver usando qualquer um dos plug-ins do Bootstrap jQuery, você também precisará fazer referência a jQuery.

Recursos e modelos básicos


O modelo do bootstrap mais básico é muito parecido com o arquivo cshtml mostrado acima e simplesmente
inclui um menu básico para navegação e um local para renderizar o restante da página.
Navegação básica
O modelo padrão usa um conjunto de <div> elementos para processar uma barra de navegação superior e o
corpo principal da página. Se você estiver usando HTML5, você pode substituir o primeiro <div> marca com um
<nav> marca para obter o mesmo efeito, mas com semântica mais precisa. Este primeiro em <div> você pode
ver, há vários outros. Primeiro, um <div> com uma classe de "contêiner" e, em seguida, em que, mais de dois
<div> elementos: "navbar-cabeçalho" e "navbar-recolher". Cabeçalho navbar div inclui um botão que será exibido
quando a tela estiver abaixo de uma determinada largura mínima, mostrando 3 linhas horizontais (uma chamada
"ícone de hambúrguer"). O ícone é renderizado usando puro HTML e CSS; Nenhuma imagem é necessária. Este é
o código que exibe o ícone, com cada o marcas de renderização de uma das barras brancas:
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>

Ele também inclui o nome do aplicativo, que aparece no canto superior esquerdo. O menu de navegação principal
é processado pelo <ul> elemento dentro do segundo div e inclui links para casa, aproximadamente e entre em
contato com. Abaixo do painel de navegação, o corpo principal de cada página é renderizado em outro <div> ,
marcado com as classes de "contêiner" e "conteúdo do corpo". No arquivo layout simples padrão mostrado aqui, o
conteúdo da página é renderizado pela exibição específica associada com a página e, em seguida, um simples
<footer> é adicionada ao final do <div> elemento. Você pode ver como o interno sobre página aparece usando
este modelo:

A barra de navegação recolhida, com o botão "hambúrguer" no canto superior direito, é exibido quando a janela
cai abaixo de uma determinada largura:
Clicando no ícone revela os itens de menu em uma gaveta vertical que slides para baixo da parte superior da
página:

Tipografia e links
Bootstrap configura tipografia básica, cores e formatação em seu arquivo CSS de link do site. Esse arquivo CSS
inclui estilos de padrão para tabelas, botões, elementos de formulário, imagens e muito mais (mais). Um recurso
útil é o sistema de layout de grade, abordado em seguida. Um recurso útil é o sistema de layout de grade,
abordado em seguida.
Grades
Um dos recursos mais populares de Bootstrap é o sistema de layout de grade. Aplicativos web modernos devem
evitar usar o <table> marca de layout, em vez disso, restringir o uso desse elemento para dados de tabela reais.
Em vez disso, colunas e linhas podem ser dispostas usando uma série de <div> elementos e as classes CSS
apropriadas. Há várias vantagens dessa abordagem, incluindo a capacidade de ajustar o layout de grade para
exibir verticalmente em estreitas telas, como em telefones.
Sistema de layout de grade do bootstrap é baseado em doze colunas. Esse número foi escolhido porque pode ser
dividido uniformemente em 1, 2, 3 ou 4 colunas e a largura das colunas pode variar de 1/12 da largura da tela
vertical. Para começar a usar o sistema de layout de grade, você deve começar com um contêiner <div> e, em
seguida, adicione uma linha <div> , conforme mostrado aqui:

<div class="container">
<div class="row">
...
</div>
</div>

Em seguida, adicione adicionais <div> elementos para cada coluna e especifique o número de colunas que
<div> devem ocupar (fora de 12 ) como parte de uma classe CSS começando com "col - md-". Por exemplo, se
você quiser simplesmente ter duas colunas de tamanho igual, você usaria uma classe de "col-md-6" para cada um.
Nesse caso, "md" é a abreviação de "Médio" e se refere a tamanhos de exibição de tamanho padrão de
computador desktop. Há quatro opções diferentes, que você pode escolher, e cada será usada para larguras
superior, a menos que substituído (para que se você quiser que o layout para ser corrigido, independentemente da
largura da tela, você pode especificar apenas as classes de xs).

PREFIXO DA CLASSE CSS CAMADA DO DISPOSITIVO LARGURA

col-xs- Telefones < 768px

col-sm- Tablets >= 768px

col-md - Áreas de trabalho >= 992px

col-lg - Exibe maior de área de trabalho >= 1200px

Ao especificar duas colunas com "col-md-6" layout resultante será duas colunas com resoluções de área de
trabalho, mas essas duas colunas serão empilhadas verticalmente quando renderizado em dispositivos menores
(ou uma janela de navegador mais estreita em uma área de trabalho), permitindo aos usuários exibir facilmente
conteúdo sem precisar rolar horizontalmente.
Bootstrap padrão será sempre um layout de coluna única, portanto você precisa apenas especificar colunas
quando você quiser mais de uma coluna. A única vez em que você deseja especificar explicitamente que um
<div> ocupem todas as 12 colunas seria substituir o comportamento de um maior nível de dispositivo. Ao
especificar várias classes de camada do dispositivo, talvez seja necessário redefinir o processamento de coluna em
determinados pontos. Adicionar um div clearfix que só é visível dentro de um visor de determinados conseguir
isso, conforme mostrado aqui:
No exemplo acima, um e dois compartilham uma linha no layout "md", enquanto dois e três compartilham uma
linha no layout de "xs". Sem o clearfix <div> , dois e três não são exibidos corretamente no modo de exibição de
"xs" (Observe que apenas um, quatro e cinco é mostrados):
Neste exemplo, apenas uma única linha <div> foi usado, e ainda Bootstrap principalmente fez o certo em relação
o layout e a pilha de colunas. Normalmente, você deve especificar uma linha <div> para cada linha horizontal
requer o layout e claro, você pode aninhar grades Bootstrap dentro de uma outra. Quando você fizer isso, cada
grade aninhada ocupará 100% da largura do elemento no qual ele é colocado, que pode ser subdividida usando
classes de coluna.
Jumbotron
Se você usou os modelos do ASP.NET MVC padrão no Visual Studio 2012 ou 2013, você provavelmente já viu
Jumbotron em ação. Ela se refere a uma seção grande de largura inteira de uma página que pode ser usada para
exibir uma imagem de plano de fundo grandes, uma chamada para ação, um AdRotator ou elementos
semelhantes. Para adicionar um jumbotron para uma página, basta adicionar uma <div> e dê a ele uma classe de
"jumbotron", em seguida, coloque um contêiner <div> dentro e adicionar o seu conteúdo. Podemos facilmente
ajustar o padrão de página para usar um jumbotron para os títulos principais exibe:

Botões
As classes de botão padrão e as cores são mostradas na figura abaixo.
Notificações
Selos consultem textos explicativos pequenos, geralmente numéricos ao lado de um item de navegação. Eles
podem indicar um número de mensagens ou notificações de espera, ou a presença de atualizações. Especificar
essas notificações é tão simple quanto adicionando um <span> que contém o texto, com a classe de "notificação":

Alertas
Talvez seja necessário exibir algum tipo de notificação de alerta ou para usuários do seu aplicativo. Que é onde as
classes de alerta padrão são úteis. Há quatro níveis de severidade diferente com esquemas de cores associadas:
Menus e barras de navegação
Nosso layout já inclui uma barra de navegação padrão, mas o tema do bootstrap dá suporte a opções de estilo
adicionais. Podemos facilmente optar por exibir a barra de navegação verticalmente em vez de horizontalmente se
preferirmos, bem como adicionar itens de subnavegação aos menus de atalho. Menus de navegação simples,
como as faixas guia baseiam-se na parte superior do <ul> elementos. Eles podem ser facilmente criados, apenas
fornecendo-os com as classes CSS "nav" e "nav-guias":
Barras de navegação são criadas da mesma forma, mas são um pouco mais complexas. Eles começar com um
<nav> ou <div> com a classe de "barra de navegação", no qual um div de contêiner contém o restante dos
elementos. Nossa página inclui uma barra de navegação em seu cabeçalho já – mostrado a seguir expande
simplesmente neste, adicionando suporte para um menu suspenso:
Elementos adicionais
O tema padrão também pode ser usado para apresentar tabelas HTML em um estilo bem formatado, incluindo
suporte para modos de exibição distribuídos. Há rótulos com estilos que são semelhantes dos botões. Você pode
criar menus suspensos personalizados que oferecem suporte a opções de estilo adicionais além do HTML padrão
<select> elemento, junto com as barras de navegação como o nosso site de início padrão já está usando. Se você
precisar de uma barra de progresso, há vários estilos para escolha, bem como a lista de grupos e painéis que
incluem um título e o conteúdo. Explore as opções adicionais do tema de Bootstrap padrão aqui:
http://getbootstrap.com/examples/theme/
Mais temas
Você pode estender o tema de Bootstrap padrão, substituindo alguns ou todos os seus CSS, ajustar as cores e
estilos para atender às necessidades do seu próprio aplicativo. Se você deseja iniciar a partir de um tema pronto,
há vários galerias de tema disponíveis online que especializados em temas de Bootstrap, como
WrapBootstrap.com (que tem uma variedade de temas comerciais) e Bootswatch.com (que oferece temas livres).
Alguns dos modelos disponíveis pagos fornecem uma grande quantidade de funcionalidade sobre o tema de
Bootstrap básica, como suporte avançado para administrativas menus e painéis avançados gráficos e medidores.
Um exemplo de um modelo pago popular está Inspinia, para a venda de US $18, que inclui um modelo de
ASP.NET MVC5 além AngularJS e versões HTML estáticas. Abaixo está uma captura de tela de exemplo.

Se você quiser alterar o tema do bootstrap, coloque o arquivo bootstrap.css para o tema que você deseja na pasta
wwwroot/css e altere as referências no cshtml para apontá-lo. Altere os links para todos os ambientes:

<environment names="Development">
<link rel="stylesheet" href="~/css/bootstrap.css" />

<environment names="Staging,Production">
<link rel="stylesheet" href="~/css/bootstrap.min.css" />

Se você deseja criar seu próprio painel, você pode iniciar do exemplo livre disponível aqui:
http://getbootstrap.com/examples/dashboard/ .

Componentes
Além desses elementos já discutidos, bootstrap inclui suporte para uma variedade de componentes internos de
interface do usuário.
Glyphicons
Bootstrap inclui conjuntos de ícones de Glyphicons (http://glyphicons.com), com mais de 200 ícones disponíveis
gratuitamente para uso dentro de seu aplicativo da web habilitado para o bootstrap. Aqui está apenas uma
pequena amostra: Aqui está a apenas uma pequena amostra:
grupos de entrada
Grupos de entrada permitem agrupamento de texto adicional ou botões com um elemento de entrada,
fornecendo ao usuário uma experiência mais intuitiva com:

Trilha
Navegação estrutural é um componente de interface do usuário comum usado para mostrar ao usuário seu
histórico recente ou a profundidade da hierarquia de navegação do site. Adicioná-los facilmente aplicando a classe
"trilha" a qualquer <ol> elemento da lista. Inclui suporte interno para paginação usando a classe "paginação" em
um <ul> elemento dentro de um <nav> . Adicionar apresentações de slides inseridas responsivos e vídeo usando
<iframe> , <embed> , <video> , ou <object> elementos, que Bootstrap será estilo automaticamente. Especifique
uma taxa de proporção específica usando classes específicas, como "Inserir-responsivo-16by9".

Suporte a JavaScript
Biblioteca de JavaScript do Bootstrap inclui suporte a API para os componentes incluídos, permitindo que você
controle de seu comportamento programaticamente dentro de seu aplicativo. Além disso, bootstrap.js inclui uma
dúzia jQuery personalizado plug-ins, fornecer recursos adicionais como transições, caixas de diálogo modais
rolagem detecção (Atualizando estilos com base no qual o usuário tem rolado no documento), comportamento de
recolher, carrosséis e fixar menus para a janela para que eles não aparecem na tela. Não há espaço suficiente para
abranger todos os complementos JavaScript incorporados Bootstrap – para saber mais, visite
http://getbootstrap.com/javascript/ .

Resumo
Bootstrap fornece uma estrutura de web que pode ser usada para formatar e uma ampla variedade de sites e
aplicativos de estilo de forma rápida e produtiva. Seu tipografia básica e estilos fornecem uma agradável
aparência que podem ser manipulada facilmente por meio do suporte de tema personalizado, que pode ser criado
manualmente ou adquirido comercialmente. Ele dá suporte a um host de componentes da web que seria tiver
solicitado caros controles de terceiros para realizar e dar suporte a padrões da web modernos e abertos no
passado.
Less, Sass e fonte Awesome no ASP.NET Core
10/04/2018 • 22 min to read • Edit Online

Por Steve Smith


Os usuários de aplicativos da web têm expectativas cada vez mais altas quando se trata de estilo e a experiência
geral. Aplicativos web modernos frequentemente aproveitam avançadas ferramentas e estruturas para definir e
gerenciar sua aparência de uma maneira consistente. Estruturas como inicialização pode ir um longo caminho para
definir um conjunto comum de opções de layout para sites da web e estilos. No entanto, a maioria dos sites não
trivial também se beneficiar de ser capaz de definir e manter estilos e arquivos de folhas de estilo em cascata com
eficiência, bem como acesso fácil a imagem não ícones que ajudam a tornar a interface do site mais intuitiva. É
onde linguagens e ferramentas que dão suporte a menos e Sass, e bibliotecas, como fonte Awesome, entrar.

Idiomas de pré-processador de CSS


Idiomas que são compilados em outros idiomas, para melhorar a experiência de trabalhar com o idioma base são
chamados de pré-processador. Há dois pré-processadores populares de CSS: menor e Sass. Esses pré-
processadores adicionar recursos a CSS, como suporte para variáveis e regras aninhadas que melhorar a
facilidade de manutenção de folhas de estilo grandes e complexas. CSS como uma linguagem é muito básica, sem
suporte a até mesmo algo simples, como variáveis e isso tende a fazer arquivos CSS repetitivas e inchada.
Adicionando recursos de idioma real de programação via pré-processadores pode ajudar a reduzir a duplicação e
fornecer melhor organização de regras de estilo. Visual Studio fornece suporte interno para ambos os menor e
Sass, bem como as extensões que podem melhorar ainda mais a experiência de desenvolvimento ao trabalhar com
esses idiomas.
Como um exemplo rápido de como pré-processadores podem melhorar a leitura e a manutenção de informações
de estilo, considere este CSS:

.header {
color: black;
font-weight: bold;
font-size: 18px;
font-family: Helvetica, Arial, sans-serif;
}

.small-header {
color: black;
font-weight: bold;
font-size: 14px;
font-family: Helvetica, Arial, sans-serif;
}

Usando Less, isso pode ser reescrito para eliminar a duplicação, usando um mixin (chamada assim porque ele
permite que você "misture" propriedades de uma classe ou conjunto de regras em outra):
.header {
color: black;
font-weight: bold;
font-size: 18px;
font-family: Helvetica, Arial, sans-serif;
}

.small-header {
.header;
font-size: 14px;
}

Less
O pré-processador CSS less é executado usando o Node. js. Para instalar less, use o Gerenciador de pacotes de nó
(npm) em um prompt de comando (-g significa "global"):

npm install -g less

Se você estiver usando o Visual Studio, você pode começar com less adicionando um ou mais arquivos less ao seu
projeto e, em seguida, configurando Gulp (ou Grunt) para processá-los em tempo de compilação. Adicione uma
pasta estilos ao seu projeto e, em seguida, adicione um novo arquivo less chamado main.less nesta pasta.

Depois de adicionado, a estrutura de pastas deve ser algo assim:


Agora você pode adicionar alguns estilos básicos para o arquivo, que será compilado em CSS e implantado na
pasta wwwroot por vez.
Modificar main.less para incluir o conteúdo a seguir, que cria uma paleta de cores simples de uma única cor base.

@base: #663333;
@background: spin(@base, 180);
@lighter: lighten(spin(@base, 5), 10%);
@lighter2: lighten(spin(@base, 10), 20%);
@darker: darken(spin(@base, -5), 10%);
@darker2: darken(spin(@base, -10), 20%);

body {
background-color:@background;
}
.baseColor {color:@base}
.bgLight {color:@lighter}
.bgLight2 {color:@lighter2}
.bgDark {color:@darker}
.bgDark2 {color:@darker2}

@base e o outro @-prefixed itens são variáveis. Cada um deles representa uma cor. Exceto para @base , eles são
definidos usando funções de cor: mais claro, mais escuro e rotação. Mais claro e escureça fazer muito bem o que
você esperaria; rotação ajusta o matiz da cor por um número de graus (ao redor do círculo de cores). O
processador de menos é inteligente ignore variáveis que não são usados, por isso, para demonstrar como
funcionam essas variáveis, precisamos usá-los em algum lugar. As classes .baseColor , etc. demonstrará os valores
calculados de cada uma das variáveis no arquivo CSS que é produzida.
Introdução
Criar um arquivo de configuração npm (Package. JSON ) na pasta do projeto e editá-lo para fazer referência a
gulp e gulp-less :

{
"version": "1.0.0",
"name": "asp.net",
"private": true,
"devDependencies": {
"gulp": "3.9.1",
"gulp-less": "3.3.0"
}
}

Instalar as dependências em um prompt de comando na pasta do projeto ou no Visual Studio Solution Explorer
(dependências > npm > restaurar os pacotes).
npm install

Na pasta do projeto, criar um Gulp arquivo de configuração (gulpfile.js) para definir o processo automatizado.
Adicione uma variável na parte superior do arquivo para representar less e uma tarefa para execução less:

var gulp = require("gulp"),


fs = require("fs"),
less = require("gulp-less");

gulp.task("less", function () {
return gulp.src('Styles/main.less')
.pipe(less())
.pipe(gulp.dest('wwwroot/css'));
});

Abra o Explorador do Executador de tarefas (exibição > outras janelas > Explorador do Executador de
tarefas). Entre as tarefas, você verá uma nova tarefa denominada less . Você talvez precise atualizar a janela.
Execute o less tarefa e você verá uma saída semelhante ao que é mostrado aqui:

O wwwroot/css pasta agora contém um novo arquivo, Main:


Abra Main e você verá algo parecido com o seguinte:

body {
background-color: #336666;
}
.baseColor {
color: #663333;
}
.bgLight {
color: #884a44;
}
.bgLight2 {
color: #aa6355;
}
.bgDark {
color: #442225;
}
.bgDark2 {
color: #221114;
}

Adicionar uma página HTML simples para o wwwroot pasta e referência Main para ver a paleta de cores em ação.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link href="css/main.css" rel="stylesheet" />
<title></title>
</head>
<body>
<div>
<div class="baseColor">BaseColor</div>
<div class="bgLight">Light</div>
<div class="bgLight2">Light2</div>
<div class="bgDark">Dark</div>
<div class="bgDark2">Dark2</div>
</div>
</body>
</html>

Você pode ver que o grau de 180 girar na @base usada para produzir @background resultou no disco de cores
opostas cor de @base :
Less também oferece suporte para regras aninhadas, bem como as consultas de mídia aninhada. Por exemplo,
definindo hierarquias aninhadas como menus podem resultar em regras de CSS detalhadas como estes:

nav {
height: 40px;
width: 100%;
}
nav li {
height: 38px;
width: 100px;
}
nav li a:link {
color: #000;
text-decoration: none;
}
nav li a:visited {
text-decoration: none;
color: #CC3333;
}
nav li a:hover {
text-decoration: underline;
font-weight: bold;
}
nav li a:active {
text-decoration: underline;
}

O ideal é todas as regras de estilo relacionados serão colocadas juntos dentro do arquivo CSS, mas na prática, não
há nada impor essa regra exceto convenção e talvez os comentários do bloco.
Definir essas mesmas regras usando less tem esta aparência:

nav {
height: 40px;
width: 100%;
li {
height: 38px;
width: 100px;
a {
color: #000;
&:link { text-decoration:none}
&:visited { color: #CC3333; text-decoration:none}
&:hover { text-decoration:underline; font-weight:bold}
&:active {text-decoration:underline}
}
}
}

Observe que, nesse caso, todos os elementos subordinados do nav estão contidos dentro de seu escopo. Não há
nenhuma repetição dos elementos pai ( nav , li , a ), e a contagem de linha de total caiu também (embora
algumas das é um resultado de colocar valores nas linhas da mesmas no segundo exemplo). Ele pode ser muito
útil, organizacional, para ver todas as regras para um determinado elemento de interface do usuário em um
escopo explicitamente associado, nesse caso definido do restante do arquivo de chaves.
O & sintaxe é um recurso seletor less, com & que representa o pai de seletor atual. Portanto, dentro do {...} bloco,
& representa um a marca e, portanto, &:link é equivalente a a:link .
Consultas de mídia, extremamente úteis na criação de designs de resposta também podem contribuir muito para
repetição e a complexidade em CSS. Less permite que as consultas de mídia a ser aninhada dentro de classes, para
que a definição de classe inteira não precisa ser repetido em diferentes nível superior @media elementos. Por
exemplo, aqui está o CSS para um menu de resposta:

.navigation {
margin-top: 30%;
width: 100%;
}
@media screen and (min-width: 40em) {
.navigation {
margin: 0;
}
}
@media screen and (min-width: 62em) {
.navigation {
width: 960px;
margin: 0;
}
}

Isso pode ser melhor definido em less como:

.navigation {
margin-top: 30%;
width: 100%;
@media screen and (min-width: 40em) {
margin: 0;
}
@media screen and (min-width: 62em) {
width: 960px;
margin: 0;
}
}

Outro recurso de less que já vimos é seu suporte para operações matemáticas, permitindo que os atributos de
estilo a ser construído de variáveis predefinidas. Isso facilita a atualização estilos relacionados muito mais fácil, já
que a variável de base pode ser modificada e todos os valores dependentes alterar automaticamente.
Arquivos CSS, especialmente para grandes sites (e especialmente se as consultas de mídia estão sendo usadas),
tendem a ter muito grandes ao longo do tempo, tornando a trabalhar com elas complicada. Arquivos less podem
ser definidos separadamente, obtidas usando @import diretivas. Less também pode ser usado para importar
arquivos CSS individuais, se desejado.
Mixins pode aceitar parâmetros e less oferece suporte à lógica condicional na forma de protege mesclado, que
fornecem uma maneira declarativa para definir quando determinados mixins entra em vigor. Um uso comum para
protege mesclado ajustar as cores com base em como a luz ou escuro a cor da fonte. Dado um mesclado que
aceita um parâmetro para a cor, um protetor mesclado pode ser usado para modificar o mesclado com base na cor:
.box (@color) when (lightness(@color) >= 50%) {
background-color: #000;
}
.box (@color) when (lightness(@color) < 50%) {
background-color: #FFF;
}
.box (@color) {
color: @color;
}

.feature {
.box (@base);
}

Considerando nossa atual @base valor #663333 , esse script less produzirá o seguinte CSS:

.feature {
background-color: #FFF;
color: #663333;
}

Less fornece uma série de recursos adicionais, mas isso deve dar uma ideia da energia desse idioma de pré-
processamento.

Sass
Sass é semelhante ao menos, fornecendo suporte para muitos dos mesmos recursos, mas com sintaxe
ligeiramente diferente. Ele é criado usando o Ruby, em vez de JavaScript, e portanto tem requisitos de instalação
diferentes. O idioma Sass original não usa chaves ou ponto e vírgula, mas em vez disso, definida escopo usando o
recuo e espaços em branco. Na versão 3 dos Sass, uma nova sintaxe foi introduzida, SCSS ("Sassy CSS"). SCSS é
semelhante ao CSS que ignora os níveis de recuo e espaços em branco e, em vez disso, usa o ponto e vírgula e
chaves.
Para instalar Sass, normalmente você deve primeiro instalar Ruby (pré-instalado macOS ) e, em seguida, execute:

gem install sass

No entanto, se você estiver executando o Visual Studio, você pode começar com Sass em grande parte da mesma
maneira como você faria com less. Abra Package. JSON e adicionar o pacote "gulp sass" devDependencies :

"devDependencies": {
"gulp": "3.9.1",
"gulp-less": "3.3.0",
"gulp-sass": "3.1.0"
}

Em seguida, modifique gulpfile.js para adicionar uma variável de sass e uma tarefa para compilar os arquivos Sass
e colocar os resultados na pasta wwwroot:
var gulp = require("gulp"),
fs = require("fs"),
less = require("gulp-less"),
sass = require("gulp-sass");

// other content removed

gulp.task("sass", function () {
return gulp.src('Styles/main2.scss')
.pipe(sass())
.pipe(gulp.dest('wwwroot/css'));
});

Agora você pode adicionar o arquivo Sass main2.scss para o estilos pasta na raiz do projeto:

Abra main2.scss e adicione o seguinte:

$base: #CC0000;
body {
background-color: $base;
}

Salve todos os arquivos. Agora quando você atualiza Explorador do Executador de tarefas, você verá um sass
tarefa. Executá-lo e examinar o /wwwroot/css pasta. Agora há uma main2.css arquivo com esse conteúdo:

body {
background-color: #CC0000;
}

Sass oferece suporte a aninhamento praticamente o mesmo foi que less não, fornecer benefícios semelhantes. Os
arquivos podem ser divididos por função e incluídos usando a @import diretiva:

@import 'anotherfile';

Sass oferece suporte a mixins, usando o @mixin palavra-chave para defini-los e @include para incluí-los, como
neste exemplo de sass lang.com:

@mixin border-radius($radius) {
-webkit-border-radius: $radius;
-moz-border-radius: $radius;
-ms-border-radius: $radius;
border-radius: $radius;
}

.box { @include border-radius(10px); }

Além mixins, Sass também oferece suporte ao conceito de herança, permitindo que uma classe estender o outro.
Ele é conceitualmente semelhante a um mesclado, mas resulta em menos código CSS. Ela é realizada usando o
@extend palavra-chave. Para testar mixins, adicione o seguinte ao seu main2.scss arquivo:

@mixin alert {
border: 1px solid black;
padding: 5px;
color: #333333;
}

.success {
@include alert;
border-color: green;
}

.error {
@include alert;
color: red;
border-color: red;
font-weight:bold;
}

Examine a saída em main2.css depois de executar o sass tarefa no Explorador do Executador de tarefas:

.success {
border: 1px solid black;
padding: 5px;
color: #333333;
border-color: green;
}

.error {
border: 1px solid black;
padding: 5px;
color: #333333;
color: red;
border-color: red;
font-weight: bold;
}

Observe que todas as propriedades comuns do alerta mesclado são repetidas em cada classe. O mesclado foi um
bom trabalho de ajudar a eliminar a duplicação no tempo de desenvolvimento, mas ela ainda está criando um CSS
com muita duplicação, resultando em maior do que arquivos CSS necessários - um possível problema de
desempenho.
Agora, substitua o alerta mesclado com um .alert classe e altere @include para @extend (Lembre-se estender
.alert , não alert ):
.alert {
border: 1px solid black;
padding: 5px;
color: #333333;
}

.success {
@extend .alert;
border-color: green;
}

.error {
@extend .alert;
color: red;
border-color: red;
font-weight:bold;
}

Execute Sass mais uma vez e examine o CSS resultante:

.alert, .success, .error {


border: 1px solid black;
padding: 5px;
color: #333333;
}

.success {
border-color: green;
}

.error {
color: red;
border-color: red;
font-weight: bold;
}

Agora as propriedades são definidas apenas como quantas vezes forem necessárias, e melhor CSS é gerado.
Sass também inclui funções e operações de lógica condicional, semelhantes ao less. Na verdade, os recursos de
dois idiomas são muito semelhantes.

Less ou Sass?
Ainda não há consenso se geralmente é melhor usar less ou Sass (ou até mesmo se preferir o Sass original ou a
sintaxe SCSS mais recente em Sass). Provavelmente, a decisão mais importante é usar uma dessas ferramentas,
em vez de apenas codificação manual em seus arquivos CSS. Depois que você fez essa decisão, ambos Less e Sass
são boas opções.

Font Awesome
Além de pré-processadores CSS, outro excelente recurso para aplicativos web modernos de estilo é incrível de
fonte. Lista de fonte é um kit de ferramentas fornece mais de 500 ícones de SVG que podem ser usados
livremente em seus aplicativos web. Ela foi originalmente projetada para trabalhar com inicialização, mas ele não
tem nenhuma dependência no framework ou em qualquer biblioteca de JavaScript.
A maneira mais fácil começar com o incríveis fonte é adicionar uma referência a ele, usando seu local de rede
(CDN ) do fornecimento de conteúdo público:
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">

Você também pode adicioná-lo ao seu projeto do Visual Studio adicionando-as "dependências" em bower. JSON:

{
"name": "ASP.NET",
"private": true,
"dependencies": {
"bootstrap": "3.0.0",
"jquery": "1.10.2",
"jquery-validation": "1.11.1",
"jquery-validation-unobtrusive": "3.2.2",
"hammer.js": "2.0.4",
"bootstrap-touch-carousel": "0.8.0",
"Font-Awesome": "4.3.0"
}
}

Depois que você tem uma referência para o incríveis fonte em uma página, você pode adicionar ícones para o seu
aplicativo aplicando fonte Awesome classes, normalmente é prefixados com "fa-", para os elementos embutidos
HTML (como <span> ou <i> ). Por exemplo, você pode adicionar ícones de listas simples e menus usando código
como este:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<link href="lib/font-awesome/css/font-awesome.css" rel="stylesheet" />
</head>
<body>
<ul class="fa-ul">
<li><i class="fa fa-li fa-home"></i> Home</li>
<li><i class="fa fa-li fa-cog"></i> Settings</li>
</ul>
</body>
</html>

Isso produz o seguinte no navegador - Observe o ícone ao lado de cada item:

Você pode exibir uma lista completa de ícones disponíveis:


http://fontawesome.io/icons/

Resumo
Aplicativos web modernos exigem designs cada vez mais responsivos e fluidos que são normais, intuitivos e fáceis
de usar em uma variedade de dispositivos. Gerenciar a complexidade das folhas de estilo CSS necessárias para
atingir essas metas é melhor usando um tipo de pré-processador less ou Sass. Além disso, kits de ferramentas
como a Awesome fonte rapidamente fornecem ícones conhecidos a menus de navegação textual e experiência de
botões, melhorando o usuário do seu aplicativo.
Pacote e minifiy ativos estáticos no núcleo do
ASP.NET
10/04/2018 • 18 min to read • Edit Online

Por Scott Addie


Este artigo explica os benefícios da aplicação de empacotamento e minimização, incluindo como esses recursos
podem ser usados com aplicativos web do ASP.NET Core.

O que é o empacotamento e minimização?


Empacotamento e minimização são duas otimizações de desempenho distintos que você pode aplicar em um
aplicativo web. Usados juntos, empacotamento e minimização melhoram o desempenho reduzindo o número de
solicitações do servidor e reduzindo o tamanho dos ativos estáticos solicitados.
Empacotamento e minimização principalmente melhoram o tempo de carregamento de solicitação de página
primeiro. Depois que uma página da web foi solicitada, o navegador armazena em cache os ativos estáticos
(JavaScript, CSS e imagens). Consequentemente, empacotamento e minimização não melhoram o desempenho
ao solicitar a mesma página ou páginas, no mesmo site que está solicitando os mesmos ativos. Se o vencimento
cabeçalho não está definido corretamente nos ativos e se não for usado o empacotamento e minimização,
heurística de atualização do navegador marca os ativos obsoletos depois de alguns dias. Além disso, o navegador
requer uma solicitação de validação para cada ativo. Nesse caso, empacotamento e minimização fornecem uma
melhoria de desempenho mesmo após a primeira solicitação de página.
Agrupamento
Agrupando combina vários arquivos em um único arquivo. Agrupando reduz o número de solicitações de
servidor que são necessários para renderizar um ativo de web, como uma página da web. Você pode criar
qualquer número de pacotes individuais especificamente para CSS, JavaScript, etc. Menos arquivos significa
menos solicitações HTTP do navegador para o servidor ou do serviço fornecendo o seu aplicativo. Isso resulta em
melhor desempenho de carregamento de página primeiro.
Minimização
Minimização remove caracteres desnecessárias de código sem alterar a funcionalidade. O resultado é uma
redução de tamanho significativo nos ativos solicitados (como CSS, imagens e arquivos JavaScript). Os efeitos
colaterais de minimização incluem encurtar nomes de variável para um caractere e remover comentários e
espaços em branco desnecessários.
Considere a função JavaScript a seguir:

AddAltToImg = function (imageTagAndImageID, imageContext) {


///<signature>
///<summary> Adds an alt tab to the image
// </summary>
//<param name="imgElement" type="String">The image selector.</param>
//<param name="ContextForImage" type="String">The image context.</param>
///</signature>
var imageElement = $(imageTagAndImageID, imageContext);
imageElement.attr('alt', imageElement.attr('id').replace(/ID/, ''));
}

Minimização reduz a função para o seguinte:


AddAltToImg=function(n,t){var i=$(n,t);i.attr("alt",i.attr("id").replace(/ID/,""))};

Além de remover os comentários e espaços em branco desnecessários, os seguintes nomes de parâmetro e


variável foram renomeados da seguinte maneira:

ORIGINAL RENOMEADO

imageTagAndImageID t

imageContext a

imageElement r

Impacto de empacotamento e minimização


A tabela a seguir descreve as diferenças entre carregamento ativos individualmente e usar o empacotamento e
minimização:

AÇÃO COM B/M SEM B/M ALTERAÇÃO

Solicitações de arquivo 7 18 157%

KB transferido 156 264.68 70%

Tempo de carregamento 885 2360 167%


(ms)

Navegadores são bastante detalhados em relação a cabeçalhos de solicitação HTTP. O total de bytes enviados
métrica viu uma redução significativa ao agrupamento. O tempo de carregamento mostra uma melhoria
significativa, no entanto, esse exemplo foi executado localmente. Maiores ganhos de desempenho são obtidos
quando usar o empacotamento e minimização com ativos transferido em uma rede.

Escolha uma estratégia de empacotamento e minimização


Os modelos de projeto MVC e páginas Razor fornecem uma solução para empacotamento e minimização
consiste em um arquivo de configuração JSON. Ferram de terceiros, como o Gulp e Grunt executores de tarefas,
realizar as mesmas tarefas com um pouco mais complexidade. Uma ferramenta de terceiros é uma excelente
opção quando o fluxo de trabalho de desenvolvimento requer processamento além de empacotamento e
minimização—como otimização linting e imagem. Usando o empacotamento e minimização tempo de design, os
arquivos minimizados são criados antes da implantação do aplicativo. Empacotando e minimizando antes da
implantação tem a vantagem de carga do servidor reduzido. No entanto, é importante reconhecer que o
agrupamento de tempo de design e minimização aumenta a complexidade de compilação e só funciona com
arquivos estáticos.

Configurar o empacotamento e minimização


Os modelos de projeto MVC e páginas Razor fornecem uma bundleconfig.json arquivo de configuração que
define as opções para cada pacote. Por padrão, uma configuração de pacote único é definida para o JavaScript
personalizado (wwwroot/js/site.js) e a folha de estilos (wwwroot/css/site.css) arquivos:
[
{
"outputFileName": "wwwroot/css/site.min.css",
"inputFiles": [
"wwwroot/css/site.css"
]
},
{
"outputFileName": "wwwroot/js/site.min.js",
"inputFiles": [
"wwwroot/js/site.js"
],
"minify": {
"enabled": true,
"renameLocals": true
},
"sourceMap": false
}
]

Opções de configuração incluem:


outputFileName : O nome do arquivo de pacote de saída. Pode conter um caminho relativo do
bundleconfig.json arquivo. required
inputFiles : Uma matriz de arquivos para agrupar em conjunto. Esses são os caminhos relativos ao arquivo
de configuração. opcional, * um valor vazio resulta em um arquivo de saída vazia. Globalização padrões são
suportados.
minify : As opções de minimização para o tipo de saída. optional, default - minify: { enabled: true }
Opções de configuração estão disponíveis por tipo de arquivo de saída.
CSS Minifier
Minificador de JavaScript
Minificador de HTML
includeInProject : O sinalizador que indica se é para adicionar arquivos gerados ao arquivo de projeto.
optional, default - false
sourceMap : O sinalizador que indica se deve gerar um mapa de origem para o arquivo de pacote. optional,
default - false
sourceMapRootPath : O caminho raiz para armazenar o arquivo de mapa de código-fonte gerado.

Execução de tempo de compilação de empacotamento e minimização


O BuildBundlerMinifier pacote NuGet permite que a execução de empacotamento e minimização no momento da
compilação. O pacote injeta destinos do MSBuild quais executar compilação e tempo limpo. O bundleconfig.json
arquivo é analisado pelo processo de compilação para produzir os arquivos de saída com base na configuração de
definidos.

OBSERVAÇÃO
BuildBundlerMinifier pertence a um projeto voltado para a comunidade no GitHub para o qual a Microsoft fornece sem
suporte. Problemas que devem ser arquivados aqui.

Visual Studio
CLI do .NET Core
Adicionar o BuildBundlerMinifier pacote ao seu projeto.
Compile o projeto. A seguir é exibido na janela de saída:

1>------ Build started: Project: BuildBundlerMinifierApp, Configuration: Debug Any CPU ------
1>
1>Bundler: Begin processing bundleconfig.json
1> Minified wwwroot/css/site.min.css
1> Minified wwwroot/js/site.min.js
1>Bundler: Done processing bundleconfig.json
1>BuildBundlerMinifierApp -> C:\BuildBundlerMinifierApp\bin\Debug\netcoreapp2.0\BuildBundlerMinifierApp.dll
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

Limpe o projeto. A seguir é exibido na janela de saída:

1>------ Clean started: Project: BuildBundlerMinifierApp, Configuration: Debug Any CPU ------
1>
1>Bundler: Cleaning output from bundleconfig.json
1>Bundler: Done cleaning output file from bundleconfig.json
========== Clean: 1 succeeded, 0 failed, 0 skipped ==========

Execução ad hoc de empacotamento e minimização


É possível executar as tarefas de empacotamento e minimização em uma base ad hoc, sem criar o projeto.
Adicionar o BundlerMinifier.Core pacote NuGet ao seu projeto:

<DotNetCliToolReference Include="BundlerMinifier.Core" Version="2.6.362" />

OBSERVAÇÃO
BundlerMinifier.Core pertence a um projeto voltado para a comunidade no GitHub para o qual a Microsoft fornece sem
suporte. Problemas que devem ser arquivados aqui.

Este pacote estende a CLI do núcleo do .NET para incluir o dotnet pacote ferramenta. O comando a seguir pode
ser executado na janela do Console de Gerenciador de pacote (PMC ) ou em um shell de comando:

dotnet bundle

IMPORTANTE
O NuGet Package Manager adiciona as dependências para o arquivo *. csproj como <PackageReference /> nós. O
dotnet bundle comando está registrado com o .NET Core CLI somente quando um <DotNetCliToolReference /> nó é
usado. Modifique o arquivo *. csproj adequadamente.

Adicionar arquivos ao fluxo de trabalho


Considere um exemplo no qual adicional custom.css arquivo é adicionado a seguir:
.about, [role=main], [role=complementary] {
margin-top: 60px;
}

footer {
margin-top: 10px;
}

Ser minificada custom.css e agrupar com site.css em uma site.min.css de arquivo, adicione o caminho relativo para
bundleconfig.json:

[
{
"outputFileName": "wwwroot/css/site.min.css",
"inputFiles": [
"wwwroot/css/site.css",
"wwwroot/css/custom.css"
]
},
{
"outputFileName": "wwwroot/js/site.min.js",
"inputFiles": [
"wwwroot/js/site.js"
],
"minify": {
"enabled": true,
"renameLocals": true
},
"sourceMap": false
}
]

OBSERVAÇÃO
Como alternativa, é possível usar o seguinte padrão de globalização:

"inputFiles": ["wwwroot/**/*(*.css|!(*.min.css)"]

Esse padrão de globalização corresponde a todos os arquivos CSS e exclui o padrão de arquivo minimizada.

Compile o aplicativo. Abra site.min.css e observe o conteúdo de custom.css é acrescentado ao final do arquivo.

Empacotamento e minimização baseado no ambiente


Como prática recomendada, os arquivos de pacotes e minimizados do seu aplicativo devem ser usados em um
ambiente de produção. Durante o desenvolvimento, os arquivos originais fazer para facilitar a depuração do
aplicativo.
Especificar quais arquivos a serem incluídos nas suas páginas usando o auxiliar de marca de ambiente em
exibições. O auxiliar de marca de ambiente processa apenas seu conteúdo durante a execução no específico
ambientes.
O seguinte environment marca processa os arquivos CSS não processados durante a execução no Development
ambiente:
ASP.NET Core 2.x
ASP.NET Core 1.x
<environment include="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>

O seguinte environment marca processa os arquivos CSS agrupados e minimizados quando executado em um
ambiente diferente de Development . Por exemplo, em execução em Production ou Staging dispara o
processamento dessas folhas de estilo:
ASP.NET Core 2.x
ASP.NET Core 1.x

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

Consumir bundleconfig.json de Gulp


Há casos em que o fluxo de trabalho empacotamento e minimização do aplicativo requer processamento
adicional. Exemplos incluem a otimização da imagem, a eliminação de cache e processamento de ativos CDN.
Para atender a esses requisitos, que você pode converter o fluxo de trabalho de empacotamento e minimização
para usar o Gulp.
Usar a extensão de empacotador & Minificador
O Visual Studio empacotador & Minificador extensão processa a conversão para Gulp.

OBSERVAÇÃO
A extensão de empacotador & Minificador pertence a um projeto voltado para a comunidade no GitHub para o qual a
Microsoft fornece sem suporte. Problemas que devem ser arquivados aqui.

Clique com botão direito do bundleconfig.json no Gerenciador de soluções e selecione empacotador &
Minificador > converter Gulp... :

O gulpfile.js e Package. JSON arquivos são adicionados ao projeto. O suporte npm os pacotes listados no
Package. JSON do arquivo devDependencies seção estão instalados.
Execute o seguinte comando na janela de PMC para instalar a CLI Gulp como uma dependência global:
npm i -g gulp-cli

O gulpfile.js leituras de arquivo do bundleconfig.json arquivo para as entradas, saídas e as configurações.

"use strict";

var gulp = require("gulp"),


concat = require("gulp-concat"),
cssmin = require("gulp-cssmin"),
htmlmin = require("gulp-htmlmin"),
uglify = require("gulp-uglify"),
merge = require("merge-stream"),
del = require("del"),
bundleconfig = require("./bundleconfig.json");

// Code omitted for brevity

Converter manualmente
Se o Visual Studio e/ou a extensão de empacotador & Minificador não estiverem disponível, converta
manualmente.
Adicionar um Package. JSON arquivo, com as seguintes devDependencies , para a raiz do projeto:

"devDependencies": {
"del": "^3.0.0",
"gulp": "^3.9.1",
"gulp-concat": "^2.6.1",
"gulp-cssmin": "^0.2.0",
"gulp-htmlmin": "^3.0.0",
"gulp-uglify": "^3.0.0",
"merge-stream": "^1.0.1"
}

Instalar as dependências, executando o seguinte comando no mesmo nível como Package. JSON:

npm i

Instale a CLI Gulp como uma dependência global:

npm i -g gulp-cli

Copie o gulpfile.js arquivo abaixo da raiz do projeto:

"use strict";

var gulp = require("gulp"),


concat = require("gulp-concat"),
cssmin = require("gulp-cssmin"),
htmlmin = require("gulp-htmlmin"),
uglify = require("gulp-uglify"),
merge = require("merge-stream"),
del = require("del"),
bundleconfig = require("./bundleconfig.json");

var regex = {
css: /\.css$/,
html: /\.(html|htm)$/,
js: /\.js$/
js: /\.js$/
};

gulp.task("min", ["min:js", "min:css", "min:html"]);

gulp.task("min:js", function () {
var tasks = getBundles(regex.js).map(function (bundle) {
return gulp.src(bundle.inputFiles, { base: "." })
.pipe(concat(bundle.outputFileName))
.pipe(uglify())
.pipe(gulp.dest("."));
});
return merge(tasks);
});

gulp.task("min:css", function () {
var tasks = getBundles(regex.css).map(function (bundle) {
return gulp.src(bundle.inputFiles, { base: "." })
.pipe(concat(bundle.outputFileName))
.pipe(cssmin())
.pipe(gulp.dest("."));
});
return merge(tasks);
});

gulp.task("min:html", function () {
var tasks = getBundles(regex.html).map(function (bundle) {
return gulp.src(bundle.inputFiles, { base: "." })
.pipe(concat(bundle.outputFileName))
.pipe(htmlmin({ collapseWhitespace: true, minifyCSS: true, minifyJS: true }))
.pipe(gulp.dest("."));
});
return merge(tasks);
});

gulp.task("clean", function () {
var files = bundleconfig.map(function (bundle) {
return bundle.outputFileName;
});

return del(files);
});

gulp.task("watch", function () {
getBundles(regex.js).forEach(function (bundle) {
gulp.watch(bundle.inputFiles, ["min:js"]);
});

getBundles(regex.css).forEach(function (bundle) {
gulp.watch(bundle.inputFiles, ["min:css"]);
});

getBundles(regex.html).forEach(function (bundle) {
gulp.watch(bundle.inputFiles, ["min:html"]);
});
});

function getBundles(regexPattern) {
return bundleconfig.filter(function (bundle) {
return regexPattern.test(bundle.outputFileName);
});
}

Executar tarefas de Gulp


Para disparar a tarefa de minimização Gulp antes que o projeto é compilado no Visual Studio, adicione o seguinte
destino do MSBuild para o arquivo *. csproj:
<Target Name="MyPreCompileTarget" BeforeTargets="Build">
<Exec Command="gulp min" />
</Target>

Neste exemplo, todas as tarefas definidas dentro de MyPreCompileTarget destino executar antes de predefinida
Build destino. Saída semelhante à seguinte é exibida na janela de saída do Visual Studio:

1>------ Build started: Project: BuildBundlerMinifierApp, Configuration: Debug Any CPU ------
1>BuildBundlerMinifierApp -> C:\BuildBundlerMinifierApp\bin\Debug\netcoreapp2.0\BuildBundlerMinifierApp.dll
1>[14:17:49] Using gulpfile C:\BuildBundlerMinifierApp\gulpfile.js
1>[14:17:49] Starting 'min:js'...
1>[14:17:49] Starting 'min:css'...
1>[14:17:49] Starting 'min:html'...
1>[14:17:49] Finished 'min:js' after 83 ms
1>[14:17:49] Finished 'min:css' after 88 ms
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

Como alternativa, o Explorador do Executador de tarefas do Visual Studio pode ser usado para associar o Gulp
tarefas a eventos específicos do Visual Studio. Consulte executando tarefas padrão para obter instruções sobre
como fazer isso.

Recursos adicionais
Usar o Gulp
Usar o Grunt
Trabalhar com vários ambientes
Auxiliares de marcação
Link do navegador no ASP.NET Core
10/04/2018 • 7 min to read • Edit Online

Por Nicolò Carandini, Mike Wasson, e Tom Dykstra


Link do navegador é um recurso no Visual Studio que cria um canal de comunicação entre o ambiente de
desenvolvimento e um ou mais navegadores da web. Você pode usar o Link do navegador para atualizar seu
aplicativo da web em vários navegadores de uma vez, que é útil para testes entre navegadores.

Instalação de Link do navegador


ASP.NET Core 2.x
ASP.NET Core 1.x
O ASP.NET Core 2. x aplicativo Web, vazio, e API da Web modelo projetos usam o Microsoft.AspNetCore.All
pacote meta, que contém uma referência de pacote para browserlink. Portanto, usando o
Microsoft.AspNetCore.All pacote meta não requer nenhuma ação adicional para disponibilizar o Link do
navegador para uso.
Configuração
No Configure método o Startup.cs arquivo:

app.UseBrowserLink();

Geralmente o código está dentro de um if bloco que permite apenas o Link de navegador no ambiente de
desenvolvimento, como mostrado aqui:

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

Para obter mais informações, consulte trabalhar com vários ambientes.

Como usar o Link do navegador


Quando um projeto do ASP.NET Core está aberto, o Visual Studio mostra o controle de barra de ferramentas do
Link do navegador ao lado do controle de barra de ferramentas do Destino de Depuração:

O controle de barra de ferramentas Link do navegador, você pode:


Atualizar o aplicativo Web em vários navegadores de uma vez.
Abrir o Painel de link do navegador.
Habilitar ou desabilitar Link do navegador. Observação: O Link do navegador está desabilitado por padrão no
Visual Studio 2017 (15,3).
Habilitar ou desabilitar sincronização automática de CSS.

OBSERVAÇÃO
Alguns plug-ins do Visual Studio, mais notoriamente Web 2015 de pacote de extensão e Web 2017 de pacote de extensão,
oferecem funcionalidade estendida para o Link do navegador, mas alguns dos recursos adicionais não funcionam com o ASP.
Projetos de rede principal.

Atualizar o aplicativo web em vários navegadores de uma vez


Para escolher um único navegador Web para abrir ao iniciar o projeto, use o menu suspenso do controle de barra
de ferramentas do Destino de depuração:

Para abrir vários navegadores de uma vez, escolha procurar com... da mesma lista suspensa. Mantenha
pressionada a tecla CTRL para selecionar os navegadores que você deseja e, em seguida, clique em procurar:

Aqui está uma captura de tela mostrando o Visual Studio com o modo de exibição do índice aberto e dois
navegadores abertas:
Passe o mouse sobre o controle de barra de ferramentas de Link do navegador para ver os navegadores que estão
conectados ao projeto:

Alterar a exibição do índice, e todos os navegadores conectados são atualizados quando você clicar no botão de
atualização de Link do navegador:
Link do navegador também funciona com navegadores que você inicia a partir de fora do Visual Studio e navegue
até a URL do aplicativo.
O painel de Link do navegador
Abra o painel de Link do navegador de menu para gerenciar a conexão com o navegador abertas suspenso Link
do navegador:

Se nenhum navegador estiver conectado, você pode iniciar uma sessão de depuração não, selecionando Se
nenhum navegador estiver conectado, você poderá iniciar uma sessão de não depuração selecionando o link exibir
no navegador:
Caso contrário, os navegadores conectados são mostrados com o caminho para a página que está mostrando cada
navegador:

Se desejar, você pode clicar em um nome de navegador listados para atualizar o navegador único.
Habilitar ou desabilitar o Link do navegador
Quando você habilitar novamente o Link do navegador depois de desabilitá-lo, você deve atualizar os navegadores
para reconectar-se-los.
Habilitar ou desabilitar a sincronização automática de CSS
Quando a sincronização automática de CSS está habilitada, navegadores conectados são atualizadas
automaticamente quando você fizer qualquer alteração nos arquivos CSS.

Como funciona?
Link do navegador usa SignalR para criar um canal de comunicação entre o navegador e o Visual Studio. Quando
o Link do navegador é habilitado, o Visual Studio atua como um servidor de SignalR que vários clientes
(navegadores) podem se conectar ao. Link do navegador também registra um componente de middleware no
pipeline de solicitação do ASP.NET. Este componente injeta especial <script> referências em cada solicitação de
página do servidor. Você pode ver as referências de script selecionando Exibir código-fonte no navegador e rolar
até o final do <body> conteúdo de marca:

<!-- Visual Studio Browser Link -->


<script type="application/json" id="__browserLink_initializationData">
{"requestId":"a717d5a07c1741949a7cefd6fa2bad08","requestMappingFromServer":false}
</script>
<script type="text/javascript" src="http://localhost:54139/b6e36e429d034f578ebccd6a79bf19bf/browserLink"
async="async"></script>
<!-- End Browser Link -->
</body>

Os arquivos de origem não são modificados. O componente de middleware injeta as referências de script
dinamicamente.
Como o código do lado do navegador é todo JavaScript, ele funciona em todos os navegadores que oferece
suporte a SignalR sem a necessidade de um plug-in de navegador.
Use JavaScriptServices para criar aplicativos de única
página no núcleo do ASP.NET
27/04/2018 • 21 min to read • Edit Online

Por Scott Addie e Fiyaz Hasan


Um aplicativo de página única (SPA) é um tipo popular de aplicativo da web devido a sua experiência de usuário
avançada inerente. Integração de estruturas SPA ou bibliotecas de cliente, como Angular ou reagir, com as
estruturas do lado do servidor como o ASP.NET Core pode ser difícil. JavaScriptServices foi desenvolvido para
reduzir a fricção no processo de integração. Ele permite que uma operação contínua entre o cliente diferentes e
pilhas de tecnologia do servidor.
Exibir ou baixar código de exemplo (como baixar)

O que é JavaScriptServices?
JavaScriptServices é uma coleção de tecnologias de cliente para o ASP.NET Core. Sua meta é posicionar o
ASP.NET Core como plataforma de lado do servidor preferencial dos desenvolvedores para a criação de SPAs.
JavaScriptServices consiste em três pacotes do NuGet distintos:
Microsoft.AspNetCore.NodeServices (NodeServices)
Microsoft.AspNetCore.SpaServices (SpaServices)
Microsoft.AspNetCore.SpaTemplates (SpaTemplates)
Esses pacotes são úteis se você:
Executar o JavaScript no servidor
Use uma estrutura SPA ou biblioteca
Criar ativos do lado do cliente com Webpack
O foco deste artigo é colocado sobre como usar o pacote de SpaServices.

O que é SpaServices?
SpaServices foi criado para posicionar o ASP.NET Core como plataforma de lado do servidor preferencial dos
desenvolvedores para a criação de SPAs. SpaServices não é necessário para desenvolver SPAs com ASP.NET
Core, e ele não bloqueie você em uma estrutura de cliente específico.
SpaServices fornece a infraestrutura úteis, como:
Pré-processamento do lado do servidor
Middleware de desenvolvimento webpack
Substituição do módulo ativa
Roteamentos auxiliares
Coletivamente, esses componentes de infraestrutura melhoram o fluxo de trabalho de desenvolvimento e a
experiência de tempo de execução. Os componentes podem ser adotados individualmente.

Pré-requisitos para usar SpaServices


Para trabalhar com SpaServices, instale o seguinte:
Node. js (versão 6 ou posterior) com npm
Para verificar se esses componentes estão instalados e podem ser encontrados, execute o seguinte
na linha de comando:

node -v && npm -v

Observação: Se você estiver implantando em um site do Azure, você não precisa fazer nada aqui — Node. js está
instalado e disponível em ambientes de servidor.
.NET Core SDK 2.0 or later
Se você estiver no Windows usando o Visual Studio de 2017, o SDK está instalado, selecionando o
desenvolvimento de plataforma cruzada do .NET Core carga de trabalho.
Microsoft.AspNetCore.SpaServices pacote NuGet

Pré-processamento do lado do servidor


Um universal (também conhecido como isomórficos) é um aplicativo JavaScript capaz de executar tanto no
servidor e o cliente. Angular, reagir e outras estruturas populares fornecem uma plataforma universal para esse
estilo de desenvolvimento de aplicativo. A ideia é primeiro renderizar os componentes do framework no servidor
por meio do Node. js e, em seguida, delegado ainda mais a execução para o cliente.
ASP.NET Core auxiliares de marcação fornecida pelo SpaServices simplificar a implementação de pré-
processamento do lado do servidor ao chamar as funções JavaScript no servidor.
Pré -requisitos
Instale o seguinte:
pré-processamento ASPNET pacote npm:

npm i -S aspnet-prerendering

Configuração
Os auxiliares de marca são feitos detectáveis por meio de registro de namespace do projeto viewimports. cshtml
arquivo:

@using SpaServicesSampleApp
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
@addTagHelper "*, Microsoft.AspNetCore.SpaServices"

Esses auxiliares de marcação abstrair a complexidade de se comunicar diretamente com as APIs de baixo nível
utilizando uma sintaxe semelhante do HTML no modo de exibição Razor:

<app asp-prerender-module="ClientApp/dist/main-server">Loading...</app>

O asp-prerender-module auxiliar de marca


O asp-prerender-module auxiliar de marca, usados no exemplo de código anterior, executa ClientApp/dist/main-
server.js no servidor por meio do Node. js. Para clareza, principal server.js arquivo é um artefato da tarefa
transpilation TypeScript e JavaScript no Webpack do processo de compilação. Webpack define um alias de ponto
de entrada de main-server ; e, passagem do gráfico de dependência para este alias começa a
inicialização/ClientApp -server.ts arquivo:
entry: { 'main-server': './ClientApp/boot-server.ts' },

No exemplo a seguir Angular, o inicialização/ClientApp -server.ts arquivo utiliza o createServerRenderer função e


RenderResult tipo do aspnet-prerendering pacote npm para configurar o processamento de servidor por meio do
Node. js. A marcação HTML destinada a renderização do lado do servidor é passada para uma chamada de função
de resolução, que é encapsulada em um JavaScript fortemente tipada Promise objeto. O Promise significância do
objeto é que ele fornece assincronamente a marcação HTML para a página para inclusão no elemento de espaço
reservado do DOM.

import { createServerRenderer, RenderResult } from 'aspnet-prerendering';

export default createServerRenderer(params => {


const providers = [
{ provide: INITIAL_CONFIG, useValue: { document: '<app></app>', url: params.url } },
{ provide: 'ORIGIN_URL', useValue: params.origin }
];

return platformDynamicServer(providers).bootstrapModule(AppModule).then(moduleRef => {


const appRef = moduleRef.injector.get(ApplicationRef);
const state = moduleRef.injector.get(PlatformState);
const zone = moduleRef.injector.get(NgZone);

return new Promise<RenderResult>((resolve, reject) => {


zone.onError.subscribe(errorInfo => reject(errorInfo));
appRef.isStable.first(isStable => isStable).subscribe(() => {
// Because 'onStable' fires before 'onError', we have to delay slightly before
// completing the request in case there's an error to report
setImmediate(() => {
resolve({
html: state.renderToString()
});
moduleRef.destroy();
});
});
});
});
});

O asp-prerender-data auxiliar de marca


Quando combinado com o asp-prerender-module auxiliar de marca, o asp-prerender-data auxiliar de marca pode
ser usado para passar informações contextuais da exibição do Razor para o JavaScript do lado do servidor. Por
exemplo, a seguinte marcação transmite dados de usuário para o main-server módulo:

<app asp-prerender-module="ClientApp/dist/main-server"
asp-prerender-data='new {
UserName = "John Doe"
}'>Loading...</app>

Recebido UserName argumento é serializado usando o serializador JSON interno e é armazenado no params.data
objeto. No exemplo a seguir Angular, os dados são usados para construir uma saudação personalizada em um h1
elemento:
import { createServerRenderer, RenderResult } from 'aspnet-prerendering';

export default createServerRenderer(params => {


const providers = [
{ provide: INITIAL_CONFIG, useValue: { document: '<app></app>', url: params.url } },
{ provide: 'ORIGIN_URL', useValue: params.origin }
];

return platformDynamicServer(providers).bootstrapModule(AppModule).then(moduleRef => {


const appRef = moduleRef.injector.get(ApplicationRef);
const state = moduleRef.injector.get(PlatformState);
const zone = moduleRef.injector.get(NgZone);

return new Promise<RenderResult>((resolve, reject) => {


const result = `<h1>Hello, ${params.data.userName}</h1>`;

zone.onError.subscribe(errorInfo => reject(errorInfo));


appRef.isStable.first(isStable => isStable).subscribe(() => {
// Because 'onStable' fires before 'onError', we have to delay slightly before
// completing the request in case there's an error to report
setImmediate(() => {
resolve({
html: result
});
moduleRef.destroy();
});
});
});
});
});

Observação: Os nomes de propriedade passados auxiliares de marca são representados com PascalCase notação.
Compare isso com a do JavaScript, onde os mesmos nomes de propriedade são representados com camelCase.
A configuração de serialização JSON padrão é responsável por essa diferença.
Para expandir o exemplo de código anterior, dados podem ser transmitidos do servidor para o modo de exibição
por hydrating o globals propriedade fornecida para o resolve função:
import { createServerRenderer, RenderResult } from 'aspnet-prerendering';

export default createServerRenderer(params => {


const providers = [
{ provide: INITIAL_CONFIG, useValue: { document: '<app></app>', url: params.url } },
{ provide: 'ORIGIN_URL', useValue: params.origin }
];

return platformDynamicServer(providers).bootstrapModule(AppModule).then(moduleRef => {


const appRef = moduleRef.injector.get(ApplicationRef);
const state = moduleRef.injector.get(PlatformState);
const zone = moduleRef.injector.get(NgZone);

return new Promise<RenderResult>((resolve, reject) => {


const result = `<h1>Hello, ${params.data.userName}</h1>`;

zone.onError.subscribe(errorInfo => reject(errorInfo));


appRef.isStable.first(isStable => isStable).subscribe(() => {
// Because 'onStable' fires before 'onError', we have to delay slightly before
// completing the request in case there's an error to report
setImmediate(() => {
resolve({
html: result,
globals: {
postList: [
'Introduction to ASP.NET Core',
'Making apps with Angular and ASP.NET Core'
]
}
});
moduleRef.destroy();
});
});
});
});
});

O postList matriz definida dentro do globals objeto está anexado no navegador global window objeto. Essa
variável suspensão em escopo global elimina a duplicação de esforço, particularmente pois ela pertence ao
carregar os mesmos dados uma vez no servidor e novamente no cliente.

Middleware de desenvolvimento webpack


Middleware de desenvolvimento webpack introduz um fluxo de trabalho de desenvolvimento simplificado por
meio do qual Webpack cria recursos sob demanda. O middleware automaticamente compila e serve de recursos
do cliente quando uma página é recarregada no navegador. A abordagem alternativa é invocar Webpack
manualmente por meio do script de compilação do projeto npm quando uma dependência de terceiros ou o
código personalizado é alterado. Script de compilação de um npm Package. JSON arquivo é mostrado no
exemplo a seguir:

"build": "npm run build:vendor && npm run build:custom",

Pré -requisitos
Instale o seguinte:
ASPNET webpack pacote npm:

npm i -D aspnet-webpack

Configuração
Middleware de desenvolvimento webpack está registrado no pipeline de solicitação HTTP por meio de código a
seguir no Startup.cs do arquivo Configure método:

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware();
}
else
{
app.UseExceptionHandler("/Home/Error");
}

// Call UseWebpackDevMiddleware before UseStaticFiles


app.UseStaticFiles();

O UseWebpackDevMiddleware método de extensão deve ser chamado antes de Registrando a hospedagem de


arquivo estático por meio de UseStaticFiles método de extensão. Por motivos de segurança, registre o
middleware somente quando o aplicativo é executado no modo de desenvolvimento.
O webpack.config.js do arquivo output.publicPath propriedade informa o middleware para observar o dist
pasta alterações:

module.exports = (env) => {


output: {
filename: '[name].js',
publicPath: '/dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
},

Substituição do módulo ativa


Pense do Webpack módulo substituições recurso (HMR ) como uma evolução do Webpack desenvolvimento
Middleware. HMR apresenta os mesmos benefícios, mas ele simplifica ainda mais o fluxo de trabalho de
desenvolvimento por atualizar automaticamente o conteúdo da página depois de compilar as alterações. Não
confunda isso com uma atualização do navegador, o que poderia interferir com o estado de memória atual e a
sessão de depuração do SPA. Há um vínculo dinâmico entre o serviço de Middleware de desenvolvimento
Webpack e o navegador, o que significa que as alterações são enviados por push para o navegador.
Pré -requisitos
Instale o seguinte:
middleware hot webpack pacote npm:

npm i -D webpack-hot-middleware

Configuração
O componente HMR deve ser registrado no pipeline de solicitação HTTP do MVC no Configure método:

app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {
HotModuleReplacement = true
});

Como ocorria no Webpack desenvolvimento Middleware, o UseWebpackDevMiddleware método de extensão deve


ser chamado antes do UseStaticFiles método de extensão. Por motivos de segurança, registre o middleware
somente quando o aplicativo é executado no modo de desenvolvimento.
O webpack.config.js arquivo deve definir um plugins de matriz, mesmo se ele for deixado em branco:

module.exports = (env) => {


plugins: [new CheckerPlugin()]

Depois de carregar o aplicativo no navegador, a guia Console de ferramentas de desenvolvedor fornece


confirmação de ativação de HMR:

Roteamentos auxiliares
Na maioria dos SPAs baseado em núcleo do ASP.NET, você vai querer roteamento do lado do cliente além do
roteamento do lado do servidor. Os sistemas de roteamento SPA e MVC podem trabalhar de forma independente,
sem interferência. Há, no entanto, uma borda caso apresentando desafios: identificar as respostas HTTP 404.
Considere o cenário no qual uma rota sem extensão de /some/page é usado. Suponha que a solicitação não-
correspondência de padrão uma rota do lado do servidor, mas o padrão corresponde a uma rota de cliente. Agora,
considere uma solicitação de entrada para /images/user-512.png , que geralmente espera encontrar um arquivo de
imagem no servidor. Se esse caminho de recurso solicitado não corresponde a qualquer rota do lado do servidor
ou um arquivo estático, é improvável que o aplicativo cliente deve tratá-la, você geralmente deseja retornar um
código de status HTTP 404.
Pré -requisitos
Instale o seguinte:
O pacote npm de roteamento do lado do cliente. Usando Angular como um exemplo:

npm i -S @angular/router

Configuração
Um método de extensão denominado MapSpaFallbackRoute é usado no Configure método:

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

routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});

Dica: As rotas são avaliadas na ordem em que eles foram configurados. Consequentemente, o default rota no
exemplo de código anterior é usada pela primeira vez para correspondência de padrões.

Criar um novo projeto


JavaScriptServices fornece modelos de aplicativo pré-configurado. SpaServices é usada nesses modelos, junto
com diferentes estruturas e bibliotecas como Angular, reagir e Redux.
Esses modelos podem ser instalados por meio da CLI do .NET Core executando o seguinte comando:

dotnet new --install Microsoft.AspNetCore.SpaTemplates::*

Uma lista de modelos disponíveis do SPA é exibida:

MODELOS NOME CURTO IDIOMA MARCAS

Núcleo do ASP.NET MVC angular [C#] Web/MVC/SPA


com Angular

Núcleo do ASP.NET MVC react [C#] Web/MVC/SPA


com React.js

Núcleo do ASP.NET MVC reactredux [C#] Web/MVC/SPA


com React.js e retorno

Para criar um novo projeto usando um dos modelos de SPA, inclua o nome curto do modelo no dotnet novo
comando. O comando a seguir cria um aplicativo Angular com ASP.NET MVC de núcleo configurado para o lado
do servidor:

dotnet new angular

Definir o modo de configuração de tempo de execução


Existem dois modos de configuração de tempo de execução principal:
Desenvolvimento:
Inclui mapas de origem para facilitar a depuração.
Não Otimize o código do lado do cliente para o desempenho.
Produção:
Exclui os mapas de origem.
Otimiza o código do lado do cliente por meio de empacotamento e minimização.
ASP.NET Core usa uma variável de ambiente denominada ASPNETCORE_ENVIRONMENT para armazenar o modo de
configuração. Consulte Configurando o ambiente para obter mais informações.
Executando com .NET Core CLI
Restaure o NuGet necessário e os pacotes de npm executando o seguinte comando na raiz do projeto:

dotnet restore && npm i

Compilar e executar o aplicativo:

dotnet run

O aplicativo for iniciado no localhost de acordo com o modo de configuração de tempo de execução. Navegar para
http://localhost:5000 no navegador exibe a página inicial.

Executando o Visual Studio de 2017


Abra o . csproj arquivo gerado pelo dotnet novo comando. Os pacotes NuGet e npm necessários são restaurados
automaticamente após a abertura do projeto. Esse processo de restauração pode demorar alguns minutos e o
aplicativo está pronto para ser executado quando ele for concluído. Clique no botão verde de execução ou
pressione Ctrl + F5 , e o navegador abre a página de aterrissagem do aplicativo. O aplicativo é executado no
localhost de acordo com o modo de configuração de tempo de execução.

O aplicativo de teste
Modelos de SpaServices são pré-configurados para executar testes de cliente usando Karma e Jasmine. Jasmine é
uma unidade popular de estrutura de teste para JavaScript, enquanto Karma é um executor de teste para os testes.
Karma está configurado para funcionar com o Webpack desenvolvimento Middleware , de modo que o
desenvolvedor não é necessário parar e o teste seja executado sempre que forem feitas alterações. Se o código em
execução no caso de teste ou o caso de teste, o teste será executado automaticamente.
Usando o aplicativo Angular como exemplo, dois casos de teste Jasmine já são fornecidos para o
CounterComponent no counter.component.spec.ts arquivo:

it('should display a title', async(() => {


const titleText = fixture.nativeElement.querySelector('h1').textContent;
expect(titleText).toEqual('Counter');
}));

it('should start with count 0, then increments by 1 when clicked', async(() => {
const countElement = fixture.nativeElement.querySelector('strong');
expect(countElement.textContent).toEqual('0');

const incrementButton = fixture.nativeElement.querySelector('button');


incrementButton.click();
fixture.detectChanges();
expect(countElement.textContent).toEqual('1');
}));
Abra o prompt de comando no ClientApp directory. Execute o seguinte comando:

npm test

O script inicia o executor de testes Karma, que lê as configurações definidas de karma.conf.js arquivo. Entre outras
configurações, o karma.conf.js identifica os arquivos de teste para ser executado por meio de seu files matriz:

module.exports = function (config) {


config.set({
files: [
'../../wwwroot/dist/vendor.js',
'./boot-tests.ts'
],

Publicando o aplicativo
Combinando os ativos do lado do cliente gerados e os artefatos do ASP.NET Core publicados em um pacote
pronto para implantar pode ser trabalhoso. Felizmente, SpaServices orquestra esse processo de publicação inteira
com um destino do MSBuild personalizado chamado RunWebpack :

<Target Name="RunWebpack" AfterTargets="ComputeFilesToPublish">


<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec Command="npm install" />
<Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod" />
<Exec Command="node node_modules/webpack/bin/webpack.js --env.prod" />

<!-- Include the newly-built files in the publish output -->


<ItemGroup>
<DistFiles Include="wwwroot\dist\**; ClientApp\dist\**" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</ResolvedFileToPublish>
</ItemGroup>
</Target>

O destino do MSBuild tem as seguintes responsabilidades:


1. Restaure os pacotes de npm
2. Crie uma compilação de nível de produção dos ativos de terceiros, do lado do cliente
3. Crie uma compilação de nível de produção dos ativos do lado do cliente personalizados
4. Copie os ativos Webpack gerado para a pasta de publicação
O destino do MSBuild é chamado durante a execução:

dotnet publish -c Release

Recursos adicionais
Documentos angulares
Usar os modelos de Aplicativo de Página Única com
o ASP.NET Core
10/04/2018 • 1 min to read • Edit Online

OBSERVAÇÃO
O SDK do .NET Core 2.0.x inclui modelos de projeto mais antigos para Angular, React e React with Redux. Esta documentação
não é sobre esses modelos de projeto antigos. Essa documentação é para os modelos Angular, React e React with Redux mais
recentes, que podem ser instalados manualmente no ASP.NET Core 2.0. Os modelos são incluídos por padrão com o ASP.NET
Core 2.1.

Pré-requisitos
.NET Core SDK 2.0 or later
Node.js, versão 6 ou posterior

Instalação
Se você tiver o ASP.NET Core 2.0, execute o seguinte comando para instalar os modelos do ASP.NET Core
atualizados para Angular, React e React with Redux:

dotnet new --install Microsoft.DotNet.Web.Spa.ProjectTemplates::2.0.0

Usar os modelos
Usar o modelo de projeto Angular
Usar o modelo de projeto React
Usar o modelo de projeto React with Redux
Use o modelo de projeto Angular com ASP.NET Core
10/04/2018 • 20 min to read • Edit Online

OBSERVAÇÃO
Esta documentação não está sobre o modelo de projeto Angular incluída no ASP.NET 2.0 de núcleo. Trata-se o modelo
Angular mais recente para o qual você pode atualizar manualmente. O modelo é incluído no ASP.NET Core 2.1 por padrão.

O modelo de projeto Angular atualizado fornece um ponto inicial conveniente para ASP.NET Core aplicativos
Angular e a CLI Angular para implementar uma interface de usuário do lado do cliente avançado (IU ).
O modelo é equivalente à criação de um projeto do ASP.NET Core para atuar como um back-end de API e um
projeto de CLI Angular para atuar como uma interface do usuário. O modelo oferece a conveniência de
hospedagem de ambos os tipos de projeto em um projeto de aplicativo único. Consequentemente, o projeto de
aplicativo pode ser criado e publicado como uma única unidade.

Criar um novo aplicativo


Se usar o ASP.NET 2.0 de núcleo, certifique-se de que você o modelo de projeto Angular atualizado instalado. Se
você tiver o ASP.NET Core 2.1, não é necessário instalá-lo.
Criar um novo projeto a partir de um prompt de comando usando o comando dotnet new angular em um
diretório vazio. Por exemplo, os seguintes comandos criam o aplicativo em um aplicativo my-novo diretório e
alterne para o diretório:

dotnet new angular -o my-new-app


cd my-new-app

Execute o aplicativo do Visual Studio ou o .NET Core CLI:


Visual Studio
CLI do .NET Core
Abra o gerado . csproj de arquivo e executar o aplicativo como normal de lá.
O processo de compilação restaura npm dependências na primeira execução, o que pode levar vários minutos.
Compilações subsequentes são muito mais rápidas.
O modelo de projeto cria um aplicativo do ASP.NET Core e um aplicativo Angular. O aplicativo do ASP.NET Core
destina-se a ser usado para acesso a dados, autorização e outras questões do lado do servidor. O aplicativo
Angular, que residem no ClientApp subdiretório, destina-se a ser usado para todas as questões de interface do
usuário.

Adicione páginas, imagens, estilos, módulos, etc.


O ClientApp diretório contém um aplicativo de CLI Angular padrão. Consulte o oficial documentação Angular
para obter mais informações.
Há pequenas diferenças entre o aplicativo Angular criado por este modelo e criado pela CLI Angular em si (via
ng new ); no entanto, os recursos do aplicativo foram modificados. O aplicativo criado pelo modelo contém um
Bootstrap-com base em layout e um exemplo básico de roteamento.
Execute comandos ng
No prompt de comando, alterne para o ClientApp subdiretório:

cd ClientApp

Se você tiver o ng ferramenta instalada globalmente, você pode executar qualquer um dos seus comandos. Por
exemplo, você pode executar ng lint , ng test , ou quaisquer outras comandos CLI Angular. Não é necessário
para executar ng serve , como seu aplicativo ASP.NET Core lida com que atende ao lado do servidor e cliente
partes do seu aplicativo. Internamente, ele usa ng serve em desenvolvimento.
Se você não tiver o ng ferramenta instalada, execute npm run ng em vez disso. Por exemplo, você pode executar
npm run ng lint ou npm run ng test .

Instalar pacotes npm


Para instalar pacotes de terceiros npm, use um prompt de comando no ClientApp subdiretório. Por exemplo:

cd ClientApp
npm install --save <package_name>

Publicar e implantar
No desenvolvimento, o aplicativo é executado em um modo otimizado para conveniência do desenvolvedor. Por
exemplo, pacotes de JavaScript incluem mapas de origem (de modo que durante a depuração, você pode ver o
código de TypeScript original). O aplicativo observa TypeScript, HTML e CSS alterações de arquivo no disco e
recompila automaticamente e recarrega quando ele vê esses arquivos alterar.
Em produção, atende a uma versão de seu aplicativo que é otimizado para desempenho. Isso é configurado para
ocorrer automaticamente. Quando você publica, a configuração de build emite um minimizada, antecipada de
tempo (AoT) compilados compilação do código do lado do cliente. Diferentemente de compilação de
desenvolvimento, a compilação de produção não requer Node. js ser instalado no servidor (a menos que você
habilitou pré-processamento do lado do servidor).
Você pode usar o padrão métodos de implantação e hospedagem ASP.NET Core.

Executar "ng sirvam" independentemente


O projeto está configurado para iniciar sua própria instância do servidor Angular CLI em segundo plano quando
o aplicativo ASP.NET Core é iniciado no modo de desenvolvimento. Isso é conveniente, porque você não precisa
executar um servidor separado manualmente.
Há uma desvantagem para essa configuração padrão. Cada vez que você modificar seu código c# e o ASP.NET
Core aplicativo precisa ser reiniciado, o servidor de CLI Angular for reiniciado. Cerca de 10 segundos é necessária
para iniciar um backup. Se você estiver fazendo frequentes edições de código c# e não quiser esperar Angular CLI
reiniciar, execute a CLI Angular servidor externamente, independentemente do processo do ASP.NET Core. Para
fazer isso:
1. No prompt de comando, alterne para o ClientApp subdiretório e iniciar o servidor de desenvolvimento
Angular CLI:

cd ClientApp
npm start
IMPORTANTE
Use npm start para iniciar o servidor de desenvolvimento de CLI Angular não ng serve , de modo que a
configuração no Package. JSON é respeitado. Para passar parâmetros adicionais para o servidor de CLI Angular,
adicioná-los ao relevante scripts de linha em sua Package. JSON arquivo.

2. Modifique seu aplicativo ASP.NET Core para usar a instância de CLI Angular externa em vez de iniciar um
dos seus próprios. No seu inicialização classe, substitua o spa.UseAngularCliServer chamada com o
seguinte:

spa.UseProxyToSpaDevelopmentServer("http://localhost:4200");

Quando você iniciar seu aplicativo ASP.NET Core, ele não é possível abrir um servidor Angular CLI. Em vez disso,
é usada a instância que é iniciado manualmente. Isso permite iniciar e reiniciar mais rapidamente. Ele não está
aguardando Angular CLI recriar seu aplicativo cliente a cada vez.

Renderização do lado do servidor


Como um recurso de desempenho, você pode escolher a pré-processar seu aplicativo Angular no servidor, bem
como executá-lo no cliente. Isso significa que os navegadores recebam uma marcação HTML que representa a
interface de usuário inicial do seu aplicativo, para exibirem mesmo antes de baixar e executar seus pacotes de
JavaScript. A maioria da implementação disso vêm de um recurso Angular chamado Angular Universal.

DICA
Habilitar renderização do lado do servidor (SSR) apresenta uma série de complicações extras tanto durante o
desenvolvimento e implantação. Leitura desvantagens SSR para determinar se SSR é uma boa opção para suas necessidades.

Para habilitar SSR, você precisa fazer um número de adições ao seu projeto.
No inicialização classe depois a linha que configura spa.Options.SourcePath , e antes de a chamada para
UseAngularCliServer ou UseProxyToSpaDevelopmentServer , adicione o seguinte:

app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";

spa.UseSpaPrerendering(options =>
{
options.BootModulePath = $"{spa.Options.SourcePath}/dist-server/main.bundle.js";
options.BootModuleBuilder = env.IsDevelopment()
? new AngularCliBuilder(npmScript: "build:ssr")
: null;
options.ExcludeUrls = new[] { "/sockjs-node" };
});

if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});

No modo de desenvolvimento, esse código tentará criar o pacote SSR executando o script build:ssr , que é
definido em ClientApp\package.json. Isso cria um aplicativo Angular chamado ssr , que ainda não está definido.
No final do apps de matriz em ClientApp/.angular-cli.json, definir um aplicativo adicional com o nome ssr . Use
as seguintes opções:

{
"name": "ssr",
"root": "src",
"outDir": "dist-server",
"assets": [
"assets"
],
"main": "main.server.ts",
"tsconfig": "tsconfig.server.json",
"prefix": "app",
"scripts": [],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
},
"platform": "server"
}

Essa nova configuração de aplicativo habilitado para SSR requer dois arquivos adicionais: tsconfig.server.json e
main.server.ts. O tsconfig.server.json arquivo Especifica opções de compilação de TypeScript. O main.server.ts
arquivo serve como o ponto de entrada de código durante SSR.
Adicionar um novo arquivo chamado tsconfig.server.json dentro de ClientApp/src ( junto com o existente
tsconfig.app.json), que contém o seguinte:

{
"extends": "../tsconfig.json",
"compilerOptions": {
"baseUrl": "./",
"module": "commonjs"
},
"angularCompilerOptions": {
"entryModule": "app/app.server.module#AppServerModule"
}
}

Esse arquivo configura o compilador de AoT do Angular para procurar por um módulo chamado
app.server.module . Adicionar isso ao criar um novo arquivo em ClientApp/src/app/app.server.module.ts ( junto
com o existente app.module.ts) que contém o seguinte:

import { NgModule } from '@angular/core';


import { ServerModule } from '@angular/platform-server';
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';
import { AppComponent } from './app.component';
import { AppModule } from './app.module';

@NgModule({
imports: [AppModule, ServerModule, ModuleMapLoaderModule],
bootstrap: [AppComponent]
})
export class AppServerModule { }

Esse módulo herda de seu cliente app.module e define quais módulos extra Angular estão disponíveis durante
SSR.
Lembre-se de que o novo ssr entrada .angular cli.json referenciado de um arquivo de ponto de entrada
chamado main.server.ts. Você ainda não adicionou esse arquivo e agora é hora de fazê-lo. Criar um novo arquivo
em ClientApp/src/main.server.ts ( junto com o existente main.ts), que contém o seguinte:

import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { renderModule, renderModuleFactory } from '@angular/platform-server';
import { APP_BASE_HREF } from '@angular/common';
import { enableProdMode } from '@angular/core';
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
import { createServerRenderer } from 'aspnet-prerendering';
export { AppServerModule } from './app/app.server.module';

enableProdMode();

export default createServerRenderer(params => {


const { AppServerModule, AppServerModuleNgFactory, LAZY_MODULE_MAP } = (module as any).exports;

const options = {
document: params.data.originalHtml,
url: params.url,
extraProviders: [
provideModuleMap(LAZY_MODULE_MAP),
{ provide: APP_BASE_HREF, useValue: params.baseUrl },
{ provide: 'BASE_URL', useValue: params.origin + params.baseUrl }
]
};

const renderPromise = AppServerModuleNgFactory


? /* AoT */ renderModuleFactory(AppServerModuleNgFactory, options)
: /* dev */ renderModule(AppServerModule, options);

return renderPromise.then(html => ({ html }));


});

Deste arquivo é código ASP.NET Core executado para cada solicitação quando ele é executado o
UseSpaPrerendering middleware que você adicionou para a inicialização classe. Trata de recebimento params o
código .NET (por exemplo, a URL que está sendo solicitada) e fazer chamadas a APIs de SSR Angular para obter
o HTML resultante.
Rigor, isso é suficiente para habilitar SSR no modo de desenvolvimento. É essencial para fazer uma alteração final
para que seu aplicativo funcione corretamente quando publicados. No principal do seu aplicativo . csproj de
arquivo, defina o BuildServerSideRenderer valor da propriedade true :

<!-- Set this to true if you enable server-side prerendering -->


<BuildServerSideRenderer>true</BuildServerSideRenderer>

Isso configura o processo de compilação para executar build:ssr durante a publicação e implantar os arquivos
SSR no servidor. Se você não habilitar isso, SSR falhará em produção.
Quando seu aplicativo é executado no modo de desenvolvimento ou de produção, o código Angular é
previamente renderizada como HTML no servidor. O código do lado do cliente é executada normalmente.
Passar dados de código .NET no código do TypeScript
Durante SSR, convém passar dados de cada solicitação de seu aplicativo ASP.NET Core em seu aplicativo Angular.
Por exemplo, você pode transmitir informações de cookie ou algo ler de um banco de dados. Para fazer isso, edite
o inicialização classe. No retorno de chamada para UseSpaPrerendering , defina um valor para options.SupplyData
como o seguinte:
options.SupplyData = (context, data) =>
{
// Creates a new value called isHttpsRequest that's passed to TypeScript code
data["isHttpsRequest"] = context.Request.IsHttps;
};

O SupplyData permite que o retorno de chamada é passar arbitrários, por solicitação, dados serializáveis em
JSON (por exemplo, cadeias de caracteres, boolianos ou números). O main.server.ts código recebe como
params.data . Por exemplo, o exemplo de código anterior passa um valor booliano como
params.data.isHttpsRequest para o createServerRenderer retorno de chamada. Você pode passar essa a outras
partes do seu aplicativo de alguma forma Angular com suporte. Por exemplo, consulte como main.server.ts passa
o BASE_URL valor para qualquer componente cujo construtor está declarado para recebê-lo.
Desvantagens de SSR
Nem todos os aplicativos se beneficiam SSR. O principal benefício é percebido desempenho. Os visitantes atingir
seu aplicativo em uma conexão de rede lenta ou em dispositivos móveis lenta consulte a interface do usuário
inicial rapidamente, mesmo que leva algum tempo para buscar ou para analisar os pacotes de JavaScript. No
entanto, muitos SPAs são usados principalmente em redes da empresa internos, rápida em computadores rápidos
onde o aplicativo aparece quase instantaneamente.
Ao mesmo tempo, há desvantagens significativas para habilitar SSR. Ele adiciona complexidade ao seu processo
de desenvolvimento. O código deve ser executado em dois ambientes diferentes: do lado do cliente e do servidor
(em um ambiente de Node. js invocado do ASP.NET Core). Aqui estão algumas coisas a considerar:
SSR requer uma instalação de Node. js nos servidores de produção. Isso ocorre automaticamente para
alguns cenários de implantação, como serviços de aplicativo do Azure, mas não para outras pessoas, como
o Azure Service Fabric.
Habilitando o BuildServerSideRenderer faz com que o sinalizador de compilação seu node_modules
directory para publicar. Esta pasta contém arquivos de 20.000, que aumenta o tempo de implantação.
Para executar o código em um ambiente de Node. js, ele não pode contar com a existência de navegador
JavaScript APIs específicas, como window ou localStorage . Se seu código (ou alguns biblioteca de
terceiros que você faz referência) tenta usar essas APIs, você obterá um erro durante SSR. Por exemplo,
não use jQuery porque faz referência a APIs específicas do navegador em vários locais. Para evitar erros,
você deve evitar SSR ou evitar APIs ou bibliotecas de navegador específico. Você pode encapsular todas as
chamadas para essas APIs em verificações para garantir que eles não são chamados durante SSR. Por
exemplo, use uma seleção como o seguinte no código JavaScript ou TypeScript:

if (typeof window !== 'undefined') {


// Call browser-specific APIs here
}
Use o modelo de projeto de reagir com ASP.NET
Core
10/04/2018 • 7 min to read • Edit Online

OBSERVAÇÃO
Esta documentação não está sobre o modelo de projeto reagir incluída no ASP.NET 2.0 de núcleo. Trata-se o modelo de
reagir mais recente para o qual você pode atualizar manualmente. O modelo é incluído no ASP.NET Core 2.1 por padrão.

O modelo de projeto reagir atualizado fornece um ponto inicial conveniente para ASP.NET Core aplicativos
usando reagir e criar reagir-aplicativo convenções (CRA) para implementar uma interface de usuário do lado do
cliente avançado (IU ).
O modelo é equivalente à criação de um projeto do ASP.NET Core para atuar como um back-end de API e um
projeto padrão CRA reagir para atuar como uma interface do usuário, mas com a conveniência de hospedagem
em um projeto de aplicativo único que pode ser criado e publicado como uma única unidade.

Criar um novo aplicativo


Se usar o ASP.NET 2.0 de núcleo, certifique-se de que você instalado o modelo de projeto reagir atualizado. Se
você tiver o ASP.NET Core 2.1, não é necessário instalá-lo.
Criar um novo projeto a partir de um prompt de comando usando o comando dotnet new react em um diretório
vazio. Por exemplo, os seguintes comandos criam o aplicativo em um aplicativo my-novo diretório e alterne para
o diretório:

dotnet new react -o my-new-app


cd my-new-app

Execute o aplicativo do Visual Studio ou o .NET Core CLI:


Visual Studio
CLI do .NET Core
Abra o gerado . csproj de arquivo e executar o aplicativo como normal de lá.
O processo de compilação restaura npm dependências na primeira execução, o que pode levar vários minutos.
Compilações subsequentes são muito mais rápidas.
O modelo de projeto cria um aplicativo do ASP.NET Core e um aplicativo reagir. O aplicativo do ASP.NET Core
destina-se a ser usado para acesso a dados, autorização e outras questões do lado do servidor. O aplicativo reagir,
que residem no ClientApp subdiretório, destina-se a ser usado para todas as questões de interface do usuário.

Adicione páginas, imagens, estilos, módulos, etc.


O ClientApp diretório é um aplicativo de reagir CRA padrão. Consulte o oficial documentação CRA para obter
mais informações.
Há pequenas diferenças entre o aplicativo reagir criado por este modelo e um criado por CRA em si; No entanto,
os recursos do aplicativo são inalterados. O aplicativo criado pelo modelo contém um Bootstrap-com base em
layout e um exemplo básico de roteamento.

Instalar pacotes npm


Para instalar pacotes de terceiros npm, use um prompt de comando no ClientApp subdiretório. Por exemplo:

cd ClientApp
npm install --save <package_name>

Publicar e implantar
No desenvolvimento, o aplicativo é executado em um modo otimizado para conveniência do desenvolvedor. Por
exemplo, pacotes de JavaScript incluem mapas de origem (de modo que durante a depuração, você pode ver o
código-fonte original). O aplicativo observa JavaScript, HTML e CSS alterações de arquivo no disco e recompila
automaticamente e recarrega quando ele vê esses arquivos alterar.
Em produção, atende a uma versão de seu aplicativo que é otimizado para desempenho. Isso é configurado para
ocorrer automaticamente. Quando você publica, a configuração de build emite uma compilação transpiled
minimizada, do seu código do lado do cliente. Diferentemente de compilação de desenvolvimento, a compilação
de produção não requer Node. js ser instalado no servidor.
Você pode usar o padrão métodos de implantação e hospedagem ASP.NET Core.

Executar o servidor CRA independentemente


O projeto está configurado para iniciar sua própria instância do servidor de desenvolvimento CRA em segundo
plano quando o aplicativo ASP.NET Core é iniciado no modo de desenvolvimento. Isso é conveniente, porque isso
significa que você não precisa executar um servidor separado manualmente.
Há uma desvantagem para essa configuração padrão. Cada vez que você modificar seu código c# e o ASP.NET
Core aplicativo precisa ser reiniciado, o servidor CRA for reiniciado. Alguns segundos são necessários para iniciar
um backup. Se você estiver fazendo frequentes edições de código c# e não quiser esperar o servidor CRA
reiniciar, execute o servidor CRA externamente, independentemente do processo do ASP.NET Core. Para fazer
isso:
1. No prompt de comando, alterne para o ClientApp subdiretório e iniciar o servidor de desenvolvimento
CRA:

cd ClientApp
npm start

2. Modifique seu aplicativo ASP.NET Core para usar a instância do servidor CRA externa em vez de iniciar
um dos seus próprios. No seu inicialização classe, substitua o spa.UseReactDevelopmentServer chamada
com o seguinte:

spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");

Quando você iniciar seu aplicativo ASP.NET Core, ele não é possível abrir um servidor CRA. Em vez disso, é
usada a instância que é iniciado manualmente. Isso permite iniciar e reiniciar mais rapidamente. Ele não esteja
mais aguardando para seu aplicativo reagir recriar a cada vez.
Use o modelo de projeto reagir com retorno com o
ASP.NET Core
10/04/2018 • 1 min to read • Edit Online

OBSERVAÇÃO
Esta documentação não está sobre o modelo de projeto reagir com retorno incluída no ASP.NET 2.0 de núcleo. Trata-se o
modelo de reagir com retorno mais recente para o qual você pode atualizar manualmente. O modelo é incluído no ASP.NET
Core 2.1 por padrão.

O modelo de projeto reagir com Redux atualizado fornece um ponto inicial conveniente para aplicativos ASP.NET
Core usando reagem, Redux, e criar reagir-aplicativo convenções (CRA) para implementar uma interface de
usuário do lado do cliente avançado (IU ).
Com exceção do comando de criação de projeto, todas as informações sobre o modelo reagir com retorno serão o
mesmo que o modelo de reagir. Para criar esse tipo de projeto, execute dotnet new reactredux em vez de
dotnet new react . Para obter mais informações sobre a funcionalidade comum para ambos os modelos baseados
em reagir, consulte reagir a documentação do modelo.
SignalR do ASP.NET Core
08/05/2018 • 1 min to read • Edit Online

Introdução
Introdução
Hubs
Cliente JavaScript
Plataformas compatíveis
Introdução ao ASP.NET Core SignalR
08/05/2018 • 4 min to read • Edit Online

Por Rachel Appel

OBSERVAÇÃO
ASP.NET Core 2.1 is in preview and not recommended for production use.

O que é o SignalR?
O SignalR do ASP.NET Core é uma biblioteca que simplifica a funcionalidade Adicionar web em tempo real para
aplicativos. A funcionalidade da web em tempo real permite que o código do lado do servidor de conteúdo por
push aos clientes imediatamente.
Bons candidatos para o SignalR:
Aplicativos que exigem atualizações de alta frequência do servidor. Os exemplos são jogos, redes sociais,
votação, leilão, mapas e aplicativos GPS.
Painéis de controle e monitoramento de aplicativos. Exemplos incluem painéis da empresa, atualizações de
vendas instantâneas, ou alertas de viagem.
Aplicativos de colaboração. Quadro de comunicações aplicativos e software de reunião de equipe são exemplos
de aplicativos de colaboração.
Aplicativos que exigem as notificações. Redes sociais, email, bate-papo, jogos, alertas de viagem e muitos
outros aplicativos usam notificações.
O SignalR fornece uma API para a criação de servidor-para-cliente chamadas de procedimento remoto (RPC ). Os
RPCs chamam funções JavaScript em clientes do código-fonte do .NET do servidor.
SignalR para ASP.NET Core:
Manipula o gerenciamento de conexão automaticamente.
Permite a transmissão de mensagens para todos os clientes conectados simultaneamente. Por exemplo, uma
sala de bate-papo.
Permite o envio de mensagens para clientes específicos ou grupos de clientes.
É o código aberto em GitHub.
Escalonável.
A conexão entre o cliente e servidor é persistente, ao contrário de uma conexão HTTP.

Transportes
SignalR resumos sobre várias técnicas para criar aplicativos web em tempo real. O WebSocket é o transporte
ideal, mas outras técnicas como eventos Server-Sent e sondagem longa podem ser usadas quando os não estão
disponíveis. SignalR detectará automaticamente e inicializar o transporte apropriado com base em recursos com
suporte no cliente e servidor.

Hubs
O SignalR usa hubs para comunicação entre clientes e servidores.
Um hub é um pipeline de alto nível que permite que o cliente e o servidor chamar métodos em si. SignalR lida
com a distribuição entre limites de máquina automaticamente, permitindo que os clientes chamar os métodos no
servidor como facilmente como métodos locais e vice-versa. Hubs permitem passar parâmetros fortemente
tipadas para métodos, que permite que a associação de modelo. O SignalR fornece dois protocolos de hub
interno: um protocolo de texto com base em JSON e um protocolo binário com base em MessagePack.
MessagePack geralmente cria mensagens menores do que o uso JSON. Devem oferecer suporte a navegadores
mais antigos nível XHR 2 para oferecer suporte a protocolo MessagePack.
Hubs de chamar o código de cliente, enviando mensagens usando o transporte ativo. As mensagens contêm o
nome e os parâmetros do método do lado do cliente. Objetos enviados como parâmetros de método
desserializados usando o protocolo configurado. O cliente tenta corresponder o nome a um método no código do
lado do cliente. Quando ocorrer uma correspondência, o método do cliente é executado usando os dados de
parâmetro desserializado.

Recursos adicionais
Introdução ao SignalR para ASP.NET Core
Plataformas com suporte
Hubs
Cliente JavaScript
Introdução ao SignalR no ASP.NET Core
08/05/2018 • 8 min to read • Edit Online

Por Rachel Appel

OBSERVAÇÃO
ASP.NET Core 2.1 is in preview and not recommended for production use.

Este tutorial ensina as Noções básicas de criação de um aplicativo em tempo real com SignalR para ASP.NET
Core.
Este tutorial demonstra as seguintes tarefas de desenvolvimento SignalR:
Crie um SignalR no aplicativo web do ASP.NET Core.
Crie um hub SignalR para enviar por push o conteúdo aos clientes.
Modificar o Startup classe e configurar o aplicativo.

Exibir ou baixar código de exemplo (como baixar)

Pré-requisitos
Instale o software a seguir:
Visual Studio
Visual Studio Code
.NET core 2.1.0 Preview SDK 2 ou posterior
Visual Studio de 2017 15.7 ou posterior com o desenvolvimento ASP.NET e web carga de trabalho
npm

Criar um projeto do ASP.NET Core que hospeda o servidor e cliente


SignalR
Visual Studio
Visual Studio Code
1. Use o arquivo > novo projeto menu opção e escolha aplicativo Web do ASP.NET Core. Nomeie o
projeto SignalRChat.

2. Selecione aplicativo Web para criar um projeto usando as páginas Razor. Em seguida, selecione Okey.
Certifique-se de que ASP.NET Core 2.1 é selecionado do seletor do framework, embora o SignalR é
executado em versões anteriores do .NET.
O Visual Studio inclui a Microsoft.AspNetCore.SignalR pacote que contém suas bibliotecas do servidor como parte
de seu aplicativo Web do ASP.NET Core modelo. No entanto, a biblioteca de cliente JavaScript para o SignalR
deve ser instalada usando npm.
3. Execute os seguintes comandos Package Manager Console janela, na raiz do projeto:

npm init -y
npm install @aspnet/signalr

4. Copiar o signalr.js arquivo *node_modules\ @aspnet\signalr\dist\browser* para o lib pasta em seu projeto.

Criar o Hub SignalR


Um hub é uma classe que serve como um pipeline de alto nível que permite que o cliente e servidor chamar
métodos em si.
Visual Studio
Visual Studio Code
1. Adicionar uma classe ao projeto, escolhendo arquivo > novo > arquivo e selecionando Visual C# classe.
2. Herdar de Microsoft.AspNetCore.SignalR.Hub . O Hub classe contém propriedades e eventos para gerenciar
conexões e grupos, bem como enviar e receber dados.
3. Criar o SendMessage método que envia uma mensagem a todos os clientes conectados bate-papo. Observe
que ele retorna um tarefa, pois o SignalR é assíncrono. Será melhor dimensionado código assíncrono.
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;

namespace SignalRChat.Hubs
{
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}

Configurar o projeto para usar o SignalR


O servidor do SignalR deve ser configurado para que ele saiba que pode passar solicitações para o SignalR.
1. Para configurar um projeto SignalR, modifique o projeto Startup.ConfigureServices método.
services.AddSignalR Adiciona o SignalR como parte do middleware pipeline.
2. Configurar rotas para seus hubs usando UseSignalR .
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using SignalRChat.Hubs;

namespace SignalRChat
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)


{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});

services.AddMvc();

//services.AddCors(options => options.AddPolicy("CorsPolicy",


//builder =>
//{
// builder.AllowAnyMethod().AllowAnyHeader()
// .WithOrigins("http://localhost:55830")
// .AllowCredentials();
//}));

services.AddSignalR();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)


{
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
//app.UseCors("CorsPolicy");
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chathub");
});
app.UseMvc();
}
}
}
Criar o código de cliente SignalR
1. Substitua o conteúdo Pages\Index.cshtml com o código a seguir:

@page
<div class="container">
<div class="row">&nbsp;</div>
<div class="row">
<div class="col-6">&nbsp;</div>
<div class="col-6">
User..........<input type="text" id="userInput" />
<br />
Message...<input type="text" id="messageInput" />
<input type="button" id="sendButton" value="Send Message" />
</div>
</div>
<div class="row">
<div class="col-12">
<hr />
</div>
</div>
<div class="row">
<div class="col-6">&nbsp;</div>
<div class="col-6">
<ul id="messagesList"></ul>
</div>
</div>
</div>
<script src="~/lib/signalr/signalr.js"></script>
<script src="~/js/chat.js"></script>
@*
If you need a version of the chat script that is compatible with IE 11, replace the above <script>
tag that imports chat.js with this one
<script src="~/js/es5-chat.js"></script>
*@

O HTML anterior exibe o nome e os campos de mensagem e um botão de envio. Observe as referências de
script na parte inferior: uma referência para o SignalR e chat.js.
2. Adicione um arquivo JavaScript, denominado chat.js, para o wwwroot\js pasta. Adicione o seguinte código
a ele:
// The following sample code uses modern ECMAScript 6 features
// that aren't supported in Internet Explorer 11.
// To convert the sample for environments that do not support ECMAScript 6,
// such as Internet Explorer 11, use a transpiler such as
// Babel at http://babeljs.io/.
//
// See Es5-chat.js for a Babel transpiled version of the following code:

const connection = new signalR.HubConnectionBuilder()


.withUrl("/chatHub")
.build();

connection.on("ReceiveMessage", (user, message) => {


const encodedMsg = user + " says " + message;
const li = document.createElement("li");
li.textContent = encodedMsg;
document.getElementById("messagesList").appendChild(li);
});

document.getElementById("sendButton").addEventListener("click", event => {


const user = document.getElementById("userInput").value;
const message = document.getElementById("messageInput").value;
connection.invoke("SendMessage", user, message).catch(err => console.error(err.toString()));
event.preventDefault();
});

connection.start().catch(err => console.error(err.toString()));

Executar o aplicativo
Visual Studio
Visual Studio Code
1. Selecione depurar > iniciar sem depuração para iniciar um navegador e carregar o site localmente.
Copie a URL da barra de endereços.
2. Abrir outra instância de navegador (qualquer navegador) e cole a URL na barra de endereços.
3. Escolha um navegador, digite um nome e uma mensagem e clique no enviar botão. O nome e a mensagem
são exibidos em ambas as páginas instantaneamente.
Recursos relacionados
Introdução ao ASP.NET Core SignalR
Usando os hubs de SignalR para ASP.NET Core
08/05/2018 • 6 min to read • Edit Online

Por Rachel Appel e Griffin Kevin

OBSERVAÇÃO
ASP.NET Core 2.1 is in preview and not recommended for production use.

Exibir ou baixar o código de exemplo (como fazer o download)

O que é um hub SignalR


A API de Hubs de SignalR permite chamar métodos em clientes conectados do servidor. No código de servidor,
você define os métodos que são chamados pelo cliente. No código do cliente, você define os métodos que são
chamados do servidor. SignalR cuida de tudo nos bastidores que possibilita a comunicação de cliente para
servidor e servidor-para-cliente em tempo real.

Configurar os hubs de SignalR


O middleware SignalR requer alguns serviços, que são configurados por meio da chamada services.AddSignalR .

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

Ao adicionar a funcionalidade de SignalR para um aplicativo ASP.NET Core, configurar rotas SignalR chamando
app.UseSignalR no Startup.Configure método.

app.UseSignalR(route =>
{
route.MapHub<ChatHub>("/chathub");

Criar e usar hubs


Criar um hub declarando uma classe que herda de Hub e adicionar métodos públicos a ele. Os clientes podem
chamar os métodos que são definidos como public .
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user,message);
}

public Task SendMessageToCaller(string message)


{
return Clients.Caller.SendAsync("ReceiveMessage", message);
}

public Task SendMessageToGroups(string message)


{
List<string> groups = new List<string>() { "SignalR Users" };
return Clients.Groups(groups).SendAsync("ReceiveMessage", message);
}

public override async Task OnConnectedAsync()


{
await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnConnectedAsync();
}

public override async Task OnDisconnectedAsync(Exception exception)


{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnDisconnectedAsync(exception);
}
}

Você pode especificar um tipo de retorno e parâmetros, incluindo tipos complexos e matrizes, como você faria em
qualquer método em c#. SignalR lida com a serialização e desserialização de objetos complexos e matrizes em
seus parâmetros e valores de retorno.

O objeto de clientes
Cada instância do Hub classe tem uma propriedade denominada Clients que contém os seguintes membros
para a comunicação entre cliente e servidor:

PROPRIEDADE DESCRIÇÃO

All Chama um método em todos os clientes conectados

Caller Chama um método no cliente que invocou o método de hub

Others Chama um método em todos os clientes conectados, exceto o


cliente que invocou o método

Além disso, a Hub classe contém os seguintes métodos:

MÉTODO DESCRIÇÃO

AllExcept Chama um método em todos os clientes conectados, exceto


para as conexões especificadas

Client Chama um método em um cliente conectado específico


MÉTODO DESCRIÇÃO

Clients Chama um método específicos clientes conectados

Group Envia uma mensagem para todas as conexões no grupo


especificado

GroupExcept Envia uma mensagem para todas as conexões no grupo


especificado, exceto as conexões especificadas

Groups Envia uma mensagem para vários grupos de conexões

OthersInGroup Envia uma mensagem para um grupo de conexões, excluindo


o cliente que invocou o método de hub

User Envia uma mensagem para todas as conexões associadas a


um usuário específico

Users Envia uma mensagem para todas as conexões associadas com


os usuários especificados

Cada propriedade ou método nas tabelas anteriores retorna um objeto com um SendAsync método. O SendAsync
método permite que você forneça o nome e os parâmetros do método de cliente para chamar.

Enviar mensagens para os clientes


Para fazer chamadas para clientes específicos, use as propriedades do Clients objeto. A seguir no exemplo a
seguir, o SendMessageToCaller método demonstra enviando uma mensagem para a conexão que invocou o
método de hub. O SendMessageToGroups método envia uma mensagem para os grupos armazenados em um List
chamado groups .

public Task SendMessageToCaller(string message)


{
return Clients.Caller.SendAsync("ReceiveMessage", message);
}

public Task SendMessageToGroups(string message)


{
List<string> groups = new List<string>() { "SignalR Users" };
return Clients.Groups(groups).SendAsync("ReceiveMessage", message);
}

Manipular eventos para uma conexão


A API de Hubs de SignalR fornece o OnConnectedAsync e OnDisconnectedAsync métodos virtuais para gerenciar e
controlar conexões. Substituir o OnConnectedAsync método virtual para executar ações quando um cliente se
conecta ao Hub, como adicioná-lo a um grupo.
public override async Task OnConnectedAsync()
{
await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnConnectedAsync();
}

public override async Task OnDisconnectedAsync(Exception exception)


{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnDisconnectedAsync(exception);
}

Tratar erros
Exceções geradas em seus métodos de hub são enviadas ao cliente que invocou o método. No cliente JavaScript, o
invoke método retorna um promessa JavaScript. Quando o cliente recebe um erro com um manipulador
anexado à promessa usando catch , ele foi chamado e passado como um JavaScript Error objeto.

const message = document.getElementById("messageInput").value;

Recursos relacionados
Introdução ao ASP.NET Core SignalR
Cliente do ASP.NET Core SignalR JavaScript
08/05/2018 • 6 min to read • Edit Online

Por Rachel Appel

OBSERVAÇÃO
ASP.NET Core 2.1 is in preview and not recommended for production use.

A biblioteca de cliente ASP.NET Core SignalR JavaScript permite que os desenvolvedores chamar o código de hub
do lado do servidor.
Exibir ou baixar código de exemplo (como baixar)

Instale o pacote de cliente SignalR


A biblioteca de cliente SignalR JavaScript é entregue como um npm pacote. Se você estiver usando o Visual
Studio, execute npm install do Package Manager Console enquanto estiver na pasta raiz. Para o código do
Visual Studio, execute o comando a partir de Terminal integrada.

npm init -y
npm install @aspnet/signalr

NPM instala o conteúdo do pacote no *node_modules\ @aspnet\signalr\dist\browser* pasta. Criar uma nova
pasta chamada signalr sob o wwwroot\lib pasta. Copie o signalr.js o arquivo para o wwwroot\lib\signalr pasta.

Usar o cliente SignalR JavaScript


Referência do cliente SignalR JavaScript a <script> elemento.

<script src="~/lib/signalr/signalr.js"></script>

Conectar a um hub
O código a seguir cria e inicia uma conexão. Nome do hub diferencia maiusculas de minúsculas.

// The following sample code uses modern ECMAScript 6 features


// that aren't supported in Internet Explorer 11.
document.getElementById("messagesList").appendChild(li);

Conexões entre origens


Normalmente, navegadores carregar conexões do mesmo domínio que a página solicitada. No entanto, há
ocasiões em que é necessária uma conexão para outro domínio.
Para impedir que um site mal-intencionado lendo dados confidenciais de outro site, conexões entre origens estão
desabilitados por padrão. Para permitir que uma solicitação entre origens, habilitá-lo na Startup classe.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using SignalRChat.Hubs;

namespace SignalRChat
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)


{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});

services.AddMvc();

services.AddCors(options => options.AddPolicy("CorsPolicy",


builder =>
{
builder.AllowAnyMethod().AllowAnyHeader()
.WithOrigins("http://localhost:55830")
.AllowCredentials();
}));

services.AddSignalR();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)


{
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseCors("CorsPolicy");
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chathub");
});
app.UseMvc();
}
}
}
Chamar métodos de hub do cliente
Os clientes JavaScript chamar métodos públicos em hubs por usando connection.invoke .O invoke método
aceita dois argumentos:
O nome do método de hub. O exemplo a seguir, o nome de hub é SendMessage .
Nenhum argumento definido no método de hub. No exemplo a seguir, é o nome do argumento message .

connection.on("ReceiveMessage", (user, message) => {

Chamar métodos de cliente do hub


Para receber mensagens do hub, definir um método usando o connection.on método.
O nome do método de cliente JavaScript. No exemplo a seguir, é o nome do método ReceiveMessage .
O hub passa para o método de argumentos. No exemplo a seguir, o valor do argumento é message .

// such as Internet Explorer 11, use a transpiler such as


// Babel at http://babeljs.io/.
//
// See Es5-chat.js for a Babel transpiled version of the following code:

const connection = new signalR.HubConnectionBuilder()

O código anterior no connection.on é executado quando o código do lado do servidor chamá-lo usando o
SendAsync método.

public async Task SendMessage(string user, string message)


{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}

SignalR determina qual método de cliente para chamar correspondendo o nome do método e argumentos
definidos no SendAsync e connection.on .

OBSERVAÇÃO
Como prática recomendada, chame connection.start depois connection.on para seus manipuladores são registrados
antes de todas as mensagens são recebidas.

Registro em log e tratamento de erros


Cadeia de um catch método a fim do connection.start método para tratar erros de cliente. Use console.error
para erros de saída para o console do navegador.

document.getElementById("messagesList").appendChild(li);

Configure o rastreamento de log do lado do cliente, passando um agente de log e o tipo de evento em log quando
a conexão é feita. Mensagens são registradas com o nível de log especificado e superior. Níveis de log disponíveis
são os seguintes:
signalR.LogLevel.Error : Mensagens de erro. Logs de Error mensagens somente.
signalR.LogLevel.Warning : Mensagens de aviso sobre erros em potencial. Logs de Warning ,e Error
mensagens.
signalR.LogLevel.Information : Mensagens de status sem erros. Logs de Information , Warning ,e Error
mensagens.
signalR.LogLevel.Trace : Mensagens de rastreamento. Registra tudo, incluindo dados transportados entre
cliente e hub.
Passe o agente de log para a conexão para iniciar o log. Ferramentas de desenvolvedor do navegador
normalmente contêm um console que exibe as mensagens.

// The following sample code uses modern ECMAScript 6 features


// that aren't supported in Internet Explorer 11.

Recursos relacionados
Hubs de SignalR do ASP.NET Core
Habilitar solicitações entre origens (CORS ) no núcleo do ASP.NET
Publicar um núcleo de ASP.NET SignalR aplicativo
para um aplicativo Web do Azure
08/05/2018 • 3 min to read • Edit Online

Aplicativo Web do Azure é um a computação em nuvem Microsoft serviço de plataforma para hospedar
aplicativos web, incluindo o ASP.NET Core.

Publique o aplicativo
Visual Studio fornece ferramentas internas para a publicação para um aplicativo Web do Azure. Usuário do Visual
Studio Code pode usar CLI do Azure comandos para publicar aplicativos no Azure. Este artigo aborda a publicação
usando as ferramentas do Visual Studio. Para publicar um aplicativo usando a CLI do Azure, consulte publicar um
aplicativo do ASP.NET Core para o Azure com ferramentas de linha de comando.
Clique com botão direito no projeto no Solution Explorer e selecione publicar. Confirme se criar novo check-in
a escolher um destino de publicação caixa de diálogo e selecione publicar.

Insira as seguintes informações no criar serviço de aplicativo caixa de diálogo e selecione criar.

ITEM DESCRIÇÃO

Nome do aplicativo Um nome exclusivo do aplicativo.

Assinatura A assinatura do Azure que usa o aplicativo.


ITEM DESCRIÇÃO

Grupo de recursos O grupo de recursos relacionados ao qual pertence o


aplicativo.

Plano de hospedagem O plano de preço para o aplicativo web.

O Visual Studio executará as seguintes tarefas:


Cria um perfil de publicação que contém as configurações de publicação.
Cria ou usa um existente aplicativo Web do Azure com os detalhes fornecidos.
Publica o aplicativo.
Inicia um navegador, com o aplicativo web publicado carregado.
Observe o formato da URL para o aplicativo é .azurewebsites {nome do aplicativo } .net. Por exemplo, um aplicativo
chamado SignalRChattR tem uma URL semelhante a https://signalrchattr.azurewebsites.net .
Se ocorrer um erro de HTTP 502.2, consulte versão de visualização de implantar o ASP.NET Core para o serviço de
aplicativo do Azure resolvê-lo.

Configurar o aplicativo web de SignalR


ASP.NET SignalR Core aplicativos que são publicados como um aplicativo Web do Azure deve ter ARR afinidade
habilitado. O WebSocket deve ser habilitado, para permitir que o transporte de WebSocket à função.
No portal do Azure, navegue até configurações do aplicativo para seu aplicativo web. Definir WebSockets para
nae verifique se ARR afinidade é em.
O WebSocket e outros transportes são limitados com base no plano de serviço de aplicativo.

Recursos relacionados
Publicar um aplicativo do ASP.NET Core para o Azure com ferramentas de linha de comando
Publicar um aplicativo do ASP.NET Core para o Azure com o Visual Studio
Hospedar e implantar aplicativos de visualização do ASP.NET Core no Azure
O SignalR do ASP.NET Core as plataformas com
suporte
08/05/2018 • 1 min to read • Edit Online

Requisitos de sistema do servidor


SignalR para ASP.NET Core dá suporte a qualquer plataforma de servidor que ASP.NET Core dá suporte.

Requisitos do sistema cliente


Suporte a navegador
O SignalR para cliente do ASP.NET Core JavaScript suporta os seguintes navegadores:

NAVEGADOR VERSÃO

Microsoft Internet Explorer 11

Microsoft Edge atual

Mozilla Firefox atual

Google Chrome; inclui o Android atual

Safari; inclui o iOS atual

Suporte de cliente .NET


Nenhuma plataforma de servidor com suporte pelo ASP.NET Core. Ao usar o IIS, o transporte de WebSocket
requer o IIS 8.0 ou superior, no Windows Server 2012 ou superior. Outros transportes têm suporte em todas as
plataformas.
Desenvolvimento móvel com o ASP.NET Core
19/03/2018 • 1 min to read • Edit Online

Criar serviços de back-end para aplicativos móveis nativos


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.
Hospedar e implantar o ASP.NET Core
10/04/2018 • 6 min to read • Edit Online

Em geral, implantar um aplicativo ASP.NET Core em um ambiente de hospedagem:


Publique o aplicativo em uma pasta no servidor de hospedagem.
Configure um gerenciador de processo que inicia o aplicativo quando a solicitação chega e reinicia-o
depois que ele falha ou que o servidor é reinicializado.
Configure um proxy reverso que encaminha solicitações para o aplicativo.

Publicar em uma pasta


O comando dotnet publish da CLI compila o código do aplicativo e copia os arquivos necessários para
executar o aplicativo em uma pasta publish. Ao implantar usando o Visual Studio, a etapa dotnet publish é
executada automaticamente antes de os arquivos serem copiados para o destino da implantação.
Conteúdo da pasta
A pasta publish contém arquivos .exe e .dll para o aplicativo, as respectivas dependências e, opcionalmente, o
tempo de execução do .NET.
Um aplicativo .NET Core pode ser publicado como autocontido ou dependente de estrutura. Se o aplicativo é
autocontido, os arquivos .dll que contêm o tempo de execução do .NET são incluídos na pasta publish. Se o
aplicativo depender da estrutura, os arquivos de tempo de execução do .NET não serão incluídos porque o
aplicativo tem uma referência para uma versão do .NET que está instalada no servidor. O modelo de
implantação padrão é dependente da estrutura. Para obter mais informações, consulte Implantação de
aplicativos .NET Core.
Além de arquivos .exe e .dll, a pasta publish para um aplicativo ASP.NET Core normalmente contém arquivos
de configuração, ativos estáticos e exibições do MVC. Para obter mais informações, consulte Estrutura de
diretórios.

Configure um gerenciador de processo


Um aplicativo ASP.NET Core é um aplicativo de console que deve ser iniciado quando um servidor é
inicializado e reiniciado após falhas. Para automatizar inicializações e reinicializações, um gerenciador de
processo é necessário. Os gerenciadores de processo mais comuns para o ASP.NET Core são:
Linux
Nginx
Apache
Windows
IIS
Serviço Windows

Configurar um proxy reverso


ASP.NET Core 2.x
ASP.NET Core 1.x
Se o aplicativo usar o servidor Web Kestrel, você poderá usar Nginx, Apache ou IIS como um servidor proxy
reverso. Um servidor proxy reverso recebe solicitações HTTP da Internet e as encaminha para o Kestrel após
algum tratamento preliminar. Para obter mais informações, consulte Quando usar Kestrel com um proxy
reverso.

Servidor proxy e cenários de balanceador de carga


Configuração adicional pode ser necessária para aplicativos hospedados atrás de servidores proxy e
balanceadores de carga. Sem configuração adicional, um aplicativo pode não ter acesso ao esquema
(HTTP/HTTPS ) e ao endereço IP remoto em que uma solicitação foi originada. Para obter mais informações,
veja Configurar o ASP.NET Core para trabalhar com servidores proxy e balanceadores de carga.

Usando o Visual Studio e o MSBuild para automatizar a implantação


A implantação muitas vezes requer tarefas adicionais além de copiar a saída da dotnet publish para um
servidor. Por exemplo, arquivos extras podem ser necessários ou excluídos da pasta publish. O MSBuild, que é
usado pelo Visual Studio para implantação da Web, pode ser personalizado para fazer muitas outras tarefas
durante a implantação. Para obter mais informações, consulte Perfis de publicação no Visual Studio e o livro
Usando MSBuild e o Team Foundation Build.
Você pode implantar diretamente do Visual Studio para o Serviço de Aplicativo do Azure usando o recurso
Publicar na Web ou usando o suporte ao Git interno. O Visual Studio Team Services dá suporte à
implantação contínua para o Serviço de Aplicativo do Azure.

Publicação no Azure
Confira as instruções sobre como publicar um 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 no Azure através da linha de comando.

Recursos adicionais
Para obter informações sobre como usar o Docker como um ambiente de hospedagem, consulte Hospedar
Aplicativos ASP.NET Core no Docker.
Hospedar o ASP.NET Core no Serviço de Aplicativo
do Azure
28/04/2018 • 12 min to read • Edit Online

Serviço de Aplicativo do Azure é um serviço de plataforma de computação em nuvem da Microsoft para hospedar
aplicativos Web, incluindo o ASP.NET Core.

Recursos úteis
A Documentação de Aplicativos Web do Azure é a página inicial para documentação de Azure Apps, tutoriais,
exemplos, guias de instruções e outros recursos. Dois tutoriais importantes que pertencem à hospedagem de
aplicativos ASP.NET Core são:
Início rápido: Criar um aplicativo Web ASP.NET Core no Azure
Use o Visual Studio para criar e implantar um aplicativo Web ASP.NET Core no Serviço de Aplicativo do Azure no
Windows.
Início rápido: Criar um aplicativo Web .NET Core no Serviço de Aplicativo no Linux
Use a linha de comando do Visual Studio para criar e implantar um aplicativo Web ASP.NET Core no Serviço de
Aplicativo do Azure no Linux.
Os artigos a seguir estão disponíveis na documentação do ASP.NET Core:
Publicar no Azure com o Visual Studio
Aprenda como publicar um aplicativo ASP.NET Core no Serviço de Aplicativo do Azure usando o Visual Studio.
Publicar no Azure com as ferramentas CLI
Saiba como publicar um aplicativo ASP.NET Core no Serviço de Aplicativo do Azure usando o cliente de linha de
comando do Git.
Implantação contínua no Azure com o Visual Studio e o Git
Saiba como criar um aplicativo Web ASP.NET Core usando o Visual Studio e implantá-lo no Serviço de Aplicativo
do Azure, usando o Git para implantação contínua.
Implantação contínua no Azure com o VSTS
Configurar um build de CI para um aplicativo ASP.NET Core e, em seguida, criar uma versão de implantação
contínua para o Serviço de Aplicativo do Azure.
Área restrita de aplicativo Web do Azure
Descubra as limitações de tempo de execução do Serviço de Aplicativo do Azure impostas pela plataforma de
Aplicativos do Azure.

Configuração do aplicativo
Com o ASP.NET Core 2.0 e posterior, três pacotes no metapacote Microsoft.AspNetCore.All fornecem recursos de
registro em log automático para aplicativos implantados no Serviço de Aplicativo do Azure:
Microsoft.AspNetCore.AzureAppServices.HostingStartup usa IHostingStartup para fornecer integração leve do
ASP.NET Core com o Serviço de Aplicativo do Azure. Os recursos de registro em log adicionais são fornecidos
pelo pacote Microsoft.AspNetCore.AzureAppServicesIntegration .
Microsoft.AspNetCore.AzureAppServicesIntegration executa AddAzureWebAppDiagnostics para adicionar
provedores de log de diagnósticos do Serviço de Aplicativo do Azure no pacote
Microsoft.Extensions.Logging.AzureAppServices.
Microsoft.Extensions.Logging.AzureAppServices fornece implementações de agente para dar suporte a
recursos de streaming de log e logs de diagnóstico do Serviço de Aplicativo do Azure.

Servidor proxy e cenários de balanceador de carga


O Middleware de integração do IIS, que configura Middleware de cabeçalhos encaminhados, e o módulo do
ASP.NET Core são configurados para encaminhar o esquema (HTTP/HTTPS ) e o endereço IP remoto de onde a
solicitação foi originada. Configuração adicional pode ser necessária para aplicativos hospedados atrás de
servidores proxy adicionais e balanceadores de carga. Para obter mais informações, veja Configurar o ASP.NET
Core para trabalhar com servidores proxy e balanceadores de carga.

Monitoramento e registro em log


Para monitoramento, registro em log e informações de solução de problemas, veja os seguintes artigos:
Como: monitorar aplicativos no Serviço de Aplicativo do Azure
Saiba como examinar as cotas e métricas para aplicativos e planos do Serviço de Aplicativo.
Habilitar log de diagnósticos para aplicativos Web no Serviço de Aplicativo do Azure
Descubra como habilitar e acessar o log de diagnósticos para os códigos de status HTTP, solicitações com falha e
atividade do servidor Web.
Introdução ao tratamento de erro no ASP.NET Core
Entenda abordagens comuns para o tratamento de erros em aplicativos ASP.NET Core.
Solucionar problemas no ASP.NET Core no Serviço de Aplicativo do Azure
Saiba como diagnosticar problemas com implantações do Serviço de Aplicativo do Azure com aplicativos
ASP.NET Core.
Referência de erros comuns para o Serviço de Aplicativo do Azure e o IIS com o ASP.NET Core
Consulte os erros comuns de configuração de implantação para aplicativos hospedados pelo Serviço de Aplicativo
do Azure/IIS com orientação para solução de problemas.

Anel de chave de proteção de dados e slots de implantação


Chaves de proteção de dados são mantidas na pasta %HOME%\ASP.NET\DataProtection-Keys. Essa pasta é
apoiada pelo repositório de rede e é sincronizada em todos os computadores que hospedam o aplicativo. As
chaves não são protegidas em repouso. Essa pasta fornece o anel de chave para todas as instâncias de um
aplicativo em um único slot de implantação. Slots de implantação separados, como de preparo e produção, não
compartilham um anel de chave.
Quando ocorre a troca entre os slots de implantação, nenhum sistema que usa a proteção de dados consegue
descriptografar dados armazenados usando o anel de chave dentro do slot anterior. O middleware de cookie do
ASP.NET usa a proteção de dados para proteger seus cookies. Com isso, os usuários são desconectados de um
aplicativo que usa o Middleware de Cookie padrão do ASP.NET. Para uma solução de anel de chave independente
de slot, use um provedor de anel de chave externo, como:
Armazenamento do Blobs do Azure
Azure Key Vault
Repositório SQL
Cache redis
Para obter mais informações, veja Principais provedores de armazenamento.
Implantar a versão de visualização do ASP.NET Core para o Serviço de
Aplicativo do Azure
Aplicativos de visualização do ASP.NET Core podem ser implantados para o Serviço de Aplicativo do Azure com
as seguintes abordagens:
Instalar a extensão de site de visualização
Implantar o aplicativo autocontido
Usar o Docker com aplicativos Web para contêineres
Se houver problemas ao usar a extensão de site de visualização, abra um problema no GitHub.
Instalar a extensão de site de visualização
No portal do Azure, navegue até a folha Serviço de Aplicativo.
Digite "ex" na caixa de pesquisa.
Selecione Extensões.
Selecione "Adicionar".

Selecione tempo de execução do ASP.NET Core 2.1 (x86) ou tempo de execução do ASP.NET Core 2.1
(x64).
Selecione OK. Selecione OK novamente.
Quando as operações de adição forem concluídas, a versão prévia mais recente do .NET Core 2.1 será instalada.
Verifique a instalação executando dotnet --info no console. Na folha de Serviço de Aplicativo:
Digite "con" na caixa de pesquisa.
Selecione Console.
Digite dotnet --info no console.
A imagem anterior era atual no momento em que este texto foi escrito. Você pode ver uma versão diferente.
O dotnet --info exibe o caminho para a extensão de site em que a visualização foi instalada. Ele mostra que o
aplicativo está em execução na extensão de site em vez do local padrão ProgramFiles. Se você vir ProgramFiles,
reinicie o site e execute dotnet --info .
Usar a extensão de site de visualização com um modelo do ARM
Se um modelo do ARM for usado para criar e implantar aplicativos, o tipo de recurso siteextensions poderá ser
usado para adicionar a extensão de site a um aplicativo Web. Por exemplo:

{
"type": "siteextensions",
"name": "AspNetCoreRuntime",
"apiVersion": "2015-04-01",
"location": "[resourceGroup().location]",
"properties": {
"version": "[parameters('aspnetcoreVersion')]"
},
"dependsOn": [
"[resourceId('Microsoft.Web/Sites', parameters('siteName'))]"
]
}

Implantar o aplicativo autocontido


Um aplicativo autocontido, que executa o tempo de execução de visualização na implantação, pode ser implantado.
Ao implantar um aplicativo autocontido:
O site não precisa ser preparado.
O aplicativo deve ser publicado de modo diferente do que ao publicar uma implantação dependente de
infraestrutura com o tempo de execução compartilhado e o host no servidor.
Os aplicativos autocontidos são uma opção para todos os aplicativos do ASP.NET Core.
Usar o Docker com aplicativos Web para contêineres
O Docker Hub contém as imagens de visualização do Docker 2.1 mais recentes. As imagens podem ser usadas
como uma imagem de base. Use a imagem e implante aplicativos Web para contêineres normalmente.

Recursos adicionais
Visão geral de aplicativos Web (vídeo de visão geral com 5 minutos)
Serviço de Aplicativo do Azure: o melhor lugar para hospedar seus aplicativos .NET (vídeo de visão geral com
55 minutos)
Azure Friday: experiência de diagnóstico e solução de problemas do Serviço de Aplicativo do Azure (vídeo com
12 minutos)
Visão geral de diagnóstico do Serviço de Aplicativo do Azure
O Serviço de Aplicativo do Azure no Windows Server usa o IIS (Serviços de Informações da Internet). Os tópicos
a seguir estão relacionados com a tecnologia subjacente do IIS:
Hospedar o ASP.NET Core no Windows com o IIS
Introdução ao Módulo do ASP.NET Core
Referência de configuração do Módulo do ASP.NET Core
Módulos do IIS com o ASP.NET Core
Biblioteca Microsoft TechNet: Windows Server
Publicar um aplicativo Web ASP.NET Core no
Serviço de Aplicativo do Azure usando o Visual
Studio
31/01/2018 • 6 min to read • Edit Online

Por Rick Anderson, Cesar Blum Silveira e Rachel Appel


Confira Publicar no Azure do Visual Studio para Mac se você estiver trabalhando em um Mac.

Configurar
Abra uma conta do Azure gratuita se você não tiver uma.

Criar um aplicativo Web


Na página inicial do Visual Studio, selecione Arquivo > Novo > Projeto...

Preencha a caixa de diálogo Novo Projeto:


No painel esquerdo, selecione .NET Core.
No painel central, toque em Aplicativo Web ASP.NET Core.
Selecione OK.
Na caixa de diálogo Novo Aplicativo Web ASP.NET Core:
Selecione Aplicativo Web.
Selecione Mudar Autenticação.

A caixa de diálogo Mudar Autenticação é exibida.


Selecione Contas de Usuário Individuais.
Selecione OK para retornar para o Novo Aplicativo Web do ASP.NET Core, em seguida, selecione OK
novamente.
O Visual Studio cria a solução.

Executar o aplicativo
Pressione CTRL+F5 para executar o projeto.
Teste os links Sobre e Contato.

Registrar um usuário
Selecione Registrar e registre um novo usuário. Você pode usar um endereço de email fictício. Ao
enviar, a página exibirá o seguinte erro:
"Erro interno do servidor: uma operação de banco de dados falhou ao processar a solicitação. Exceção
SQL: não é possível abrir o banco de dados. A aplicação de migrações existentes ao contexto do BD do
Aplicativo pode resolver esse problema."
Selecione Aplicar Migrações e, depois que a página for atualizada, atualize a página.

O aplicativo exibe o email usado para registrar o novo usuário e um link Fazer logout.
Implantar o aplicativo no Azure
Clique com o botão direito do mouse no projeto no Gerenciador de Soluções e selecione Publicar....

Na caixa de diálogo Publicar:


Selecione Serviço de Aplicativo do Microsoft Azure.
Selecione o ícone de engrenagem e, em seguida, Criar Perfil.
Selecione Criar Perfil.
Criar recursos do Azure
A caixa de diálogo Criar Serviço de Aplicativo será exibida:
Insira sua assinatura.
Os campos de entrada Nome do Aplicativo, Grupo de Recursos e Plano do Serviço de Aplicativo
serão populados. Você pode manter esses nomes ou alterá-los.
Selecione a guia Serviços para criar um novo banco de dados.
Selecione o ícone verde + para criar um novo Banco de Dados SQL

Selecione Novo... na caixa de diálogo Configurar Banco de Dados SQL para criar um novo banco de
dados.

A caixa de diálogo Configurar o SQL Server é exibida.


Insira um nome de usuário do administrador e a senha e, em seguida, selecione OK. Você pode manter o
Nome do Servidor padrão.

OBSERVAÇÃO
“admin” não é permitido como o nome de usuário administrador.
Selecione OK.
O Visual Studio retorna para a caixa de diálogo Criar Serviço de Aplicativo.
Selecione Criar na caixa de diálogo Criar Serviço de Aplicativo.

O Visual Studio cria o aplicativo Web e o SQL Server no Azure. Esta etapa pode levar alguns minutos. Para
obter informações sobre os recursos criados, consulte Recursos adicionais.
Quando a implantação for concluída, selecione Configurações:

Na página Configurações da caixa de diálogo Publicar:


Expanda Bancos de Dados e marque a opção Usar esta cadeia de conexão no tempo de
execução.
Expanda Migrações do Entity Framework e marque a opção Aplicar esta migração durante a
publicação.
Selecione Salvar. O Visual Studio retorna para a caixa de diálogo Publicar.
Clique em Publicar. O Visual Studio publica o aplicativo no Azure. Quando a implantação for concluída, o
aplicativo será aberto em um navegador.
Testar o aplicativo no Azure
Teste os links Sobre e Contato
Registrar um novo usuário
Atualizar o aplicativo
Edite a página do Razor Pages/About.cshtml e altere seu conteúdo. Por exemplo, você pode modificar o
parágrafo para indicar “Olá, ASP.NET Core!”: [!code-htmlAbout]
Clique com o botão direito do mouse no projeto e selecione Publicar... novamente.
Depois que o aplicativo for publicado, verifique se as alterações feitas estão disponíveis no Azure.
Limpar
Quando você concluir o teste do aplicativo, acesse o portal do Azure e exclua o aplicativo.
Selecione Grupos de recursos e, em seguida, selecione o grupo de recursos criado.

Na página Grupos de recursos, selecione Excluir.


Insira o nome do grupo de recursos e selecione Excluir. O aplicativo e todos os outros recursos criados
neste tutorial agora foram excluídos do Azure.
Próximas etapas
Implantação contínua no Azure com o Visual Studio e o Git

Recursos adicionais
Serviço de Aplicativo do Azure
Grupo de recursos do Azure
Banco de Dados SQL do Azure
Implante um aplicativo ASP.NET Core para o
Serviço de Aplicativo do Azure da linha de
comando
02/02/2018 • 5 min to read • Edit Online

Por Cam Soper


Este tutorial mostrará como criar e implantar um aplicativo ASP.NET Core para o Serviço de Aplicativo do
Microsoft Azure usando as ferramentas de linha de comando. No fim, você terá um aplicativo Web criado em
ASP.NET MVC Core hospedado como um Aplicativo Web do Serviço de Aplicativo do Azure. Este tutorial foi
criado usando as ferramentas de linha de comando do Windows, mas também pode ser aplicado aos
ambientes do macOS e Linux.
Neste tutorial, você aprenderá como:
Crie um site do Serviço de Aplicativo do Azure usando a CLI do Azure
Implante um aplicativo ASP.NET Core para o Serviço de Aplicativo do Azure usando a ferramenta de linha
de comando Git

Pré-requisitos
Para concluir este tutorial, você precisará de:
Uma assinatura do Microsoft Azure
.NET Core
Cliente de linha de comando Git

Criar um aplicativo Web


Crie um novo diretório para o aplicativo Web, crie um novo aplicativo MVC do ASP.NET Core e execute o site
localmente.
Windows
Outros

REM Create a new ASP.NET Core MVC application


dotnet new razor -o MyApplication

REM Change to the new directory that was just created


cd MyApplication

REM Run the application


dotnet run
Teste o aplicativo navegando até http://localhost:5000.

Crie a instância do Serviço de Aplicativo do Azure


Usando o Azure Cloud Shell, crie um grupo de recursos, o plano do Serviço de Aplicativo e o aplicativo Web do
Serviço de Aplicativo.
# Generate a unique Web App name
let randomNum=$RANDOM*$RANDOM
webappname=tutorialApp$randomNum

# Create the DotNetAzureTutorial resource group


az group create --name DotNetAzureTutorial --location EastUS

# Create an App Service plan.


az appservice plan create --name $webappname --resource-group DotNetAzureTutorial --sku FREE

# Create the Web App


az webapp create --name $webappname --resource-group DotNetAzureTutorial --plan $webappname

Antes da implantação, defina as credenciais de implantação a nível de conta usando o seguinte comando:

az webapp deployment user set --user-name <desired user name> --password <desired password>

Uma URL de implantação é necessária para implantar o aplicativo usando o Git. Recupere a URL como esta.

az webapp deployment source config-local-git -n $webappname -g DotNetAzureTutorial --query [url] -o tsv

Observe a URL exibida terminada em .git . Ela é usada na próxima etapa.

Implantar o aplicativo usando o Git


Você está pronto para implantar a partir da sua máquina local usando o Git.

OBSERVAÇÃO
É seguro ignorar quaisquer alertas do Git sobre os términos das linhas.

Windows
Outros

REM Initialize the local Git repository


git init

REM Add the contents of the working directory to the repo


git add --all

REM Commit the changes to the local repo


git commit -a -m "Initial commit"

REM Add the URL as a Git remote repository


git remote add azure <THE GIT URL YOU NOTED EARLIER>

REM Push the local repository to the remote


git push azure master

O Git solicitará as credenciais de implantação que foram definidas anteriormente. Depois de autenticar, o
aplicativo será enviado para o local remoto, compilado e implantado.
Testar o aplicativo
Teste o aplicativo navegando até https://<web app name>.azurewebsites.net . Para exibir o endereço no Shell da
Nuvem (ou CLI do Azure), use o seguinte:

az webapp show -n $webappname -g DotNetAzureTutorial --query defaultHostName -o tsv

Limpar
Quando concluir o teste do aplicativo e a inspeção do código e recursos, exclua o aplicativo Web e plano
removendo o grupo de recursos.

az group delete -n DotNetAzureTutorial


Próximas etapas
Neste tutorial, você aprendeu como:
Crie um site do Serviço de Aplicativo do Azure usando a CLI do Azure
Implante um aplicativo ASP.NET Core para o Serviço de Aplicativo do Azure usando a ferramenta de linha
de comando Git
A seguir, você aprenderá a usar a linha de comando para implantar um aplicativo Web existente que usa o
CosmosDB.
Implantar no Azure, na linha de comando com o .NET Core
Implantação contínua para o Azure com o Visual
Studio e o Git com ASP.NET Core
10/04/2018 • 13 min to read • Edit Online

Por Erik Reitan

IMPORTANTE
Aviso para usando versões de visualização do ASP.NET Core 2.1
Consulte versão de visualização de implantar o ASP.NET Core para o serviço de aplicativo do Azure.

Este tutorial mostra como criar um aplicativo web do ASP.NET Core usando o Visual Studio e implantá-lo do
Visual Studio para o serviço de aplicativo do Azure usando a implantação contínua.
Consulte também Usar o VSTS para criar e publicar um Aplicativo Web do Azure com a implantação contínua,
que mostra como configurar um fluxo de trabalho de CD (entrega contínua) para o Serviço de Aplicativo do
Azure usando o Visual Studio Team Services. Entrega contínua do Azure no Team Services simplifica a
configuração de um pipeline de implantação robusta para publicar atualizações para aplicativos hospedados no
serviço de aplicativo do Azure. O pipeline pode ser configurado no portal do Azure para criar, executar testes,
implantar em um slot de preparo e, em seguida, implantar na produção.

OBSERVAÇÃO
Para concluir este tutorial, é necessária uma conta do Microsoft Azure. Para obter uma conta, ativar os benefícios de
assinante do MSDN ou inscrever-se para uma avaliação gratuita.

Pré-requisitos
Este tutorial pressupõe que o seguinte software está instalado:
Visual Studio
.NET Core SDK 2.0 or later
Git para Windows

Criar um aplicativo Web ASP.NET Core


1. Inicie o Visual Studio.
2. No menu Arquivo, selecione Novo > Projeto.
3. Selecione o modelo de projeto Aplicativo Web ASP.NET Core. Ele será exibido em Instalado >
Modelos > Visual C# > .NET Core. Nomeie o projeto SampleWebAppDemo . Selecione o criar novo
repositório do Git opção e clique em Okey.
4. Na caixa de diálogo Novo Projeto ASP.NET Core, selecione o modelo Vazio do ASP.NET Core e clique
em OK.

OBSERVAÇÃO
A versão mais recente do .NET Core é 2.0.

Executando o aplicativo Web localmente


1. Depois que o Visual Studio concluir a criação do aplicativo, execute o aplicativo selecionando Depurar >
Iniciar Depuração. Como alternativa, pressione F5.
Talvez seja necessário alguns instantes para inicializar o Visual Studio e o novo aplicativo. Quando ele for
concluído, o navegador mostra o aplicativo em execução.

2. Depois de revisar o aplicativo Web em execução, feche o navegador e selecione o ícone de "Parar
depuração" na barra de ferramentas do Visual Studio para interromper o aplicativo.

Criar um aplicativo Web no Portal do Azure


As seguintes etapas criam um aplicativo web no Portal do Azure:
1. Faça logon na Portal do Azure.
2. Selecione novo na parte superior esquerda da interface do portal.
3. Selecione Web + móvel > aplicativo da Web.
4. Na folha Aplicativo Web, insira um valor exclusivo para o Nome do Serviço de Aplicativo.
OBSERVAÇÃO
O nome do serviço de aplicativo nome deve ser exclusivo. O portal impõe essa regra quando o nome for
fornecido. Se fornecer um valor diferente, substitua esse valor para cada ocorrência do SampleWebAppDemo
neste tutorial.

Também na folha Aplicativo Web, selecione um Plano do Serviço de Aplicativo/Localização


existente ou crie um novo. Se criar um novo plano, selecione a camada de preços, local e outras opções.
Para obter mais informações sobre planos de serviço de aplicativo, consulte visão geral detalhada de
planos de serviço de aplicativo do Azure.
5. Selecione Criar. Azure provisionará e iniciar o aplicativo da web.
Habilitar a publicação do Git no novo aplicativo Web
Git é um sistema de controle de versão distribuídos que pode ser usado para implantar um aplicativo da web do
serviço de aplicativo do Azure. Código de aplicativo da Web é armazenado em um repositório Git local e o código
é implantado no Azure por push para um repositório remoto.
1. Faça logon na Portal do Azure.
2. Selecione serviços de aplicativos para exibir uma lista dos serviços de aplicativo associado à assinatura
do Azure.
3. Selecione o aplicativo web criado na seção anterior deste tutorial.
4. Na folha Implantação, selecione Opções de implantação > Escolher Origem > Repositório Git
Local.

5. Selecione OK.
6. Se as credenciais de implantação para publicar um aplicativo web ou outro aplicativo de serviço de
aplicativo ainda não foram configuradas anteriormente, configurá-los agora:
Selecione configurações > credenciais de implantação. O definir credenciais de implantação
folha é exibida.
Crie um nome de usuário e uma senha. Salve a senha para uso posterior ao configurar o Git.
Selecione Salvar.
7. No aplicativo Web folha, selecione configurações > propriedades. A URL do repositório Git remoto
para implantar é mostrada em URL do GIT.
8. Copie o valor URL do GIT para uso posterior no tutorial.

Publicar o aplicativo web para o serviço de aplicativo do Azure


Nesta seção, crie um repositório Git local usando Visual Studio e envio desse repositório para o Azure para
implantar o aplicativo web. As etapas envolvidas incluem as seguintes:
Adicione a configuração do repositório remoto usando o valor da URL de GIT, para que o repositório local
pode ser implantado no Azure.
Confirme as alterações do projeto.
Enviar por push as alterações do projeto do repositório local para o repositório remoto no Azure.
1. No Gerenciador de Soluções, clique com o botão direito do mouse em “SampleWebAppDemo” da
Solução e selecione Confirmar. O Team Explorer é exibido.

2. No Team Explorer, selecione a Página Inicial (ícone da página inicial) > Configurações >
Configurações do Repositório.
3. No programáveis seção o configurações do repositório, selecione adicionar. O adicionar remoto
caixa de diálogo é exibida.
4. Defina o Nome do remoto como Azure-SampleApp.
5. Definir o valor de buscar para o URL de Git que copiados do Azure anteriormente neste tutorial. Observe
que essa é a URL que termina com .git.

OBSERVAÇÃO
Como alternativa, especifique o repositório remoto do janela comando abrindo o janela de comando, alterar
para o diretório do projeto e inserir o comando. Exemplo:
git remote add Azure-SampleApp https://me@sampleapp.scm.azurewebsites.net:443/SampleApp.git
6. Selecione a Página Inicial (ícone da página inicial) > Configurações > Configurações Globais.
Confirme se o nome e endereço de email estão definidas. Selecione atualização se necessário.
7. Selecione Página Inicial > Alterações para retornar à exibição Alterações.
8. Insira uma mensagem de confirmação, como inicial Push #1 e selecione confirmação. Essa ação cria um
confirmação localmente.

OBSERVAÇÃO
Como alternativa, confirmação muda do janela comando abrindo o janela de comando, alterar para o diretório
do projeto e digitar os comandos do git. Exemplo:
git add .

git commit -am "Initial Push #1"

9. Selecione Página Inicial > Sincronização > Ações > Abrir Prompt de Comando. Abre o prompt de
comando para o diretório do projeto.
10. Insira o seguinte comando na janela de comando:
git push -u Azure-SampleApp master

11. Insira o Azure credenciais de implantação senha criados anteriormente no Azure.


Esse comando inicia o processo de envio por push os arquivos do projeto local para o Azure. A saída do
comando acima termina com uma mensagem de que a implantação foi bem-sucedida.

remote: Finished successfully.


remote: Running post deployment command(s)...
remote: Deployment successful.
To https://username@samplewebappdemo01.scm.azurewebsites.net:443/SampleWebAppDemo01.git
* [new branch] master -> master
Branch master set up to track remote branch master from Azure-SampleApp.
OBSERVAÇÃO
Se a colaboração no projeto é necessária, considere a possibilidade de envio por push para GitHub antes de enviar
por push para o Azure.

Verificar a implantação ativa


Verifique se a transferência de aplicativos web do ambiente local para o Azure é bem-sucedida.
No Portal do Azure, selecione o aplicativo web. Selecione implantação > opções de implantação.

Executar o aplicativo no Azure


Agora que o aplicativo web é implantado no Azure, execute o aplicativo.
Isso pode ser feito de duas maneiras:
No Portal do Azure, localize a folha do aplicativo web para o aplicativo web. Selecione procurar para exibir o
aplicativo no navegador padrão.
Abra um navegador e digite a URL do aplicativo web. Exemplo: http://SampleWebAppDemo.azurewebsites.net

Atualizar o aplicativo web e publicar novamente


Depois de fazer alterações no código local, republicar:
1. No Gerenciador de Soluções do Visual Studio, abra o arquivo Startup.cs.
2. No método Configure , modifique o método Response.WriteAsync para que ele seja exibido da seguinte
maneira:

await context.Response.WriteAsync("Hello World! Deploy to Azure.");

3. Salvar as alterações em Startup.cs.


4. No Gerenciador de Soluções, clique com o botão direito do mouse em “SampleWebAppDemo” da
Solução e selecione Confirmar. O Team Explorer é exibido.
5. Insira uma mensagem de confirmação, como Update #2 .
6. Pressione o botão Confirmar para confirmar as alterações do projeto.
7. Selecione Página Inicial > Sincronização > Ações > Enviar por Push.

OBSERVAÇÃO
Como alternativa, enviar por push as alterações do janela comando abrindo o janela de comando, alterar para o
diretório do projeto e inserir um comando do git. Exemplo:
git push -u Azure-SampleApp master

Exibir o aplicativo Web atualizado no Azure


Exibir o aplicativo web atualizadas selecionando procurar na folha de aplicativo web no Portal do Azure ou
abrindo um navegador e digitando a URL do aplicativo web. Exemplo: http://SampleWebAppDemo.azurewebsites.net

Recursos adicionais
Usar o VSTS para criar e publicar um aplicativo Web do Azure com implantação contínua
Kudu do projeto
Solucionar problemas de ASP.NET Core no serviço
de aplicativo do Azure
10/04/2018 • 18 min to read • Edit Online

Por Luke Latham

IMPORTANTE
Aviso para usando versões de visualização do ASP.NET Core 2.1
Consulte versão de visualização de implantar o ASP.NET Core para o serviço de aplicativo do Azure.

Este artigo fornece instruções sobre como diagnosticar uma ASP.NET Core problema de inicialização do aplicativo
usando ferramentas de diagnóstico do serviço de aplicativo do Azure. Para avisos de solução de problemas
adicionais, consulte visão geral do serviço de aplicativo do Azure diagnostics e como: monitorar aplicativos no
serviço de aplicativo do Azure na documentação do Azure.

Erros de inicialização do aplicativo


502.5 falha de processo
O processo de trabalho falha. O aplicativo não foi iniciada.
O ASP.NET Core módulo tentativas de iniciar o processo de trabalho, mas ele não pode ser iniciado. Examinar o
Log de eventos do aplicativo geralmente ajuda a solucionar esse tipo de problema. Acessar o log é explicado no
Log de eventos do aplicativo seção.
O 502.5 falha no processo página de erro é retornada quando um aplicativo configurado incorretamente faz com
que a falha do processo de trabalho:

Erro de servidor interno 500


O aplicativo é iniciado, mas um erro impede que o servidor de atender à solicitação.
Esse erro ocorre no código do aplicativo durante a inicialização ou durante a criação de uma resposta. A resposta
não poderá conter nenhum conteúdo ou a resposta pode ser exibido como um 500 Erro interno do servidor no
navegador. O Log de eventos do aplicativo geralmente indica que o aplicativo é iniciado normalmente. Da
perspectiva do servidor, que está correta. O aplicativo foi iniciado, mas não pode gerar uma resposta válida.
Execute o aplicativo no console do Kudu ou habilite o log do módulo do ASP.NET Core stdout para solucionar o
problema.
Redefinição de Conexão
Se um erro ocorrer após os cabeçalhos são enviados, é muito tarde para o servidor enviar um 500 Erro interno
do servidor quando ocorre um erro. Isso geralmente acontece quando ocorre um erro durante a serialização de
objetos complexos por uma resposta. Esse tipo de erro é exibida como uma redefinição de conexão erro no
cliente. Log de aplicativo pode ajudar a solucionar esses tipos de erros.

Limites de inicialização padrão


O módulo de núcleo do ASP.NET está configurado com um padrão startupTimeLimit de 120 segundos. Quando
deixada no valor padrão, um aplicativo pode levar até dois minutos para ser iniciado antes do módulo faz uma
falha do processo. Para obter informações sobre como configurar o módulo, consulte atributos do elemento
aspNetCore.

Solucionar problemas de inicialização do aplicativo


Log de eventos do aplicativo
Para acessar o Log de eventos do aplicativo, use o diagnosticar e resolver problemas folha no portal do Azure:
1. No portal do Azure, abra a folha do aplicativo no serviços de aplicativos folha.
2. Selecione o diagnosticar e resolver problemas folha.
3. Em Selecionar categoria de problema, selecione o aplicativo Web para baixo botão.
4. Em soluções sugeridas, abra o painel para abrir Logs de eventos do aplicativo. Selecione o abrir Logs de
eventos de aplicativo botão.
5. Examine o erro mais recente fornecido pelo IIS AspNetCoreModule no fonte coluna.
Uma alternativa ao uso de diagnosticar e resolver problemas folha é examinar o arquivo de Log de eventos do
aplicativo diretamente usando Kudu:
1. Selecione o ferramentas avançadas de folha no ferramentas de desenvolvimento área. Selecione o vá→
botão. O console do Kudu é aberto em uma janela ou nova guia do navegador.
2. Usando a barra de navegação na parte superior da página, abra console de depuração e selecione CMD.
3. Abra o LogFiles pasta.
4. Selecione o ícone de lápis ao lado de eventlog.xml arquivo.
5. Examine o log. Role até o final do log para ver os eventos mais recentes.
Execute o aplicativo no console do Kudu
Muitos erros de inicialização não produzem informações úteis no Log de eventos do aplicativo. Você pode
executar o aplicativo no Kudu Console remoto de execução para descobrir o erro:
1. Selecione o ferramentas avançadas de folha no ferramentas de desenvolvimento área. Selecione o vá→
botão. O console do Kudu é aberto em uma janela ou nova guia do navegador.
2. Usando a barra de navegação na parte superior da página, abra console de depuração e selecione CMD.
3. Abra as pastas no caminho site > wwwroot.
4. No console do, execute o aplicativo com a execução de assembly do aplicativo.
Se o aplicativo for um implantação dependentes de framework, executar o assembly do aplicativo com
dotnet.exe. No comando a seguir, substitua o nome do assembly do aplicativo para <assembly_name> :
dotnet .\<assembly_name>.dll
Se o aplicativo for um implantação autossuficiente, execute o aplicativo do executável. No comando a
seguir, substitua o nome do assembly do aplicativo para <assembly_name> : <assembly_name>.exe
5. A saída do aplicativo, mostrando os erros do console está conectado ao console Kudu.
Log de stdout de módulo principal do ASP.NET
O módulo do ASP.NET Core stdout geralmente registra mensagens de erro úteis não encontradas no Log de
eventos do aplicativo. Para habilitar e exibir logs de stdout:
1. Navegue até o diagnosticar e resolver problemas folha no portal do Azure.
2. Em Selecionar categoria de problema, selecione o aplicativo Web para baixo botão.
3. Em soluções sugeridas > habilitar o redirecionamento de Log Stdout, selecione o botão de abrir o
Console do Kudu para editar o Web. config.
4. No Kudu Console diagnóstico, abra as pastas no caminho site > wwwroot. Role para baixo para revelar o
Web. config arquivo na parte inferior da lista.
5. Clique no ícone de lápis ao lado de Web. config arquivo.
6. Definir stdoutLogEnabled para true e altere o stdoutLogFile caminho: \\?\%home%\LogFiles\stdout .
7. Selecione salvar para salvar o documento atualizado Web. config arquivo.
8. Fazer uma solicitação para o aplicativo.
9. Retorne ao portal do Azure. Selecione o ferramentas avançadas de folha no ferramentas de
desenvolvimento área. Selecione o vá→ botão. O console do Kudu é aberto em uma janela ou nova guia do
navegador.
10. Usando a barra de navegação na parte superior da página, abra console de depuração e selecione CMD.
11. Selecione o LogFiles pasta.
12. Inspecione o modificadas coluna e selecione o ícone de lápis para editar o stdout, faça logon com a data da
última modificação.
13. Quando o arquivo de log é aberto, o erro é exibido.
Importante! Desabilite stdout registro em log quando o problema for solucionado.
1. No Kudu Console diagnóstico, retorne para o caminho site > wwwroot para revelar o Web. config arquivo.
Abra o Web. config arquivo novamente, selecionando o ícone de lápis.
2. Definir stdoutLogEnabled para false .
3. Selecione salvar para salvar o arquivo.

AVISO
Falha ao desabilitar o log de stdout pode levar a falhas de aplicativo ou servidor. Não há limites para o tamanho do arquivo
de log ou para o número de arquivos de log criados. Somente use stdout log para solucionar problemas de inicialização do
aplicativo.
Para geral log em um aplicativo do ASP.NET Core após a inicialização, use uma biblioteca de registro em log que limita o
tamanho do arquivo de log e gira logs. Para obter mais informações, consulte provedores de log de terceiros.

Erros comuns de inicialização


Consulte o referência de erros comuns do ASP.NET Core. A maioria dos problemas comuns que impedem a
inicialização do aplicativo é abordada no tópico de referência.

Aplicativo lento ou deslocado


Quando um aplicativo responde lentamente ou trava em uma solicitação, consulte solucionar problemas de
desempenho de aplicativo web lenta no serviço de aplicativo do Azure para orientação de depuração.
Depuração remota
Consulte os tópicos a seguir:
Seção de aplicativos da web de solucionar problemas de um aplicativo web no serviço de aplicativo do Azure
usando o Visual Studio de depuração remota (documentação do Azure)
Remota de depuração ASP.NET Core no IIS no Azure no Visual Studio de 2017 (documentação do Visual
Studio)

Informações do aplicativo
Application Insights fornece a telemetria de aplicativos hospedados no serviço de aplicativo do Azure, incluindo o
log de erros e recursos de relatório. Application Insights só pode relatar erros ocorridos depois que o aplicativo é
iniciado quando recursos de log do aplicativo se tornar disponíveis. Para obter mais informações, consulte
Application Insights para ASP.NET Core.

Folhas de monitoramentos
Folhas de monitoramentos fornecem uma alternativa experiência para os métodos descritos anteriormente no
tópico de solução de problemas. Essas folhas podem ser usadas para diagnosticar erros 500.
Confirme que as extensões de núcleo do ASP.NET estão instaladas. Se as extensões não estiverem instaladas,
instale-os manualmente:
1. No ferramentas de desenvolvimento seção de folha, selecione o extensões folha.
2. O ASP.NET Core extensões devem aparecer na lista.
3. Se as extensões não estão instaladas, selecione o adicionar botão.
4. Escolha o ASP.NET Core extensões da lista.
5. Selecione Okey para aceitar os termos legais.
6. Selecione Okey no Adicionar extensão folha.
7. Uma mensagem pop-up informativa indica quando as extensões são instaladas com êxito.
Se o log de stdout não está habilitado, siga estas etapas:
1. No portal do Azure, selecione o ferramentas avançadas de folha no ferramentas de desenvolvimento
área. Selecione o vá→ botão. O console do Kudu é aberto em uma janela ou nova guia do navegador.
2. Usando a barra de navegação na parte superior da página, abra console de depuração e selecione CMD.
3. Abra as pastas no caminho site > wwwroot e role para baixo para revelar o Web. config arquivo na parte
inferior da lista.
4. Clique no ícone de lápis ao lado de Web. config arquivo.
5. Definir stdoutLogEnabled para true e altere o stdoutLogFile caminho: \\?\%home%\LogFiles\stdout .
6. Selecione salvar para salvar o documento atualizado Web. config arquivo.
Vá para ativar o log de diagnóstico:
1. No portal do Azure, selecione o logs de diagnóstico folha.
2. Selecione o na alternar (sistema de arquivos) de log de aplicativo e mensagens de erro detalhadas.
Selecione o salvar botão na parte superior da folha.
3. Para incluir o rastreamento de solicitação com falha, também conhecido como registro em log de falha na
solicitação evento buffer (FREB ), selecione o na alternar o rastreamento de solicitação com falha.
4. Selecione o fluxo de Log folha, que é listada imediatamente sob o logs de diagnóstico folha no portal.
5. Fazer uma solicitação para o aplicativo.
6. Dentro dos dados de fluxo de log, a causa do erro é indicada.
Importante! Certifique-se de desabilitar stdout registro em log quando o problema for solucionado. Consulte as
instruções de log do módulo do ASP.NET Core stdout seção.
Para exibir os logs de rastreamento de solicitação com falha (logs FREB ):
1. Navegue até o diagnosticar e resolver problemas folha no portal do Azure.
2. Selecione falha os Logs de rastreamento de solicitação do ferramentas de suporte área de barra lateral.
Consulte seção o habilite o log de diagnóstico para aplicativos web no tópico de serviço de aplicativo do Azure de
rastreamentos de solicitação com falha e perguntas frequentes do desempenho do aplicativo para aplicativos Web
no Azure: como ativar o rastreamento de solicitação com falha? Para obter mais informações.
Para obter mais informações, consulte habilitar o log de diagnóstico para aplicativos web no serviço de aplicativo
do Azure.

AVISO
Falha ao desabilitar o log de stdout pode levar a falhas de aplicativo ou servidor. Não há limites para o tamanho do arquivo
de log ou para o número de arquivos de log criados.
Para log de rotina no aplicativo do ASP.NET Core, use uma biblioteca de registro em log que limita o tamanho do arquivo de
log e gira logs. Para obter mais informações, consulte provedores de log de terceiros.

Recursos adicionais
Introdução ao tratamento de erro no ASP.NET Core
Referência de erros comuns para o Serviço de Aplicativo do Azure e o IIS com o ASP.NET Core
Solucionar problemas de um aplicativo web no serviço de aplicativo do Azure usando o Visual Studio
Solucionar problemas de erros HTTP de "502 gateway incorreto" e "503 Serviço indisponível" em seus
aplicativos web do Azure
Solucionar problemas de desempenho do aplicativo web lenta no serviço de aplicativo do Azure
Perguntas frequentes do desempenho do aplicativo para aplicativos Web no Azure
Área restrita do aplicativo Web do Azure (limitações de execução de tempo de execução do serviço de
aplicativo)
Azure Friday: experiência de diagnóstico e solução de problemas do Serviço de Aplicativo do Azure (vídeo com
12 minutos)
Hospedar o ASP.NET Core no Windows com o IIS
28/04/2018 • 37 min to read • Edit Online

Por Luke Latham e Rick Anderson

Sistemas operacionais com suporte


Há suporte para os seguintes sistemas operacionais:
Windows 7 ou posterior
Windows Server 2008 R2 ou posterior
O servidor HTTP.sys (anteriormente chamado de WebListener) não funcionará em uma configuração de proxy
reverso com IIS. Use o servidor Kestrel.

Configuração do aplicativo
Habilitar os componentes de IISIntegration
ASP.NET Core 2.x
ASP.NET Core 1.x
Um típico Program.cs chama CreateDefaultBuilder para começar a configurar um host. CreateDefaultBuilder
configura Kestrel como o servidor Web e habilita a integração IIS configurando o caminho base e a porta para
o módulo do ASP.NET Core:

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


WebHost.CreateDefaultBuilder(args)
...

O Módulo do ASP.NET Core gera uma porta dinâmica a ser atribuída ao processo de back-end. O método
UseIISIntegration seleciona a porta dinâmica e configura o Kestrel para escutar em
http://localhost:{dynamicPort}/ . Isso substitui outras configurações de URL, como chamadas a UseUrls ou à
API de Escuta do Kestrel. Portanto, as chamadas a UseUrls ou à API Listen do Kestrel não são necessárias ao
usar o módulo. Se UseUrls ou Listen for chamado, o Kestrel escutará na porta especificada durante a
execução do aplicativo sem o IIS.
Para obter mais informações sobre hospedagem, consulte Hospedagem em ASP.NET Core.
Opções do IIS
Para configurar opções de IIS, inclua uma configuração de serviço para IISOptions em ConfigureServices. No
exemplo a seguir, o encaminhamento de certificados do cliente para o aplicativo para preencher
HttpContext.Connection.ClientCertificate está desabilitado:

services.Configure<IISOptions>(options =>
{
options.ForwardClientCertificate = false;
});
OPÇÃO PADRÃO CONFIGURAÇÃO

AutomaticAuthentication true Se true , o middleware de integração


do IIS define o HttpContext.User
autenticado pela Autenticação do
Windows. Se false , o middleware
fornecerá apenas uma identidade para
HttpContext.User e responderá a
desafios quando explicitamente
solicitado pelo
AuthenticationScheme . A
autenticação do Windows deve estar
habilitada no IIS para que o
AutomaticAuthentication funcione.
Saiba mais no tópico Autenticação do
Windows.

AuthenticationDisplayName null Configura o nome de exibição


mostrado aos usuários em páginas de
logon.

ForwardClientCertificate true Se true e o cabeçalho da solicitação


MS-ASPNETCORE-CLIENTCERT
estiverem presentes, o
HttpContext.Connection.ClientCertificate
será populado.

Servidor proxy e cenários de balanceador de carga


O Middleware de integração do IIS, que configura Middleware de cabeçalhos encaminhados, e o módulo do
ASP.NET Core são configurados para encaminhar o esquema (HTTP/HTTPS ) e o endereço IP remoto de onde a
solicitação foi originada. Configuração adicional pode ser necessária para aplicativos hospedados atrás de
servidores proxy adicionais e balanceadores de carga. Para obter mais informações, veja Configurar o ASP.NET
Core para trabalhar com servidores proxy e balanceadores de carga.
Arquivo web.config
O arquivo web.config configura o Módulo do ASP.NET Core. A criação, transformação e publicação do
web.config é gerenciada pelo SDK Web do .NET Core ( Microsoft.NET.Sdk.Web ). O SDK é definido na parte
superior do arquivo de projeto:

<Project Sdk="Microsoft.NET.Sdk.Web">

Se um arquivo web.config não estiver presente no projeto, ele será criado com o processPath e os argumentos
corretos para configurar o Módulo do ASP.NET Core e será transferido para o resultado publicado.
Se um arquivo web.config estiver presente no projeto, ele será transformado com o processPath e os
argumentos corretos para configurar o Módulo do ASP.NET Core e será movido para o resultado publicado. A
transformação não altera as definições de configuração do IIS no arquivo.
O arquivo web.config pode fornecer configurações adicionais do IIS que controlam módulos ativos do IIS. Para
saber mais sobre os módulos do IIS que podem processar solicitações com aplicativos do ASP.NET Core, veja o
tópico Módulos do IIS.
Para impedir que o SDK Web transforme o arquivo web.config, use a propriedade
<IsTransformWebConfigDisabled> no arquivo do projeto:
<PropertyGroup>
<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
</PropertyGroup>

Ao impedir que o SDK Web transforme o arquivo, o processPath e os argumentos devem ser definidos
manualmente pelo desenvolvedor. Para obter mais informações, consulte Referência de configuração do
módulo do ASP.NET Core.
Local do arquivo web.config
Os aplicativos ASP.NET Core são hospedados em um proxy reverso entre o IIS e o servidor Kestrel. Para criar
um proxy reverso, o arquivo web.config deve estar presente no caminho raiz do conteúdo (geralmente, o
aplicativo base do caminho) do aplicativo implantado. Esse é o mesmo local que o caminho físico do site
fornecido ao IIS. O arquivo web.config é necessário na raiz do aplicativo para habilitar a publicação de vários
aplicativos usando a Implantação da Web.
Existem arquivos confidenciais no caminho físico do aplicativo, como <assembly>.runtimeconfig.json,
<assembly>.xml (comentários da Documentação XML ) e <assembly>.deps.json. Quando o arquivo web.config
estiver presente e o site for iniciado normalmente, o IIS não servirá esses arquivos confidenciais se eles forem
solicitados. Se o arquivo web.config estiver ausente, nomeado incorretamente ou se não for possível configurar
o site para inicialização normal, o IIS poderá servir arquivos confidenciais publicamente.
O arquivo web.config deve estar presente na implantação em todos os momentos, nomeado
corretamente e ser capaz de configurar o site para inicialização normal. Nunca remova o arquivo
web.config de uma implantação de produção.

Configuração do IIS
Sistemas operacionais do Windows Server
Habilite a função Servidor Web (IIS ) e estabeleça serviços de função.
1. Use o assistente Adicionar Funções e Recursos por meio do menu Gerenciar ou do link no
Gerenciador do Servidor. Na etapa Funções de Servidor, marque a caixa de Servidor Web (IIS ).
2. Após a etapa Recursos, a etapa Serviços de função é carregada para o servidor Web (IIS ). Selecione
os serviços de função do IIS desejados ou aceite os serviços de função padrão fornecidos.

Autenticação do Windows (opcional)


Para habilitar a Autenticação do Windows, expanda os nós a seguir: Servidor Web > Segurança.
Selecione o recurso Autenticação do Windows. Saiba mais em Autenticação do Windows
<windowsAuthentication> e Configurar autenticação do Windows.
WebSockets (opcional)
O WebSockets é compatível com o ASP.NET Core 1.1 ou posterior. Para habilitar o WebSockets,
expanda os nós a seguir: Servidor Web > Desenvolvimento de Aplicativos. Selecione o recurso
Protocolo WebSocket. Para obter mais informações, consulte WebSockets.
3. Continue para a etapa Confirmação para instalar os serviços e a função de servidor Web. Um comando
server/IIS restart não será necessário após a instalação da função Servidor Web (IIS ).
Sistemas operacionais Windows de área de trabalho
Habilite o Console de Gerenciamento do IIS e os Serviços na World Wide Web.
1. Navegue para Painel de Controle > Programas > Programas e Recursos > Ativar ou desativar
recursos do Windows (lado esquerdo da tela).
2. Abra o nó Serviços de Informações da Internet. Abra o nó Ferramentas de Gerenciamento da
Web.
3. Marque a caixa de Console de Gerenciamento do IIS.
4. Marque a caixa de Serviços na World Wide Web.
5. Aceite os recursos padrão dos Serviços na World Wide Web ou personalize os recursos do IIS.
Autenticação do Windows (opcional)
Para habilitar a Autenticação do Windows, expanda os nós a seguir: Serviços World Wide Web >
Segurança. Selecione o recurso Autenticação do Windows. Saiba mais em Autenticação do Windows
<windowsAuthentication> e Configurar autenticação do Windows.
WebSockets (opcional)
O WebSockets é compatível com o ASP.NET Core 1.1 ou posterior. Para habilitar o WebSockets,
expanda os nós a seguir: Serviços World Wide Web > Recursos de Desenvolvimento de
Aplicativos. Selecione o recurso Protocolo WebSocket. Para obter mais informações, consulte
WebSockets.
6. Se a instalação do IIS exigir uma reinicialização, reinicie o sistema.

Instalar o pacote de hospedagem do .NET Core


1. Instale o pacote de hospedagem do .NET Core no sistema de hospedagem. O pacote instala o Tempo de
Execução .NET Core, a Biblioteca do .NET Core e o Módulo do ASP.NET Core. O módulo cria o proxy
reverso entre o IIS e o servidor Kestrel. Se o sistema não tiver uma conexão com a Internet, obtenha e
instale os Pacotes redistribuíveis do Microsoft Visual C++ 2015 antes de instalar o pacote de
hospedagem do .NET Core.
a. Navegue até a página Todos os downloads do .NET.
b. Selecione o tempo de execução do .NET Core sem visualização mais recente na lista (.NET Core >
Tempo de Execução > Tempo de Execução do .NET Core x.y.z). A menos que você pretenda
trabalhar com softwares de visualização, evite tempos de execução com a palavra "visualização" no
texto de link.
c. Na página de download de tempo de execução do .NET Core no Windows, selecione o link
Instalador de pacote de hospedagem para baixar o pacote de hospedagem do .NET Core.
Importante! Se o pacote de hospedagem for instalado antes do IIS, a instalação do pacote deverá ser
reparada. Execute o instalador do pacote de hospedagem novamente depois de instalar o IIS.
Para impedir que o instalador instale pacotes x86 em sistemas operacionais x64, execute o instalador em
um prompt de comando de administrador com a opção OPT_NO_X86=1 .
2. Reinicie o sistema ou execute net stop was /y seguido por net start w3svc em um prompt de
comando. A reinicialização do IIS identifica uma alteração no caminho do sistema realizada pelo
instalador.

OBSERVAÇÃO
Para obter informações sobre a Configuração Compartilhada do IIS, consulte Módulo do ASP.NET Core com a
Configuração Compartilhada do IIS.

Instalar a Implantação da Web durante a publicação com o Visual


Studio
Ao implantar aplicativos para servidores com Implantação da Web, instale a versão mais recente da
Implantação da Web no servidor. Para instalar a Implantação da Web, use o WebPI (Web Platform Installer) ou
obtenha um instalador diretamente no Centro de Download da Microsoft. O método preferencial é usar o
WebPI. O WebPI oferece uma instalação autônoma e uma configuração para provedores de hospedagem.

Criar o site do IIS


1. No sistema de hospedagem, crie uma pasta para conter arquivos e pastas publicados do aplicativo. O
layout de implantação do aplicativo é descrito no tópico Estrutura de Diretórios.
2. Na nova pasta, crie uma pasta logs para armazenar os logs do stdout do Módulo do ASP.NET Core
quando o log do stdout estiver habilitado. Se o aplicativo for implantado com uma pasta logs no
conteúdo, ignore esta etapa. Para obter instruções sobre como habilitar o MSBuild para criar a pasta logs
automaticamente quando o projeto for criado localmente, veja o tópico Estrutura de Diretórios.
IMPORTANTE
Somente use o log do stdout para solucionar problemas de falhas de inicialização de aplicativo. Nunca use o log
do stdout para registrar aplicativos de rotina. Não há limites para o tamanho do arquivo de log ou para o número
de arquivos de log criados. O pool de aplicativos deve ter acesso de gravação ao local em que os logs foram
gravados. Todas as pastas no caminho para a localização do log devem existir. Para saber mais sobre o log do
stdout, veja Criação e redirecionamento de logs. Para saber mais sobre o registro em um aplicativo do ASP.NET
Core, veja o tópico Registrar.

3. No Gerenciador do IIS, abra o nó do servidor no painel Conexões. Clique com botão direito do mouse
na pasta Sites. Selecione Adicionar Site no menu contextual.
4. Forneça um Nome do site e defina o Caminho físico como a pasta de implantação do aplicativo.
Forneça a configuração Associação e crie o site ao selecionar OK:

AVISO
Associações de curinga de nível superior ( http://*:80/ e http://+:80 ) não devem ser usadas. Associações
de curinga de nível superior podem abrir o aplicativo para vulnerabilidades de segurança. Isso se aplica a curingas
fortes e fracos. Use nomes de host explícitos em vez de curingas. Associações de curinga de subdomínio (por
exemplo, *.mysub.com ) não têm esse risco de segurança se você controlar o domínio pai completo (em vez de
*.com , o qual é vulnerável). Veja rfc7230 section-5.4 para obter mais informações.

5. No nó do servidor, selecione Pools de Aplicativos.


6. Clique com o botão direito do mouse no pool de aplicativos do site e selecione Configurações Básicas
no menu contextual.
7. Na janela Editar Pool de Aplicativos, defina a versão do CLR do .NET como Sem Código
Gerenciado:

O ASP.NET Core é executado em um processo separado e gerencia o tempo de execução. O ASP.NET


Core não depende do carregamento do CLR de área de trabalho. Definir a versão do CLR do .NET
como Sem Código Gerenciado é opcional.
8. Confirme se a identidade do modelo de processo tem as permissões apropriadas.
Se você alterar a identidade padrão do pool de aplicativos (Modelo de Processo > Identidade) em
ApplicationPoolIdentity para outra, verifique se a nova identidade tem as permissões necessárias
para acessar a pasta do aplicativo, o banco de dados e outros recursos necessários. Por exemplo, o pool
de aplicativos requer acesso de leitura e gravação às pastas nas quais o aplicativo lê e grava os arquivos.
Configuração de Autenticação do Windows (opcional)
Para saber mais, veja Configurar a Autenticação do Windows.

Implantar o aplicativo
Implante o aplicativo na pasta criada no sistema de hospedagem. A Implantação da Web é o mecanismo
recomendado para implantação.
Implantação da Web com o Visual Studio
Consulte o tópico Perfis de publicação do Visual Studio para implantação de aplicativos ASP.NET Core para
saber como criar um perfil de publicação para uso com a Implantação da Web. Se o provedor de hospedagem
fornecer um Perfil de Publicação ou o suporte para a criação de um, baixe o perfil e importe-o usando a caixa
de diálogo Publicar do Visual Studio.

Implantação da Web fora do Visual Studio


Também é possível usar a Implantação da Web fora do Visual Studio na linha de comando. Para obter mais
informações, consulte Ferramenta de Implantação da Web.
Alternativas à Implantação da Web
Use qualquer um dos vários métodos para mover o aplicativo para o sistema host, como cópia manual, Xcopy,
Robocopy ou PowerShell.
Para obter mais informações sobre a implantação do ASP.NET Core no IIS, consulte a seção Recursos de
implantação para administradores do IIS.

Navegar no site

Arquivos de implantação bloqueados


Os arquivos na pasta de implantação são bloqueados quando o aplicativo está em execução. Os arquivos
bloqueados não podem ser substituídos durante a implantação. Para liberar os arquivos bloqueados em uma
implantação, interrompa o pool de aplicativos usando uma das abordagens a seguir:
Use a Implantação da Web e referencie Microsoft.NET.Sdk.Web no arquivo do projeto. Um arquivo
app_offline.htm é colocado na raiz do diretório de aplicativo da Web. Quando o arquivo estiver presente,
o módulo do ASP.NET Core apenas desligará o aplicativo e servirá o arquivo app_offline.htm durante a
implantação. Para obter mais informações, consulte Referência de configuração do módulo do ASP.NET
Core.
Manualmente interrompa o pool de aplicativos no Gerenciador do IIS no servidor.
Use o PowerShell para interromper e reiniciar o pool de aplicativos (requer o PowerShell 5 ou posterior):
$webAppPoolName = 'APP_POOL_NAME'

# Stop the AppPool


if((Get-WebAppPoolState $webAppPoolName).Value -ne 'Stopped') {
Stop-WebAppPool -Name $webAppPoolName
while((Get-WebAppPoolState $webAppPoolName).Value -ne 'Stopped') {
Start-Sleep -s 1
}
Write-Host `-AppPool Stopped
}

# Provide script commands here to deploy the app

# Restart the AppPool


if((Get-WebAppPoolState $webAppPoolName).Value -ne 'Started') {
Start-WebAppPool -Name $webAppPoolName
while((Get-WebAppPoolState $webAppPoolName).Value -ne 'Started') {
Start-Sleep -s 1
}
Write-Host `-AppPool Started
}

Proteção de dados
A pilha Proteção de Dados do ASP.NET Core é usada por vários middlewares ASP.NET Core, incluindo aqueles
usados na autenticação. Mesmo se as APIs de proteção de dados não forem chamadas pelo código do usuário,
a proteção de dados deverá ser configurada com um script de implantação ou no código do usuário para criar
um repositório de chaves criptográfico persistente. Se a proteção de dados não estiver configurada, as chaves
serão mantidas na memória e descartadas quando o aplicativo for reiniciado.
Se o token de autenticação for armazenado na memória quando o aplicativo for reiniciado:
Todos os tokens de autenticação baseados em cookies serão invalidados.
Os usuários precisam entrar novamente na próxima solicitação deles.
Todos os dados protegidos com o token de autenticação não poderão mais ser descriptografados. Isso pode
incluir os tokens CSRF e cookies TempData do MVC do ASP.NET Core.
Para configurar a proteção de dados no IIS para persistir o token de autenticação, use uma das seguintes
abordagens:
Criar chaves de registro de proteção de dados
As chaves de proteção de dados usadas pelos aplicativos ASP.NET Core são armazenadas no registro
externo aos aplicativos. Para persistir as chaves de determinado aplicativo, crie uma chave de registro
para o pool de aplicativos.
Para instalações autônomas do IIS não Web Farm, você pode usar o script Provision-AutoGenKeys.ps1
de Proteção de Dados do PowerShell para cada pool de aplicativos usado com um aplicativo ASP.NET
Core. Esse script cria uma chave de registro no registro HKLM que é acessível apenas para a conta do
processo de trabalho do pool de aplicativos do aplicativo. As chaves são criptografadas em repouso
usando a DPAPI com uma chave para todo o computador.
Em cenários de web farm, um aplicativo pode ser configurado para usar um caminho UNC para
armazenar seu token de autenticação de proteção de dados. Por padrão, as chaves de proteção de dados
não são criptografadas. Garanta que as permissões de arquivo de o compartilhamento de rede sejam
limitadas à conta do Windows na qual o aplicativo é executado. Um certificado X509 pode ser usado
para proteger chaves em repouso. Considere um mecanismo para permitir aos usuários carregar
certificados: coloque os certificados no repositório de certificados confiáveis do usuário e certifique-se
de que eles estejam disponíveis em todos os computadores nos quais o aplicativo do usuário é
executado. Veja Configurar a proteção de dados do ASP.NET Core para obter detalhes.
Configurar o pool de aplicativos do IIS para carregar o perfil do usuário
Essa configuração está na seção Modelo de processo nas Configurações avançadas do pool de
aplicativos. Defina Carregar perfil do usuário como True . Isso armazena as chaves no diretório do perfil
do usuário e as protege usando a DPAPI com uma chave específica à conta de usuário usada pelo pool
de aplicativos.
Use o sistema de arquivos como um repositório de tokens de autenticação
Ajuste o código do aplicativo para usar o sistema de arquivos como um repositório de tokens de
autenticação. Use um certificado X509 para proteger o token de autenticação e verifique se ele é um
certificado confiável. Se o certificado for autoassinado, você deverá colocá-lo no repositório Raiz
confiável.
Ao usar o IIS em uma web farm:
Use um compartilhamento de arquivos que pode ser acessado por todos os computadores.
Implante um certificado X509 em cada computador. Configure a proteção de dados no código.
Definir uma política para todo o computador para proteção de dados
O sistema de proteção de dados tem suporte limitado para a configuração da política de todo o
computador padrão para todos os aplicativos que consomem as APIs de proteção de dados. Veja a
documentação de proteção de dados para obter detalhes.

Configuração de subaplicativos
Os subaplicativos adicionados no aplicativo raiz não devem incluir o Módulo do ASP.NET Core como um
manipulador. Se o módulo for adicionado como um manipulador em um arquivo web.config de um
subaplicativo, quando tentar procurar o subaplicativo, você receberá um 500.19 Erro Interno do Servidor que
referenciará o arquivo de configuração com falha.
O seguinte exemplo mostra um arquivo web.config publicado de um subaplicativo ASP.NET Core:

<?xml version="1.0" encoding="utf-8"?>


<configuration>
<system.webServer>
<aspNetCore processPath="dotnet"
arguments=".\<assembly_name>.dll"
stdoutLogEnabled="false"
stdoutLogFile=".\logs\stdout" />
</system.webServer>
</configuration>

Ao hospedar um subaplicativo não ASP.NET Core abaixo de um aplicativo ASP.NET Core, remova
explicitamente o manipulador herdado no arquivo web.config do subaplicativo:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<remove name="aspNetCore" />
</handlers>
<aspNetCore processPath="dotnet"
arguments=".\<assembly_name>.dll"
stdoutLogEnabled="false"
stdoutLogFile=".\logs\stdout" />
</system.webServer>
</configuration>

Para obter mais informações sobre como configurar o Módulo do ASP.NET Core, consulte o tópico Introdução
ao Módulo do ASP.NET Core e a Referência de configuração do Módulo do ASP.NET Core.

Configuração do IIS com web.config


A configuração do IIS é influenciada pela seção <system.webServer> de web.config para os recursos do IIS
que se aplicam a uma configuração de proxy reverso. Se o IIS é configurado no nível do servidor para usar a
compactação dinâmica, o elemento <urlCompression> no arquivo web.config do aplicativo pode desabilitá-la.
Para saber mais, veja a referência de configuração do <system.webServer>, a referência de configuração do
Módulo do ASP.NET Core e Módulos do IIS com o ASP.NET Core. Para definir variáveis de ambiente para
aplicativos individuais executados em pools de aplicativos isolados (compatível com o IIS 10.0+ ou posterior),
veja a seção comando AppCmd.exe do tópico Variáveis de ambiente <environmentVariables> na
documentação de referência do IIS.

Seções de configuração de web.config


As seções de configuração de aplicativos ASP.NET 4.x em web.config não são usadas por aplicativos ASP.NET
Core para configuração:
<system.web>
<appSettings>
<connectionStrings>
<location>
Aplicativos ASP.NET Core são configurados para usar outros provedores de configuração. Para obter mais
informações, consulte Configuração.

Pools de aplicativos
Ao hospedar vários sites em um servidor, isole os aplicativos entre si, executando cada aplicativo em seu
próprio pool de aplicativos. A caixa de diálogo Adicionar Site do IIS usa como padrão essa configuração.
Quando você fornece um Nome do site, o texto é transferido automaticamente para a caixa de texto Pool de
aplicativos. Um novo pool de aplicativos é criado usando o nome do site quando você adicionar o site.

Identidade do pool de aplicativos


Uma conta de identidade do pool de aplicativos permite executar um aplicativo em uma conta exclusiva sem a
necessidade de criar e gerenciar domínios ou contas locais. No IIS 8.0 ou posterior, o WAS (Processo de
trabalho do administrador) do IIS cria uma conta virtual com o nome do novo pool de aplicativos e executa os
processos de trabalho do pool de aplicativos nesta conta por padrão. No Console de Gerenciamento do IIS, em
Configurações avançadas do pool de aplicativos, verifique se a Identidade é definida para usar
ApplicationPoolIdentity:
O processo de gerenciamento do IIS cria um identificador seguro com o nome do pool de aplicativos no
Sistema de segurança do Windows. Os recursos podem ser protegidos usando essa identidade. No entanto,
essa identidade não é uma conta de usuário real e não será mostrada no Console de Gerenciamento de
Usuários do Windows.
Se o processo de trabalho do IIS requerer acesso elevado ao aplicativo, modifique a ACL (lista de controle de
acesso) do diretório que contém o aplicativo:
1. Abra o Windows Explorer e navegue para o diretório.
2. Clique com o botão direito do mouse no diretório e selecione Propriedades.
3. Na guia Segurança, selecione o botão Editar e, em seguida, no botão Adicionar.
4. Clique no botão Locais e verifique se o sistema está selecionado.
5. Insira IIS AppPool\<nome_pool_aplicativos> na área Inserir os nomes de objeto a serem
selecionados. Selecione o botão Verificar Nomes. Para o DefaultAppPool, verifique os nomes usando
IIS AppPool\DefaultAppPool. Quando o botão Verificar Nomes é selecionado, um valor de
DefaultAppPool é indicado na área de nomes de objeto. Não é possível inserir o nome do pool de
aplicativos diretamente na área de nomes de objeto. Use o formato IIS AppPool\
<nome_pool_aplicativos> ao verificar o nome do objeto.
6. Selecione OK.

7. As permissões de leitura & execução devem ser concedidas por padrão. Forneça permissões adicionais
conforme necessário.
O acesso também pode ser concedido por meio de um prompt de comando usando a ferramenta ICACLS.
Usando o DefaultAppPool como exemplo, o comando a seguir é usado:

ICACLS C:\sites\MyWebApp /grant "IIS AppPool\DefaultAppPool":F

Para saber mais, veja o tópico icacls.

Recursos de implantação para administradores do IIS


Conheça o ISS detalhadamente na documentação do IIS.
Documentação do ISS
Saiba mais sobre os modelos de implantação de aplicativos do .NET Core.
Implantação de aplicativos do .NET Core
Saiba como o módulo do ASP.NET Core permite que o servidor Web Kestrel use o IIS ou o IIS Express como
um servidor proxy reverso.
Módulo do ASP.NET Core
Saiba como configurar o módulo do ASP.NET Core para hospedar aplicativos do ASP.NET Core.
Referência de configuração do Módulo do ASP.NET Core
Saiba mais sobre a estrutura do diretório de aplicativos publicados do ASP.NET Core.
Estrutura de diretórios
Descubra módulos ativos e inativos do IIS para aplicativos do ASP.NET Core e como gerenciar os módulos do
IIS.
Módulos do IIS
Saiba como diagnosticar problemas com as implantações do ISS dos aplicativos do ASP.NET Core.
Solução de problemas
Distinga erros comuns ao hospedar aplicativos do ASP.NET Core no IIS.
Referência de erros comuns para o Serviço de Aplicativo do Azure e o IIS

Recursos adicionais
Introdução ao ASP.NET Core
O site oficial da IIS da Microsoft
Biblioteca de conteúdo técnico do Windows Server
Solucionar problemas de núcleo do ASP.NET no IIS
10/04/2018 • 15 min to read • Edit Online

Por Luke Latham


Este artigo fornece instruções sobre como diagnosticar uma ASP.NET Core problema de inicialização do
aplicativo ao hospedar com serviços de informações da Internet (IIS ). As informações neste artigo se aplica ao
hospedar no IIS no Windows Server e Windows Desktop.
No Visual Studio, um projeto do ASP.NET Core padrão IIS Express hospedagem durante a depuração. Um 502.5
falha no processo que ocorre quando a depuração local pode ser troubleshooted usando o aviso neste tópico.
Tópicos de solução de problemas adicionais:
Solucionar problemas no ASP.NET Core no Serviço de Aplicativo do Azure
Embora o serviço de aplicativo usa o ASP.NET Core módulo e IIS para hospedar aplicativos, consulte o tópico
dedicado para obter instruções específicas para o serviço de aplicativo.
Tratar erros
Descobrir como tratar erros em aplicativos do ASP.NET Core durante o desenvolvimento em um sistema local.
Aprenda a depurar usando o Visual Studio
Este tópico apresenta os recursos do depurador do Visual Studio.

Erros de inicialização do aplicativo


502.5 falha de processo
O processo de trabalho falha. O aplicativo não foi iniciada.
O módulo do ASP.NET Core tenta iniciar o processo de trabalho, mas falhar ao iniciar. A causa de uma falha de
inicialização do processo geralmente pode ser determinada de entradas a Log de eventos do aplicativo e log do
módulo do ASP.NET Core stdout.
O 502.5 falha no processo página de erro é retornada quando um erro de configuração de hospedagem ou o
aplicativo faz com que a falha do processo de trabalho:

Erro de servidor interno 500


O aplicativo é iniciado, mas um erro impede que o servidor de atender à solicitação.
Esse erro ocorre no código do aplicativo durante a inicialização ou durante a criação de uma resposta. A resposta
não poderá conter nenhum conteúdo ou a resposta pode ser exibido como um 500 Erro interno do servidor no
navegador. O Log de eventos do aplicativo geralmente indica que o aplicativo é iniciado normalmente. Da
perspectiva do servidor, que está correta. O aplicativo foi iniciado, mas não pode gerar uma resposta válida.
Executar o aplicativo em um prompt de comando no servidor ou habilite o log do módulo do ASP.NET Core
stdout para solucionar o problema.
Redefinição de Conexão
Se um erro ocorrer após os cabeçalhos são enviados, é muito tarde para o servidor enviar um 500 Erro interno
do servidor quando ocorre um erro. Isso geralmente acontece quando ocorre um erro durante a serialização de
objetos complexos por uma resposta. Esse tipo de erro é exibida como uma redefinição de conexão erro no
cliente. Log de aplicativo pode ajudar a solucionar esses tipos de erros.

Limites de inicialização padrão


O módulo de núcleo do ASP.NET está configurado com um padrão startupTimeLimit de 120 segundos. Quando
deixada no valor padrão, um aplicativo pode levar até dois minutos para ser iniciado antes do módulo faz uma
falha do processo. Para obter informações sobre como configurar o módulo, consulte atributos do elemento
aspNetCore.

Solucionar problemas de inicialização do aplicativo


Log de eventos do aplicativo
Acesse o Log de eventos do aplicativo:
1. Abra o menu Iniciar, procure Visualizador de eventose, em seguida, selecione o Visualizador de eventos
aplicativo.
2. Em Visualizador de eventos, abra o Logs do Windows nó.
3. Selecione aplicativo para abrir o Log de eventos do aplicativo.
4. Procure erros associados com o aplicativo com falha. Erros têm um valor de IIS AspNetCore módulo ou
módulo do IIS Express AspNetCore no fonte coluna.
Executar o aplicativo em um prompt de comando
Muitos erros de inicialização não produzem informações úteis no Log de eventos do aplicativo. Você pode
encontrar a causa de alguns erros ao executar o aplicativo em um prompt de comando no sistema de
hospedagem.
Dependente de estrutura de implantação
Se o aplicativo for um implantação dependentes de framework:
1. Em um prompt de comando, navegue até a pasta de implantação e executar o aplicativo com a execução de
assembly do aplicativo com dotnet.exe. No comando a seguir, substitua o nome do assembly do aplicativo
para <nome_do_assembly >: dotnet .\<assembly_name>.dll .
2. A saída do aplicativo, mostrando os erros do console é escrito na janela de console.
3. Se o erro ocorrer ao fazer uma solicitação para o aplicativo, fazer uma solicitação para o host e a porta onde
Kestrel escuta. Usando o host padrão e o post, fazer uma solicitação para http://localhost:5000/ . Se o
aplicativo normalmente responde no endereço de ponto de extremidade Kestrel, o problema é mais provável
relacionados à configuração de proxy reverso e menos provável dentro do aplicativo.
Independente de implantação
Se o aplicativo for um implantação autossuficiente:
1. Em um prompt de comando, navegue até a pasta de implantação e execute o executável do aplicativo. No
comando a seguir, substitua o nome do assembly do aplicativo para <nome_do_assembly >:
<assembly_name>.exe .
2. A saída do aplicativo, mostrando os erros do console é escrito na janela de console.
3. Se o erro ocorrer ao fazer uma solicitação para o aplicativo, fazer uma solicitação para o host e a porta onde
Kestrel escuta. Usando o host padrão e o post, fazer uma solicitação para http://localhost:5000/ . Se o
aplicativo normalmente responde no endereço de ponto de extremidade Kestrel, o problema é mais provável
relacionados à configuração de proxy reverso e menos provável dentro do aplicativo.
Log de stdout de módulo principal do ASP.NET
Para habilitar e exibir logs de stdout:
1. Navegue até a pasta de implantação do site no sistema de hospedagem.
2. Se o logs pasta não estiver presente, crie a pasta. Para obter instruções sobre como habilitar o MSBuild criar o
logs pasta na implantação automaticamente, consulte o estrutura de diretórios tópico.
3. Editar o Web. config arquivo. Definir stdoutLogEnabled para true e altere o stdoutLogFile path para
apontar para o logs pasta (por exemplo, .\logs\stdout ). stdout o caminho é o prefixo de nome de arquivo
de log. Um carimbo de hora, a id do processo e a extensão de arquivo são adicionadas automaticamente
quando o log é criado. Usando stdout como o prefixo de nome de arquivo, um arquivo de log típico é
nomeado stdout_20180205184032_5412.log.
4. Salvar o documento atualizado Web. config arquivo.
5. Fazer uma solicitação para o aplicativo.
6. Navegue até o logs pasta. Localize e abra o log de stdout mais recente.
7. Estude o log de erros.
Importante! Desabilite stdout registro em log quando o problema for solucionado.
1. Editar o Web. config arquivo.
2. Definir stdoutLogEnabled para false .
3. Salve o arquivo.

AVISO
Falha ao desabilitar o log de stdout pode levar a falhas de aplicativo ou servidor. Não há limites para o tamanho do arquivo
de log ou para o número de arquivos de log criados.
Para log de rotina no aplicativo do ASP.NET Core, use uma biblioteca de registro em log que limita o tamanho do arquivo
de log e gira logs. Para obter mais informações, consulte provedores de log de terceiros.

Habilitando a página de exceção do desenvolvedor


O ASPNETCORE_ENVIRONMENT variável de ambiente pode ser adicionada ao Web. config para executar o aplicativo
no ambiente de desenvolvimento. Como o ambiente não é substituído na inicialização do aplicativo por
UseEnvironment no construtor de host, definindo a variável de ambiente permite que o página de exceção de
desenvolvedor aparecem quando o aplicativo é executado.
<aspNetCore processPath="dotnet"
arguments=".\MyApp.dll"
stdoutLogEnabled="false"
stdoutLogFile=".\logs\stdout">
<environmentVariables>
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
</environmentVariables>
</aspNetCore>

Definir a variável de ambiente para ASPNETCORE_ENVIRONMENT só é recomendado para uso no preparo e teste de
servidores que não são expostos à Internet. Remova a variável de ambiente a Web. config arquivo após a
solução de problemas. Para obter informações sobre como definir variáveis de ambiente Web. config, consulte
environmentVariables elemento filho aspNetCore.

Erros comuns de inicialização


Consulte o referência de erros comuns do ASP.NET Core. A maioria dos problemas comuns que impedem a
inicialização do aplicativo é abordada no tópico de referência.

Aplicativo lento ou deslocado


Quando um aplicativo responde lentamente ou trava em uma solicitação, obter e analisar um arquivo de
despejo. Arquivos de despejo podem ser obtidos usando qualquer uma das ferramentas a seguir:
ProcDump
DebugDiag
WinDbg: baixar as ferramentas de depuração para Windows, depuração usando o WinDbg

Depuração remota
Consulte remota de depuração ASP.NET Core em um computador de IIS remoto no Visual Studio de 2017 na
documentação do Visual Studio.

Informações do aplicativo
Application Insights fornece a telemetria de aplicativos hospedados pelo IIS, incluindo recursos de relatório e log
de erros. Application Insights só pode relatar erros ocorridos depois que o aplicativo é iniciado quando recursos
de log do aplicativo se tornar disponíveis. Para obter mais informações, consulte Application Insights para
ASP.NET Core.

Avisos de solução de problemas adicionais


Às vezes, um aplicativo está funcionando falha imediatamente após a atualização ou o SDK do .NET Core nas
versões de pacote ou de máquina de desenvolvimento dentro do aplicativo. Em alguns casos, pacotes
incoerentes podem interromper um aplicativo ao executar atualizações principais. A maioria desses problemas
pode ser corrigida seguindo estas instruções:
1. Excluir o bin e obj pastas.
2. Limpe o pacote armazena em cache em % UserProfile %\. NuGet\pacotes e % LocalAppData %\Nuget\v3
cache.
3. Restaurar e recompilar o projeto.
4. Confirme que a implantação anterior no servidor foi completamente excluída antes de reimplantar o
aplicativo.
DICA
Uma maneira conveniente para Limpar caches do pacote é executar dotnet nuget locals all --clear em um prompt
de comando.
Limpar os caches de pacote também pode ser feito usando o nuget.exe ferramenta e executar o comando
nuget locals all -clear . NuGet.exe não é uma instalação de pacote com o sistema operacional da área de trabalho do
Windows e devem ser obtidos separadamente do NuGet site.

Recursos adicionais
Introdução ao tratamento de erro no ASP.NET Core
Referência de erros comuns para o Serviço de Aplicativo do Azure e o IIS com o ASP.NET Core
Referência de configuração do Módulo do ASP.NET Core
Solucionar problemas no ASP.NET Core no Serviço de Aplicativo do Azure
Referência de configuração de módulo principal do
ASP.NET
27/04/2018 • 21 min to read • Edit Online

Por Luke Latham, Rick Anderson, e Sourabh Shirhatti


Este documento fornece instruções sobre como configurar o módulo do núcleo do ASP.NET para hospedar
aplicativos ASP.NET Core. Para obter uma introdução ao ASP.NET Core Module e instruções de instalação,
consulte o visão geral do módulo do ASP.NET Core.

Configuração com a Web. config


O módulo de núcleo do ASP.NET está configurado com o aspNetCore seção o system.webServer nó do site
Web. config arquivo.
O seguinte Web. config arquivo é publicado para uma implantação dependentes de framework e configura o
módulo de núcleo do ASP.NET para manipular solicitações de site:

<?xml version="1.0" encoding="utf-8"?>


<configuration>
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet"
arguments=".\MyApp.dll"
stdoutLogEnabled="false"
stdoutLogFile=".\logs\stdout" />
</system.webServer>
</configuration>

O seguinte Web. config é publicado para uma implantação autossuficiente:

<?xml version="1.0" encoding="utf-8"?>


<configuration>
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath=".\MyApp.exe"
stdoutLogEnabled="false"
stdoutLogFile=".\logs\stdout" />
</system.webServer>
</configuration>

Quando um aplicativo é implantado em do serviço de aplicativo do Azure, o stdoutLogFile caminho é definido


como \\?\%home%\LogFiles\stdout . O caminho salva stdout logs para o LogFiles pasta, que é um local
automaticamente criada pelo serviço.
Consulte configuração abaixo aplicativo para uma nota importante relativas à configuração do Web. config
arquivos em subpastas de aplicativos.
Atributos do elemento aspNetCore
ATRIBUTO DESCRIÇÃO PADRÃO

arguments Atributo de cadeia de caracteres


opcional.
Argumentos para o executável
especificado em processPath.

disableStartUpErrorPage verdadeiro ou falso. false

Se for true, o 502.5 - falha do


processo página é suprimida, e a
página de código de 502 status
configuradas no Web. config terá
precedência.

forwardWindowsAuthToken verdadeiro ou falso. true

Se for true, o token é


encaminhado para o processo
filho escuta em %
ASPNETCORE_PORT % como um
cabeçalho 'MS-ASPNETCORE-
WINAUTHTOKEN' por solicitação.
É responsabilidade do processo
para chamar CloseHandle neste
token por solicitação.

processPath Atributo de cadeia de caracteres


obrigatória.
Caminho para o executável que
inicia um processo que escuta
solicitações HTTP. Há suporte para
caminhos relativos. Se o caminho
começa com . , o caminho é
considerado em relação à raiz do
site.

rapidFailsPerMinute Atributo inteiro opcional. 10

Especifica o número de vezes que


o processo especificado na
processPath pode falhar por
minuto. Se esse limite for
excedido, o módulo para iniciar o
processo para o restante do
minuto.
ATRIBUTO DESCRIÇÃO PADRÃO

requestTimeout Atributo timespan opcional. 00:02:00

Especifica a duração para a qual o


módulo do ASP.NET Core aguarda
uma resposta do processo que
escuta em % ASPNETCORE_PORT
%.
Em versões do módulo ASP.NET
Core que acompanha a versão do
ASP.NET Core 2.0 ou anterior, o
requestTimeout deve ser
especificado em minutos inteiros,
caso contrário, o padrão é 2
minutos.

shutdownTimeLimit Atributo inteiro opcional. 10

Duração em segundos que o


módulo espera para o executável
para desligar normalmente
quando o app_offline.htm arquivo
é detectado.

startupTimeLimit Atributo inteiro opcional. 120

Duração em segundos que o


módulo espera para o arquivo
executável iniciar um processo que
escuta na porta. Se esse tempo
limite for excedido, o módulo
elimina o processo. O módulo
tentará reiniciar o processo
quando ele recebe uma nova
solicitação e continuará a tentar
reiniciar o processo em solicitações
subsequentes de entrada, a
menos que o aplicativo não pode
ser iniciado rapidFailsPerMinute
vezes nos últimos minuto sem
interrupção.

stdoutLogEnabled Atributo booliano opcional. false

Se true, stdout e stderr para o


processo especificado na
processPath são redirecionadas
para o arquivo especificado em
stdoutLogFile.
ATRIBUTO DESCRIÇÃO PADRÃO

stdoutLogFile Atributo de cadeia de caracteres aspnetcore-stdout


opcional.
Especifica o caminho relativo ou
absoluto para o qual stdout e
stderr do processo especificado
na processPath são registrados
em log. Caminhos relativos são
relativo à raiz do site. Qualquer
caminho começando com .
estão em relação ao site raiz e
todos os outros caminhos são
tratados como caminhos
absolutos. As pastas fornecidas no
caminho devem existir para que o
módulo criar o arquivo de log.
Usando os delimitadores de
sublinhado, um carimbo de hora,
ID de processo e extensão de
arquivo (. log) são adicionados
para o último segmento do
stdoutLogFile caminho. Se
.\logs\stdout é fornecido
como um valor, um log de
exemplo stdout é salvo como
stdout_20180205194132_1934.lo
g no logs pasta quando salvos em
5/2/2018 às 19:41:32 com uma ID
de processo de 1934.

ATRIBUTO DESCRIÇÃO PADRÃO

arguments Atributo de cadeia de caracteres


opcional.
Argumentos para o executável
especificado em processPath.

disableStartUpErrorPage verdadeiro ou falso. false

Se for true, o 502.5 - falha do


processo página é suprimida, e a
página de código de 502 status
configuradas no Web. config terá
precedência.

forwardWindowsAuthToken verdadeiro ou falso. true

Se for true, o token é


encaminhado para o processo
filho escuta em %
ASPNETCORE_PORT % como um
cabeçalho 'MS-ASPNETCORE-
WINAUTHTOKEN' por solicitação.
É responsabilidade do processo
para chamar CloseHandle neste
token por solicitação.
ATRIBUTO DESCRIÇÃO PADRÃO

processPath Atributo de cadeia de caracteres


obrigatória.
Caminho para o executável que
inicia um processo que escuta
solicitações HTTP. Há suporte para
caminhos relativos. Se o caminho
começa com . , o caminho é
considerado em relação à raiz do
site.

rapidFailsPerMinute Atributo inteiro opcional. 10

Especifica o número de vezes que


o processo especificado na
processPath pode falhar por
minuto. Se esse limite for
excedido, o módulo para iniciar o
processo para o restante do
minuto.

requestTimeout Atributo timespan opcional. 00:02:00

Especifica a duração para a qual o


módulo do ASP.NET Core aguarda
uma resposta do processo que
escuta em % ASPNETCORE_PORT
%.
Em versões do módulo ASP.NET
Core que acompanha a versão do
ASP.NET Core 2.1 ou posterior, o
requestTimeout é especificado
em horas, minutos e segundos.

shutdownTimeLimit Atributo inteiro opcional. 10

Duração em segundos que o


módulo espera para o executável
para desligar normalmente
quando o app_offline.htm arquivo
é detectado.
ATRIBUTO DESCRIÇÃO PADRÃO

startupTimeLimit Atributo inteiro opcional. 120

Duração em segundos que o


módulo espera para o arquivo
executável iniciar um processo que
escuta na porta. Se esse tempo
limite for excedido, o módulo
elimina o processo. O módulo
tentará reiniciar o processo
quando ele recebe uma nova
solicitação e continuará a tentar
reiniciar o processo em solicitações
subsequentes de entrada, a
menos que o aplicativo não pode
ser iniciado rapidFailsPerMinute
vezes nos últimos minuto sem
interrupção.

stdoutLogEnabled Atributo booliano opcional. false

Se true, stdout e stderr para o


processo especificado na
processPath são redirecionadas
para o arquivo especificado em
stdoutLogFile.

stdoutLogFile Atributo de cadeia de caracteres aspnetcore-stdout


opcional.
Especifica o caminho relativo ou
absoluto para o qual stdout e
stderr do processo especificado
na processPath são registrados
em log. Caminhos relativos são
relativo à raiz do site. Qualquer
caminho começando com .
estão em relação ao site raiz e
todos os outros caminhos são
tratados como caminhos
absolutos. As pastas fornecidas no
caminho devem existir para que o
módulo criar o arquivo de log.
Usando os delimitadores de
sublinhado, um carimbo de hora,
ID de processo e extensão de
arquivo (. log) são adicionados
para o último segmento do
stdoutLogFile caminho. Se
.\logs\stdout é fornecido
como um valor, um log de
exemplo stdout é salvo como
stdout_20180205194132_1934.lo
g no logs pasta quando salvos em
5/2/2018 às 19:41:32 com uma ID
de processo de 1934.

Definir variáveis de ambiente


Variáveis de ambiente podem ser especificadas para o processo no processPath atributo. Especificar uma
variável de ambiente com o environmentVariable elemento filho de um environmentVariables elemento da
coleção. Variáveis de ambiente definidas nesta seção têm precedência sobre o sistema, variáveis de ambiente.
O exemplo a seguir define duas variáveis de ambiente. ASPNETCORE_ENVIRONMENT configura o ambiente do
aplicativo para Development . Um desenvolvedor pode definir esse valor temporariamente Web. config arquivo
para forçar o página de exceção de desenvolvedor carregar ao depurar uma exceção de aplicativo. CONFIG_DIR
é um exemplo de uma variável de ambiente definidas pelo usuário, em que o desenvolvedor tenha gravado
código que lê o valor de inicialização para formar um caminho para carregar o arquivo de configuração do
aplicativo.

<aspNetCore processPath="dotnet"
arguments=".\MyApp.dll"
stdoutLogEnabled="false"
stdoutLogFile="\\?\%home%\LogFiles\stdout">
<environmentVariables>
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
<environmentVariable name="CONFIG_DIR" value="f:\application_config" />
</environmentVariables>
</aspNetCore>

AVISO
Apenas definir o ASPNETCORE_ENVIRONMENT envirnonment variável Development no preparo e teste de servidores que
não estão acessíveis a redes não confiáveis, como a Internet.

app_offline.htm
Se um arquivo com o nome app_offline.htm é detectado no diretório raiz de um aplicativo, o módulo do
ASP.NET Core tenta desligar normalmente o aplicativo e parar o processamento de solicitações de entrada. Se
o aplicativo ainda está em execução após o número de segundos definido no shutdownTimeLimit , o módulo do
ASP.NET Core elimina o processo em execução.
Enquanto o app_offline.htm arquivo estiver presente, o módulo do ASP.NET Core responde às solicitações
enviando o conteúdo de app_offline.htm arquivo. Quando o app_offline.htm arquivo é removido, a próxima
solicitação inicia o aplicativo.

Página de erro de inicialização


Se o módulo do ASP.NET Core falhar ao iniciar o processo de back-end ou o início do processo de back-end,
mas não escutar na porta configurada, um 502.5 falha no processo página de código de status será exibida.
Para omitir esta página e reverter para a página de código de status de IIS 502 padrão, use o
disableStartUpErrorPage atributo. Para obter mais informações sobre como configurar mensagens de erro
personalizadas, consulte erros HTTP <httpErrors> .
Criação de log e de redirecionamento
O módulo do ASP.NET Core redireciona stdout e stderr logs no disco se o stdoutLogEnabled e stdoutLogFile
atributos do aspNetCore elemento são definidas. As pastas no stdoutLogFile caminho deve existir para que o
módulo criar o arquivo de log. O pool de aplicativos deve ter acesso de gravação para o local onde os logs são
gravados (usar IIS AppPool\<app_pool_name> para fornecer permissão de gravação).
Logs não são girados, a menos que ocorra a reciclagem de processo/reinício. É responsabilidade do hoster
para limitar o espaço em disco que consomem os logs.
Usando o log de stdout é recomendado apenas para solucionar problemas de inicialização do aplicativo. Não
use o log de stdout para fins de registro do aplicativo geral. Para log de rotina no aplicativo do ASP.NET Core,
use uma biblioteca de registro em log que limita o tamanho do arquivo de log e gira logs. Para obter mais
informações, consulte provedores de log de terceiros.
Uma extensão de arquivo e o carimbo de hora são adicionados automaticamente quando o arquivo de log é
criado. O nome do arquivo de log é composto por meio do acréscimo de carimbo de hora, a ID do processo e a
extensão de arquivo (. log) para o último segmento do stdoutLogFile caminho (normalmente stdout)
delimitados por sublinhados. Se o stdoutLogFile caminho termina com stdout, um log para um aplicativo com
um PID de 1934 criada em 5/2/2018 às 19:42:32 tem o nome de arquivo stdout_20180205194132_1934.log.
O exemplo a seguir aspNetCore elemento configura stdout registro em log para um aplicativo hospedado no
serviço de aplicativo do Azure. Um caminho local ou um caminho de compartilhamento de rede é aceitável
para o registro local. Confirme se a identidade do usuário AppPool tem permissão para gravar o caminho
fornecido.

<aspNetCore processPath="dotnet"
arguments=".\MyApp.dll"
stdoutLogEnabled="true"
stdoutLogFile="\\?\%home%\LogFiles\stdout">
</aspNetCore>

Consulte configuração com a Web. config para obter um exemplo de aspNetCore elemento no Web. config
arquivo.
A configuração de proxy usa o protocolo HTTP e um token de
emparelhamento
O proxy criado entre o módulo do ASP.NET Core e Kestrel usa o protocolo HTTP. Usando HTTP é uma
otimização de desempenho, onde o tráfego entre o módulo e Kestrel ocorre em um endereço de loopback da
interface de rede. Não há nenhum risco de espionagem o tráfego entre o módulo e Kestrel de um local fora do
servidor.
Um token de emparelhamento é usado para assegurar que as solicitações recebidas pelo Kestrel foram
transmitidas por proxy pelo IIS e que não são provenientes de outra origem. O token de emparelhamento é
criado e definido em uma variável de ambiente ( ASPNETCORE_TOKEN ) pelo módulo. O token de emparelhamento
também é definido em um cabeçalho ( MSAspNetCoreToken ) em cada solicitação com proxy. O Middleware do IIS
verifica cada solicitação recebida para confirmar se o valor de cabeçalho do token de emparelhamento
corresponde ao valor da variável de ambiente. Se os valores do token forem incompatíveis, a solicitação será
registrada em log e rejeitada. A variável de ambiente token emparelhamento e o tráfego entre o módulo e
Kestrel não são acessíveis a partir de um local fora do servidor. Sem saber o valor do token de
emparelhamento, um invasor não pode enviar solicitações que ignoram a verificação no Middleware do IIS.

Configuração do módulo principal do ASP.NET com um IIS


compartilhada
O instalador do módulo de núcleo do ASP.NET é executado com os privilégios do sistema conta. Como a
conta sistema local não ter modificar permissões para o caminho do compartilhamento usado por
configuração compartilhada de IIS, o instalador atinge um erro de acesso negado ao tentar definir as
configurações de módulo em applicationHost. config no compartilhamento. Ao usar uma configuração
compartilhada de IIS, siga estas etapas:
1. Desabilite a configuração compartilhada de IIS.
2. Execute o instalador.
3. Exportar atualizado applicationHost. config arquivo para o compartilhamento.
4. Habilite novamente a configuração compartilhada de IIS.

Versão do módulo e logs do instalador do pacote de hospedagem


Para determinar a versão do ASP.NET Core módulo instalado:
1. No sistema de hospedagem, navegue até %windir%\system32\inetsrv.
2. Localize o aspnetcore.dll arquivo.
3. Clique no arquivo e selecione propriedades no menu contextual.
4. Selecione o detalhes guia. O versão do arquivo e versão do produto representar a versão instalada do
módulo.
Os logs de instalador do pacote de hospedagem para o módulo são encontrados no c:\usuários\% UserName
%\AppData\Local\Temp. O arquivo é nomeado dd_DotNetCoreWinSvrHosting__<timestamp >
_000_AspNetCoreModule_x64.log.

Locais de arquivo do módulo, o esquema e a configuração


Módulo
IIS (x86/amd64):
%windir%\System32\inetsrv\aspnetcore.dll
%windir%\SysWOW64\inetsrv\aspnetcore.dll
O IIS Express (x86/amd64):
%ProgramFiles%\IIS Express\aspnetcore.dll
%ProgramFiles(x86)%\IIS Express\aspnetcore.dll
Esquema
IIS
%windir%\System32\inetsrv\config\schema\aspnetcore_schema.xml
IIS Express
%ProgramFiles%\IIS Express\config\schema\aspnetcore_schema.xml
Configuração
IIS
%windir%\System32\inetsrv\config\applicationHost.config
IIS Express
.vs\config\applicationHost.config
Os arquivos podem ser encontrados pesquisando aspnetcore.dll no applicationHost. config arquivo. Para o IIS
Express, o applicationHost. config arquivo não existe por padrão. O arquivo é criado no <application_root
>\VS\config ao iniciar qualquer projeto de aplicativo web na solução do Visual Studio.
Suporte ao IIS no tempo de desenvolvimento no
Visual Studio para ASP.NET Core
10/04/2018 • 2 min to read • Edit Online

Por Sourabh Shirhatti


Este artigo descreve Visual Studio suporte para depuração de aplicativos do ASP.NET Core em execução por trás
do IIS no Windows Server. Este tópico explica como habilitar esse recurso e configuração de um projeto.

Pré-requisitos
Visual Studio for Windows
ASP.NET and web development workload
.NET Core cross-platform development workload

Habilitar o IIS
Habilite o IIS. Navegue para Painel de Controle > Programas > Programas e Recursos > Ativar ou desativar
recursos do Windows (lado esquerdo da tela). Marque a caixa de seleção Serviços de Informações da
Internet.

Se a instalação do IIS exigir uma reinicialização, reinicie o sistema.

Habilitar suporte ao IIS no tempo de desenvolvimento


Inicie o instalador do Visual Studio. Selecione o IIS suporte de tempo de desenvolvimento componente. O
componente está listado como opcionais no resumo painel para o desenvolvimento ASP.NET e web carga de
trabalho. Isso instala o ASP.NET Core módulo, que é um módulo nativo do IIS necessário para executar o ASP.NET
Core aplicativos.
Configurar o projeto
Crie um novo perfil de inicialização para adicionar suporte ao IIS no tempo de desenvolvimento. No Gerenciador
de Soluções do Visual Studio, clique com o botão direito do mouse no projeto e selecione Propriedades.
Selecione a guia Depurar. Selecione IIS da lista suspensa Iniciar. Confirme se o recurso Iniciar Navegador está
habilitado com a URL correta.

Como alternativa, um perfil de lançamento para adicionar manualmente o launchSettings.json arquivo no


aplicativo:
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iis": {
"applicationUrl": "http://localhost/WebApplication2",
"sslPort": 0
}
},
"profiles": {
"IIS": {
"commandName": "IIS",
"launchBrowser": "true",
"launchUrl": "http://localhost/WebApplication2",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

O Visual Studio pode solicitar uma reinicialização se não executar como administrador. Se solicitado, reinicie o
Visual Studio.

Recursos adicionais
Hospedar o ASP.NET Core no Windows com o IIS
Introdução ao Módulo do ASP.NET Core
Referência de configuração do Módulo do ASP.NET Core
Módulos do IIS com o ASP.NET Core
27/04/2018 • 10 min to read • Edit Online

Por Luke Latham


Aplicativos ASP.NET Core são hospedados pelo IIS em uma configuração de proxy reverso. Alguns dos módulos
nativos do IIS e todos os módulos gerenciado do IIS não estão disponíveis para processar solicitações para
aplicativos do ASP.NET Core. Em muitos casos, o ASP.NET Core oferece uma alternativa para os recursos
gerenciados e nativos de módulos do IIS.

Módulos nativos
A tabela indica módulos nativos do IIS que estão funcionando em solicitações de proxy reverso para aplicativos
do ASP.NET Core.

FUNCIONAL COM OS APLICATIVOS


MÓDULO ASP.NET CORE OPÇÃO DE NÚCLEO DO ASP.NET

Autenticação anônima Sim


AnonymousAuthenticationModule

Autenticação básica Sim


BasicAuthenticationModule

Autenticação de mapeamento de Sim


certificação de cliente
CertificateMappingAuthenticationModule

CGI Não
CgiModule

Validação de configuração Sim


ConfigurationValidationModule

Erros HTTP Não Middleware de páginas de código de


CustomErrorModule status

Registro em log personalizado Sim


CustomLoggingModule

Documento padrão Não Middleware de arquivos padrão


DefaultDocumentModule

Autenticação Digest Sim


DigestAuthenticationModule

Pesquisa no Diretório Não Middleware de navegação de diretório


DirectoryListingModule

Compactação dinâmica Sim Middleware de compactação de


DynamicCompressionModule resposta
FUNCIONAL COM OS APLICATIVOS
MÓDULO ASP.NET CORE OPÇÃO DE NÚCLEO DO ASP.NET

Rastreamento Sim Registro do ASP.NET Core


FailedRequestsTracingModule

Cache de arquivo Não Middleware de Cache de Resposta


FileCacheModule

Cache de HTTP Não Middleware de Cache de Resposta


HttpCacheModule

Log HTTP Sim Registro do ASP.NET Core


HttpLoggingModule Implementações: elmah.io, Loggr,
NLog, Serilog

Redirecionamento de HTTP Sim Middleware de regravação de URL


HttpRedirectionModule

Autenticação de mapeamento de Sim


certificado de cliente do IIS
IISCertificateMappingAuthenticationModule

Restrições de IP e domínio Sim


IpRestrictionModule

Filtros ISAPI Sim Middleware


IsapiFilterModule

ISAPI Sim Middleware


IsapiModule

Suporte de protocolo Sim


ProtocolSupportModule

Filtragem de Solicitações Sim Middleware de regravação de URL


RequestFilteringModule IRule

Monitor de Solicitações Sim


RequestMonitorModule

Regravação de URL Sim† Middleware de regravação de URL


RewriteModule

Inclusões do lado do servidor Não


ServerSideIncludeModule

Compactação estática Não Middleware de compactação de


StaticCompressionModule resposta

Conteúdo Estático Não Middleware de arquivos estáticos


StaticFileModule

Cache de token Sim


TokenCacheModule
FUNCIONAL COM OS APLICATIVOS
MÓDULO ASP.NET CORE OPÇÃO DE NÚCLEO DO ASP.NET

Cache de URI Sim


UriCacheModule

Autorização de URL Sim Identidade do ASP.NET Core


UrlAuthorizationModule

Autenticação do Windows Sim


WindowsAuthenticationModule

†Do URL Rewrite Module isFile e isDirectory corresponde aos tipos não funciona com aplicativos do
ASP.NET Core devido a alterações em estrutura de diretórios.

Módulos gerenciados
Os módulos gerenciados são não funcional com aplicativos do ASP.NET Core hospedados quando a versão do
.NET CLR do pool de aplicativos é definido como sem código gerenciado. ASP.NET Core oferece alternativas
de middleware em vários casos.

MÓDULO OPÇÃO DE NÚCLEO DO ASP.NET

AnonymousIdentification

DefaultAuthentication

FileAuthorization

FormsAuthentication Middleware de autenticação de cookie

OutputCache Middleware de Cache de Resposta

Perfil

RoleManager

ScriptModule-4.0

Session Middleware de sessão

UrlAuthorization

UrlMappingsModule Middleware de regravação de URL

UrlRoutingModule-4.0 Identidade do ASP.NET Core

WindowsAuthentication

Alterações de aplicativo do Gerenciador do IIS


Ao usar o Gerenciador do IIS para definir as configurações, o Web. config o arquivo do aplicativo é alterado. Se a
implantação de um aplicativo e incluindo Web. config, todas as alterações feitas com o Gerenciador do IIS são
substituídas por implantado Web. config arquivo. Se forem feitas alterações para o servidor Web. config de
arquivo, copie o atualizada Web. config arquivo no servidor para o local do projeto imediatamente.

Desabilitando módulos do IIS


Se um módulo do IIS é configurado no nível do servidor deve ser desabilitado para um aplicativo, um acréscimo
para o aplicativo Web. config arquivo pode desabilitar o módulo. Deixe o módulo no lugar e desativá-lo usando
um parâmetro de configuração (se disponível) ou remover o módulo do aplicativo.
Desativação do módulo
Muitos módulos oferecem uma configuração que permite que eles sejam desabilitadas sem remover o módulo
do aplicativo. Essa é a maneira mais simples e rápida para desativar um módulo. Por exemplo, o módulo de
redirecionamento de HTTP pode ser desabilitado com o <httpRedirect > elemento Web. config:

<configuration>
<system.webServer>
<httpRedirect enabled="false" />
</system.webServer>
</configuration>

Para obter mais informações sobre como desativar módulos com definições de configuração, siga os links a
elementos filho seção IIS <System. webServer >.
Remoção do módulo
Se a opção para remover um módulo com uma configuração em Web. config, desbloqueie o módulo e
desbloquear o <módulos > seção Web. config primeiro:
1. Desbloquear o módulo no nível do servidor. Selecione o servidor do IIS no Gerenciador do IIS conexões
barra lateral. Abra o módulos no IIS área. Selecione o módulo na lista. No ações barra lateral à direita,
selecione Unlock. Desbloquear tantos módulos que você planeja remover de Web. config mais tarde.
2. Implantar o aplicativo sem um <módulos > seção Web. config. Se um aplicativo é implantado com um
Web. config que contém o <módulos > seção sem ter a seção desbloqueada pela primeira vez no
Gerenciador do IIS, o Configuration Manager gera uma exceção ao tentar desbloquear a seção. Portanto,
implante o aplicativo sem um <módulos > seção.
3. Desbloquear o <módulos > seção Web. config. No conexões barra lateral, selecione o site em Sites. No
gerenciamento área, abra o Editor de configuração. Use os controles de navegação para selecionar o
system.webServer/modules seção. No ações barra lateral à direita, selecione a desbloquear a seção.

4. Neste ponto, um <módulos > seção pode ser adicionada para o Web. config arquivo com um <remover
> elemento para remover o módulo de o aplicativo. Vários <remover > elementos podem ser adicionados
para remover vários módulos. Se Web. config alterações são feitas no servidor, imediatamente fazer as
mesmas alterações no projeto Web. config arquivo localmente. Remover um módulo dessa maneira não
afetará o uso do módulo com outros aplicativos no servidor.

<configuration>
<system.webServer>
<modules>
<remove name="MODULE_NAME" />
</modules>
</system.webServer>
</configuration>

Um módulo do IIS também pode ser removido com Appcmd.exe. Forneça o MODULE_NAME e APPLICATION_NAME no
comando:
Appcmd.exe delete module MODULE_NAME /app.name:APPLICATION_NAME

Por exemplo, remover o DynamicCompressionModule do Site da Web padrão:

%windir%\system32\inetsrv\appcmd.exe delete module DynamicCompressionModule /app.name:"Default Web Site"

Configuração do módulo mínimo


Somente módulos necessários para executar um aplicativo ASP.NET Core são o módulo de autenticação anônima
e o módulo do ASP.NET Core.

O módulo de cache de URI ( UriCacheModule ) permite que o IIS para a configuração de site do cache no nível da
URL. Sem esse módulo, o IIS deve ler e analisar a configuração em cada solicitação, mesmo quando a mesma
URL é solicitada repetidamente. Cada solicitação ao analisar a configuração resulta em uma penalidade de
desempenho significativa. Embora o módulo de cache de URI não é estritamente necessário para um aplicativo
ASP.NET Core hospedado para ser executado, é recomendável que o módulo de cache de URI seja habilitado
para todas as implantações do ASP.NET Core.
O módulo HTTP do cache ( HttpCacheModule ) implementa o cache de saída do IIS e também a lógica de cache de
itens no cache do HTTP. sys. Sem esse módulo, conteúdo não é armazenado em cache no modo kernel e perfis de
cache são ignorados. Remover o módulo HTTP do cache geralmente tem um efeito adverso no desempenho e
uso de recursos. Embora o módulo HTTP do cache não é estritamente necessário para um aplicativo ASP.NET
Core hospedado para ser executado, é recomendável que o módulo HTTP do cache esteja habilitada para todas
as implantações do ASP.NET Core.

Recursos adicionais
Hospedar no Windows com o IIS
Introdução às arquiteturas IIS: módulos no IIS
Visão geral de módulos do IIS
Personalizando 7.0 funções e módulos do IIS
IIS <system.webServer>
Host ASP.NET Core em um serviço do Windows
10/04/2018 • 7 min to read • Edit Online

Por Tom Dykstra


A maneira recomendada para hospedar um aplicativo ASP.NET Core no Windows sem usar o IIS é executá-lo
uma serviço Windows. Quando hospedado como um serviço do Windows, o aplicativo pode automaticamente
iniciar após reinicializar e falhas sem exigir intervenção humana.
Exibir ou baixar um código de exemplo (como baixar). Para obter instruções sobre como executar o aplicativo de
exemplo, consulte o exemplo README.md arquivo.

Pré-requisitos
O aplicativo deve ser executado em tempo de execução do .NET Framework. No . csproj de arquivos,
especifique os valores apropriados para TargetFramework e RuntimeIdentifier. Veja um exemplo:

<PropertyGroup>
<TargetFramework>net461</TargetFramework>
<RuntimeIdentifier>win7-x64</RuntimeIdentifier>
</PropertyGroup>

Ao criar um projeto no Visual Studio, use o aplicativo do ASP.NET Core (.NET Framework) modelo.
Se o aplicativo recebe solicitações de Internet (não apenas a partir de uma rede interna), ele deve usar o
HTTP.sys servidor web (anteriormente conhecida como WebListener para aplicativos do ASP.NET Core 1. x)
em vez de Kestrel. O IIS é recomendado para uso como um servidor proxy reverso com Kestrel para
implantações de borda. Para obter mais informações, consulte Quando usar Kestrel com um proxy reverso.

Introdução
Esta seção explica as alterações mínimas necessárias para configurar um projeto existente do ASP.NET Core para
executar em um serviço.
1. Instale o pacote NuGet Microsoft.AspNetCore.Hosting.WindowsServices.
2. Faça as seguintes alterações em Program.Main :
Chamar host.RunAsService em vez de host.Run .
Se o código chama UseContentRoot , use um caminho para o local de publicação em vez de
Directory.GetCurrentDirectory() .
ASP.NET Core 2.x
ASP.NET Core 1.x
public static void Main(string[] args)
{
var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
var pathToContentRoot = Path.GetDirectoryName(pathToExe);

var host = WebHost.CreateDefaultBuilder(args)


.UseContentRoot(pathToContentRoot)
.UseStartup<Startup>()
.UseApplicationInsights()
.Build();

host.RunAsService();
}

3. Publica o aplicativo em uma pasta. Use dotnet publicar ou um perfil de publicação do Visual Studio que
publica uma pasta.
4. Teste ao criar e iniciar o serviço.
Abra um shell de comando com privilégios administrativos para usar o sc.exe ferramenta de linha de
comando para criar e iniciar um serviço. Se o serviço é chamado MyService, publicados c:\svc , e
denominado AspNetCoreService, os comandos são:

sc create MyService binPath="c:\svc\aspnetcoreservice.exe"


sc start MyService

O binPath valor é o caminho para o executável do aplicativo, que inclui o nome do arquivo executável.

Quando terminar desses comandos, navegue até o mesmo caminho que durante a execução como um
aplicativo de console (por padrão, http://localhost:5000 ):
Fornecem uma maneira de executar fora de um serviço
É mais fácil de testar e depurar quando em execução fora de um serviço, portanto, é comum para adicionar o
código que chama RunAsService apenas em determinadas condições. Por exemplo, o aplicativo pode ser
executado como um aplicativo de console com um --console argumento de linha de comando ou se o depurador
é anexado:
ASP.NET Core 2.x
ASP.NET Core 1.x

public static void Main(string[] args)


{
bool isService = true;
if (Debugger.IsAttached || args.Contains("--console"))
{
isService = false;
}

var pathToContentRoot = Directory.GetCurrentDirectory();


if (isService)
{
var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
pathToContentRoot = Path.GetDirectoryName(pathToExe);
}

var host = WebHost.CreateDefaultBuilder(args)


.UseContentRoot(pathToContentRoot)
.UseStartup<Startup>()
.UseApplicationInsights()
.Build();

if (isService)
{
host.RunAsService();
}
else
{
host.Run();
}
}

Tratar parar e iniciar eventos


Para tratar OnStarting , OnStarted ,e OnStopping eventos, faça as seguintes alterações adicionais:
1. Criar uma classe que deriva de WebHostService :
internal class CustomWebHostService : WebHostService
{
public CustomWebHostService(IWebHost host) : base(host)
{
}

protected override void OnStarting(string[] args)


{
base.OnStarting(args);
}

protected override void OnStarted()


{
base.OnStarted();
}

protected override void OnStopping()


{
base.OnStopping();
}
}

2. Criar um método de extensão para IWebHost que passa personalizado WebHostService para
ServiceBase.Run :

public static class WebHostServiceExtensions


{
public static void RunAsCustomService(this IWebHost host)
{
var webHostService = new CustomWebHostService(host);
ServiceBase.Run(webHostService);
}
}

3. Em Program.Main , chame o novo método de extensão, RunAsCustomService , em vez de RunAsService :


ASP.NET Core 2.x
ASP.NET Core 1.x
public static void Main(string[] args)
{
bool isService = true;
if (Debugger.IsAttached || args.Contains("--console"))
{
isService = false;
}

var pathToContentRoot = Directory.GetCurrentDirectory();


if (isService)
{
var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
pathToContentRoot = Path.GetDirectoryName(pathToExe);
}

var host = WebHost.CreateDefaultBuilder(args)


.UseContentRoot(pathToContentRoot)
.UseStartup<Startup>()
.UseApplicationInsights()
.Build();

if (isService)
{
host.RunAsCustomService();
}
else
{
host.Run();
}
}

Se personalizado WebHostService código requer que um serviço de injeção de dependência (como um agente de
log), ele obtido o Services propriedade IWebHost :

internal class CustomWebHostService : WebHostService


{
private ILogger _logger;

public CustomWebHostService(IWebHost host) : base(host)


{
_logger = host.Services.GetRequiredService<ILogger<CustomWebHostService>>();
}

protected override void OnStarting(string[] args)


{
_logger.LogDebug("OnStarting method called.");
base.OnStarting(args);
}

protected override void OnStarted()


{
_logger.LogDebug("OnStarted method called.");
base.OnStarted();
}

protected override void OnStopping()


{
_logger.LogDebug("OnStopping method called.");
base.OnStopping();
}
}
Servidor proxy e cenários de Balanceador de carga
Serviços que interagem com solicitações da Internet ou de uma rede corporativa e estiverem atrás de um proxy ou
balanceador de carga podem exigir configuração adicional. Para obter mais informações, consulte configurar o
ASP.NET Core para trabalhar com servidores proxy e balanceadores de carga.

Agradecimentos
Este artigo foi escrito com a Ajuda das fontes publicados:
Hospedagem ASP.NET Core como serviço do Windows
Como hospedar o ASP.NET Core em um serviço do Windows
Host ASP.NET Core no Linux com Nginx
10/04/2018 • 17 min to read • Edit Online

Por Sourabh Shirhatti


Este guia explica como configurar um ambiente do ASP.NET Core pronto para produção em um servidor do
Ubuntu 16.04.

OBSERVAÇÃO
Para Ubuntu 14.04 supervisord é recomendado como uma solução para monitorar o processo de Kestrel. systemd não
está disponível no Ubuntu 14.04. Consulte a versão anterior deste documento.

Este guia:
Coloca um aplicativo ASP.NET Core existente em um servidor proxy reverso.
Configura o servidor proxy inverso para encaminhar solicitações ao servidor da web Kestrel.
Garante que o aplicativo web é executado na inicialização como um daemon.
Configura uma ferramenta de gerenciamento de processo para ajudar a reiniciar o aplicativo web.

Pré-requisitos
1. Acesso a um Servidor Ubuntu 16.04 com uma conta de usuário padrão com privilégio sudo
2. Um aplicativo ASP.NET Core existente

Copie o aplicativo
Executar dotnet publicar do ambiente de desenvolvimento para empacotar um aplicativo em um diretório
independente que pode ser executados no servidor.
O aplicativo ASP.NET Core para o servidor usando qualquer ferramenta de cópia se integra ao fluxo de trabalho
da organização (por exemplo, "SCP", "FTP"). Teste o aplicativo, por exemplo:
Na linha de comando, execute dotnet <app_assembly>.dll .
Em um navegador, navegue até http://<serveraddress>:<port> para verificar se o aplicativo funciona no
Linux.

Configurar um servidor proxy reverso


Um proxy reverso é uma instalação comum para que serve a aplicativos web dinâmicos. Um proxy reverso
encerra a solicitação HTTP e as encaminha para o aplicativo ASP.NET Core.
Por que usar um servidor proxy reverso?
Kestrel é excelente para servir conteúdo dinâmico do ASP.NET Core. No entanto, os recursos de servidor web
não são como muitos recursos como servidores, como o IIS, o Apache ou Nginx. Um servidor proxy reverso
pode descarregar o trabalho, como que serve o conteúdo estático, solicitações de cache, a compactação de
solicitações e a terminação de SSL do servidor HTTP. Um servidor proxy reverso pode residir em um
computador dedicado ou pode ser implantado junto com um servidor HTTP.
Para os fins deste guia, uma única instância de Nginx é usada. Ela é executada no mesmo servidor, junto com o
servidor HTTP. Com base nos requisitos, uma configuração diferente pode ser escolhida.
Como as solicitações são encaminhadas pelo proxy reverso, use o Middleware de cabeçalhos encaminhados do
Microsoft.AspNetCore.HttpOverrides pacote. As atualizações de middleware de Request.Scheme , usando o
X-Forwarded-Proto cabeçalho, de forma que URIs de redirecionamento e outras políticas de segurança
funcionam corretamente.
Ao usar qualquer tipo de middleware de autenticação, o Middleware de cabeçalhos encaminhado deve ser
executado primeiro. Essa ordenação garantirá que o middleware de autenticação pode consumir os valores de
cabeçalho e gerar URIs de redirecionamento correto.
ASP.NET Core 2.x
ASP.NET Core 1.x
Invocar o UseForwardedHeaders método Startup.Configure antes de chamar UseAuthentication ou
semelhante middleware de esquema de autenticação. Configurar o middleware para encaminhar o
X-Forwarded-For e X-Forwarded-Proto cabeçalhos:

app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});

app.UseAuthentication();

Se nenhum ForwardedHeadersOptions são especificados para o middleware, os cabeçalhos padrão para


encaminhar são None .
Configuração adicional pode ser necessária para aplicativos hospedados por trás de servidores proxy e
balanceadores de carga. Para obter mais informações, consulte configurar o ASP.NET Core para trabalhar com
servidores proxy e balanceadores de carga.
Instalar o Nginx

sudo apt-get install nginx

OBSERVAÇÃO
Se módulos Nginx opcionais serão instalados, pode ser necessário criar Nginx da fonte.

Use apt-get para instalar o Nginx. O instalador cria um script de inicialização System V que executa o Nginx
como daemon na inicialização do sistema. Já que Nginx foi instalado pela primeira vez, inicie-o explicitamente
executando:

sudo service nginx start

Verifique se um navegador exibe a página de aterrissagem padrão do Nginx.


Configurar o Nginx
Para configurar Nginx como um proxy inverso para encaminhar solicitações para seu aplicativo ASP.NET Core,
modifique /etc/nginx/sites-available/default. Abra-o em um editor de texto arquivo e substitua o conteúdo pelo
mostrado a seguir:
server {
listen 80;
server_name example.com *.example.com;
location / {
proxy_pass http://localhost:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $http_host;
proxy_cache_bypass $http_upgrade;
}
}

Quando nenhum server_name correspondências, Nginx usa o servidor padrão. Se nenhum servidor padrão for
definido, o primeiro servidor no arquivo de configuração é o servidor padrão. Como prática recomendada,
adicione um servidor de padrão específico que retorna um código de status de 444 no arquivo de configuração.
Um exemplo de configuração de servidor padrão é:

server {
listen 80 default_server;
# listen [::]:80 default_server deferred;
return 444;
}

Com anterior servidor de configuração de arquivo e padrão, Nginx aceita tráfego público na porta 80 com um
cabeçalho de host example.com ou *.example.com . Solicitações que não correspondem a esses hosts não forem
encaminhadas para Kestrel. Nginx encaminha as solicitações de correspondência para Kestrel em
http://localhost:5000 . Consulte como nginx processa uma solicitação para obter mais informações.

AVISO
Falha ao especificar apropriadas server_name diretiva expõe seu aplicativo para vulnerabilidades de segurança. Associação
de curinga de subdomínio (por exemplo, *.example.com ) não apresenta esse risco de segurança se você controlar o
domínio pai inteira (em vez de *.com , que é vulnerável). Veja rfc7230 section-5.4 para obter mais informações.

Quando a configuração Nginx é estabelecida, execute sudo nginx -t para verificar a sintaxe dos arquivos de
configuração. Se o teste do arquivo de configuração for bem-sucedida, forçar Nginx para acompanhar as
alterações executando sudo nginx -s reload .

Monitoramento do aplicativo
O servidor está configurado para encaminhar solicitações feitas a http://<serveraddress>:80 para o aplicativo
ASP.NET Core em execução em Kestrel em http://127.0.0.1:5000 . No entanto, Nginx não está configurado
para gerenciar o processo de Kestrel. systemd pode ser usado para criar um arquivo de serviço para iniciar e
monitorar o aplicativo web subjacente. systemd é um sistema de inicialização que fornece muitos recursos
poderosos para iniciar, parar e gerenciar processos.
Criar o arquivo de serviço
Crie o arquivo de definição de serviço:

sudo nano /etc/systemd/system/kestrel-hellomvc.service

Este é um exemplo de arquivo de serviço para o aplicativo:


[Unit]
Description=Example .NET Web API App running on Ubuntu

[Service]
WorkingDirectory=/var/aspnetcore/hellomvc
ExecStart=/usr/bin/dotnet /var/aspnetcore/hellomvc/hellomvc.dll
Restart=always
RestartSec=10 # Restart service after 10 seconds if dotnet service crashes
SyslogIdentifier=dotnet-example
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target

Observação: se o usuário www dados não é usado pela configuração do usuário definido aqui deve ser criado
primeiro e dado propriedade adequada para arquivos. Observação: Linux tem um sistema de arquivos
diferencia maiusculas de minúsculas. Definindo ASPNETCORE_ENVIRONMENT como resultados de
"Produção" na pesquisa do arquivo de configuração appsettings. Production.JSON, não
appsettings.production.json.
Salve o arquivo e habilite o serviço.

systemctl enable kestrel-hellomvc.service

Inicie o serviço e verifique se ele está em execução.

systemctl start kestrel-hellomvc.service


systemctl status kestrel-hellomvc.service

● kestrel-hellomvc.service - Example .NET Web API App running on Ubuntu


Loaded: loaded (/etc/systemd/system/kestrel-hellomvc.service; enabled)
Active: active (running) since Thu 2016-10-18 04:09:35 NZDT; 35s ago
Main PID: 9021 (dotnet)
CGroup: /system.slice/kestrel-hellomvc.service
└─9021 /usr/local/bin/dotnet /var/aspnetcore/hellomvc/hellomvc.dll

Com o proxy reverso configurado e Kestrel gerenciadas por meio de systemd, o aplicativo web está totalmente
configurado e pode ser acessado a partir de um navegador no computador local em http://localhost . Também
é acessível a partir de um computador remoto, bloqueio qualquer firewall pode estar bloqueando.
Inspecionando os cabeçalhos de resposta, o Server cabeçalho mostra o aplicativo do ASP.NET Core sendo
servido por Kestrel.

HTTP/1.1 200 OK
Date: Tue, 11 Oct 2016 16:22:23 GMT
Server: Kestrel
Keep-Alive: timeout=5, max=98
Connection: Keep-Alive
Transfer-Encoding: chunked

Exibindo logs
Desde que o aplicativo web usar Kestrel é gerenciada usando systemd , todos os eventos e os processos são
registrados para um diário centralizado. No entanto, esse diário contém todas as entradas para todos os
serviços e processos gerenciados pelo systemd . Para exibir os itens específicos de kestrel-hellomvc.service ,
use o seguinte comando:
sudo journalctl -fu kestrel-hellomvc.service

Para obter mais filtragem, opções de hora como --since today , --until 1 hour ago ou uma combinação delas,
pode reduzir a quantidade de entradas retornadas.

sudo journalctl -fu kestrel-hellomvc.service --since "2016-10-18" --until "2016-10-18 04:00"

Protegendo o aplicativo
Habilitar AppArmor
Módulos de segurança do Linux (LSM ) é uma estrutura que é parte do kernel do Linux desde 2.6 do Linux. O
LSM dá suporte a diferentes implementações de módulos de segurança. O AppArmor é um LSM que
implementa um sistema de controle de acesso obrigatório que permite restringir o programa a um conjunto
limitado de recursos. Verifique se o AppArmor está habilitado e configurado corretamente.
Configurando o firewall
Feche todas as portas externas que não estão em uso. Firewall descomplicado (ufw ) fornece um front-end para
iptables fornecendo uma interface de linha de comando para configurar o firewall. Verifique ufw está
configurado para permitir o tráfego em todas as portas necessárias.

sudo apt-get install ufw


sudo ufw enable

sudo ufw allow 80/tcp


sudo ufw allow 443/tcp

Protegendo o Nginx
A distribuição padrão do Nginx não habilita o SSL. Para habilitar recursos de segurança adicionais, compile da
origem.
Baixe a origem e instale as dependências de build

# Install the build dependencies


sudo apt-get update
sudo apt-get install build-essential zlib1g-dev libpcre3-dev libssl-dev libxslt1-dev libxml2-dev libgd2-xpm-
dev libgeoip-dev libgoogle-perftools-dev libperl-dev

# Download Nginx 1.10.0 or latest


wget http://www.nginx.org/download/nginx-1.10.0.tar.gz
tar zxf nginx-1.10.0.tar.gz

Alterar o nome da resposta do Nginx


Edite src/http/ngx_http_header_filter_module.c:

static char ngx_http_server_string[] = "Server: Web Server" CRLF;


static char ngx_http_server_full_string[] = "Server: Web Server" CRLF;

Configurar as opções e compilar


A biblioteca PCRE é necessária para expressões regulares. Expressões regulares são usadas na diretiva local
para o ngx_http_rewrite_module. O http_ssl_module adiciona suporte a protocolo HTTPS.
Considere o uso de um firewall de aplicativo da web como ModSecurity para proteger o aplicativo.
./configure
--with-pcre=../pcre-8.38
--with-zlib=../zlib-1.2.8
--with-http_ssl_module
--with-stream
--with-mail=dynamic

Configurar o SSL
Configurar o servidor para ouvir o tráfego HTTPS na porta 443 especificando um certificado válido
emitido por uma autoridade de certificação confiável (CA).
Proteger a segurança, empregando algumas das práticas descritas na seguinte /etc/nginx/nginx.conf
arquivo. Exemplos incluem a escolha de uma criptografia mais forte e o redirecionamento de todo o
tráfego por meio de HTTP para HTTPS.
Adicionar um cabeçalho HTTP Strict-Transport-Security (HSTS ) garante que todas as solicitações
subsequentes feitas pelo cliente sejam apenas via HTTPS.
Não adicione o cabeçalho de segurança de transporte Strict ou escolher um apropriado max-age se SSL
será desabilitado no futuro.
Adicione o arquivo de configuração /etc/nginx/proxy.conf:

proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffers 32 4k;

Edite o arquivo de configuração /etc/nginx/nginx.conf. O exemplo contém ambas as seções http e server em
um arquivo de configuração.
http {
include /etc/nginx/proxy.conf;
limit_req_zone $binary_remote_addr zone=one:10m rate=5r/s;
server_tokens off;

sendfile on;
keepalive_timeout 29; # Adjust to the lowest possible value that makes sense for your use case.
client_body_timeout 10; client_header_timeout 10; send_timeout 10;

upstream hellomvc{
server localhost:5000;
}

server {
listen *:80;
add_header Strict-Transport-Security max-age=15768000;
return 301 https://$host$request_uri;
}

server {
listen *:443 ssl;
server_name example.com;
ssl_certificate /etc/ssl/certs/testCert.crt;
ssl_certificate_key /etc/ssl/certs/testCert.key;
ssl_protocols TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on; #ensure your cert is capable
ssl_stapling_verify on; #ensure your cert is capable

add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";


add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

#Redirects all traffic


location / {
proxy_pass http://hellomvc;
limit_req zone=one burst=10 nodelay;
}
}
}

Proteger o Nginx de clickjacking


Clickjacking é uma técnica mal-intencionada para coletar cliques de um usuário infectado. O clickjacking engana
a vítima (visitante) fazendo-a clicar em um site infectado. Use X-FRAME -OPTIONS para proteger o site.
Edite o arquivo nginx.conf:

sudo nano /etc/nginx/nginx.conf

Adicione a linha add_header X-Frame-Options "SAMEORIGIN"; e salve o arquivo, depois reinicie o Nginx.
Detecção de tipo MIME
Esse cabeçalho evita que a maioria dos navegadores faça detecção MIME de uma resposta distante do tipo de
conteúdo declarado, visto que o cabeçalho instrui o navegador para não substituir o tipo de conteúdo de
resposta. Com a opção nosniff , se o servidor informa que o conteúdo é "text/html", o navegador renderiza-a
como "text/html".
Edite o arquivo nginx.conf:
sudo nano /etc/nginx/nginx.conf

Adicione a linha add_header X-Content-Type-Options "nosniff"; e salve o arquivo, depois reinicie o Nginx.
Hospedar o ASP.NET Core no Linux com o Apache
10/04/2018 • 16 min to read • Edit Online

Por Shayne Boyer


Usando este guia, saiba como configurar Apache como um servidor de proxy reverso na CentOS 7 para
redirecionar o tráfego de HTTP para um aplicativo web do ASP.NET Core em execução no Kestrel. O mod_proxy
extensão e módulos relacionados Criar proxy reverso do servidor.

Pré-requisitos
1. Servidor que executa o CentOS 7 com uma conta de usuário padrão com privilégios sudo
2. Aplicativo do ASP.NET Core

Publique o aplicativo
Publicar o aplicativo como um implantação autossuficiente na configuração de versão para o tempo de
execução do CentOS 7 ( centos.7-x64 ). Copie o conteúdo de bin/Release/netcoreapp2.0/centos.7 -x64/publish
pasta ao servidor usando o SCP, FTP ou outro método de transferência de arquivo.

OBSERVAÇÃO
Em um cenário de implantação de produção, um fluxo de trabalho de integração contínua faz o trabalho de publicação do
aplicativo e copiar os ativos para o servidor.

Configurar um servidor proxy


Um proxy reverso é uma instalação comum para que serve a aplicativos web dinâmicos. O proxy inverso
encerra a solicitação HTTP e as encaminha para o aplicativo ASP.NET.
Um servidor proxy é uma que encaminha as solicitações de cliente para outro servidor, em vez de atender às
solicitações em si. Um proxy reverso encaminha para um destino fixo, normalmente, em nome de clientes
arbitrários. Neste guia, Apache é configurado como o proxy inverso em execução no mesmo servidor que
Kestrel está atendendo o aplicativo ASP.NET Core.
Como as solicitações são encaminhadas pelo proxy reverso, use o Middleware de cabeçalhos encaminhados do
Microsoft.AspNetCore.HttpOverrides pacote. As atualizações de middleware de Request.Scheme , usando o
X-Forwarded-Proto cabeçalho, de forma que URIs de redirecionamento e outras políticas de segurança
funcionam corretamente.
Ao usar qualquer tipo de middleware de autenticação, o Middleware de cabeçalhos encaminhado deve ser
executado primeiro. Essa ordenação garantirá que o middleware de autenticação pode consumir os valores de
cabeçalho e gerar URIs de redirecionamento correto.
ASP.NET Core 2.x
ASP.NET Core 1.x
Invocar o UseForwardedHeaders método Startup.Configure antes de chamar UseAuthentication ou
semelhante middleware de esquema de autenticação. Configurar o middleware para encaminhar o
X-Forwarded-For e X-Forwarded-Proto cabeçalhos:
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});

app.UseAuthentication();

Se nenhum ForwardedHeadersOptions são especificados para o middleware, os cabeçalhos padrão para


encaminhar são None .
Configuração adicional pode ser necessária para aplicativos hospedados por trás de servidores proxy e
balanceadores de carga. Para obter mais informações, consulte configurar o ASP.NET Core para trabalhar com
servidores proxy e balanceadores de carga.
Instalar o Apache
CentOS pacotes de atualização de suas versões estável mais recente:

sudo yum update -y

Instalar o servidor web Apache em CentOS com um único yum comando:

sudo yum -y install httpd mod_ssl

Exemplo de saída depois de executar o comando:

Downloading packages:
httpd-2.4.6-40.el7.centos.4.x86_64.rpm | 2.7 MB 00:00:01
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
Installing : httpd-2.4.6-40.el7.centos.4.x86_64 1/1
Verifying : httpd-2.4.6-40.el7.centos.4.x86_64 1/1

Installed:
httpd.x86_64 0:2.4.6-40.el7.centos.4

Complete!

OBSERVAÇÃO
Neste exemplo, a saída reflete httpd.86_64 desde a versão CentOS 7 é de 64 bits. Para verificar o local em que o Apache
está instalado, execute whereis httpd em um prompt de comando.

Configurar o Apache para proxy reverso


Os arquivos de configuração do Apache estão localizados no diretório /etc/httpd/conf.d/ . Qualquer arquivo
com o .conf extensão é processada em ordem alfabética, além dos arquivos de configuração do módulo em
/etc/httpd/conf.modules.d/ , que contém todas as configurações de arquivos necessário para carregar módulos.

Crie um arquivo de configuração, chamado hellomvc.conf, para o aplicativo:


<VirtualHost *:80>
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:5000/
ProxyPassReverse / http://127.0.0.1:5000/
ServerName www.example.com
ServerAlias *.example.com
ErrorLog ${APACHE_LOG_DIR}hellomvc-error.log
CustomLog ${APACHE_LOG_DIR}hellomvc-access.log common
</VirtualHost>

O VirtualHost bloco pode aparecer várias vezes, em um ou mais arquivos em um servidor. No arquivo de
configuração anterior, Apache aceita tráfego público na porta 80. O domínio www.example.com é mantido e o
*.example.com alias resolve para o mesmo site. Consulte suporte baseado em nome de host virtual para obter
mais informações. As solicitações são delegadas na raiz para a porta 5000 do servidor no 127.0.0.1. Para a
comunicação bidirecional, ProxyPass e ProxyPassReverse são necessários.

AVISO
Falha ao especificar apropriadas diretiva ServerName no VirtualHost bloco expõe seu aplicativo para vulnerabilidades de
segurança. Associação de curinga de subdomínio (por exemplo, *.example.com ) não apresenta esse risco de segurança
se você controlar o domínio pai inteira (em vez de *.com , que é vulnerável). Veja rfc7230 section-5.4 para obter mais
informações.

Registro em log pode ser configurado por VirtualHost usando ErrorLog e CustomLog diretivas. ErrorLog é o
local onde o servidor registra erros, e CustomLog define o nome de arquivo e o formato do arquivo de log.
Nesse caso, isso é onde as informações de solicitação são registradas. Há uma linha para cada solicitação.
Salve o arquivo e testar a configuração. Se tudo passar, a resposta deverá ser Syntax [OK] .

sudo service httpd configtest

Reinicie o Apache:

sudo systemctl restart httpd


sudo systemctl enable httpd

Monitoramento do aplicativo
Apache agora está configurado para encaminhar solicitações feitas a http://localhost:80 para o aplicativo
ASP.NET Core em execução em Kestrel em http://127.0.0.1:5000 . No entanto, o Apache não está configurado
para gerenciar o processo de Kestrel. Use systemd e crie um arquivo de serviço para iniciar e monitorar o
aplicativo web subjacente. systemd é um sistema de inicialização que fornece muitos recursos poderosos para
iniciar, parar e gerenciar processos.
Criar o arquivo de serviço
Crie o arquivo de definição de serviço:

sudo nano /etc/systemd/system/kestrel-hellomvc.service

Um arquivo de serviço de exemplo para o aplicativo:


[Unit]
Description=Example .NET Web API App running on CentOS 7

[Service]
WorkingDirectory=/var/aspnetcore/hellomvc
ExecStart=/usr/local/bin/dotnet /var/aspnetcore/hellomvc/hellomvc.dll
Restart=always
# Restart service after 10 seconds if dotnet service crashes
RestartSec=10
SyslogIdentifier=dotnet-example
User=apache
Environment=ASPNETCORE_ENVIRONMENT=Production

[Install]
WantedBy=multi-user.target

OBSERVAÇÃO
Usuário — se o usuário apache não é usado pela configuração, o usuário deve criado pela primeira vez e determinado
propriedade adequada de arquivos.

Salve o arquivo e habilitar o serviço:

systemctl enable kestrel-hellomvc.service

Inicie o serviço e verifique se ele está em execução:

systemctl start kestrel-hellomvc.service


systemctl status kestrel-hellomvc.service

● kestrel-hellomvc.service - Example .NET Web API App running on CentOS 7


Loaded: loaded (/etc/systemd/system/kestrel-hellomvc.service; enabled)
Active: active (running) since Thu 2016-10-18 04:09:35 NZDT; 35s ago
Main PID: 9021 (dotnet)
CGroup: /system.slice/kestrel-hellomvc.service
└─9021 /usr/local/bin/dotnet /var/aspnetcore/hellomvc/hellomvc.dll

Com o proxy reverso configurado e gerenciadas por meio de Kestrel systemd, o aplicativo web está totalmente
configurado e pode ser acessado a partir de um navegador no computador local em http://localhost .
Inspecionando os cabeçalhos de resposta, o Server cabeçalho indica que o aplicativo ASP.NET Core é atendido
por Kestrel:

HTTP/1.1 200 OK
Date: Tue, 11 Oct 2016 16:22:23 GMT
Server: Kestrel
Keep-Alive: timeout=5, max=98
Connection: Keep-Alive
Transfer-Encoding: chunked

Exibindo logs
Desde que o aplicativo web usar Kestrel é gerenciada usando systemd, eventos e os processos são registrados
para um diário centralizado. No entanto, esse diário contém entradas para todos os serviços e processos
gerenciados pelo systemd. Para exibir os itens específicos de kestrel-hellomvc.service , use o seguinte
comando:
sudo journalctl -fu kestrel-hellomvc.service

Para filtragem de hora, especifica opções de tempo com o comando. Por exemplo, use --since today para filtrar
o dia atual ou --until 1 hour ago para ver as entradas da hora anterior. Para obter mais informações, consulte
o página para journalctl.

sudo journalctl -fu kestrel-hellomvc.service --since "2016-10-18" --until "2016-10-18 04:00"

Protegendo o aplicativo
Configurar o firewall
Firewalld é um daemon dinâmico para gerenciar o firewall com suporte para zonas de rede. Portas e filtragem
de pacote ainda podem ser gerenciados pelo iptables. Firewalld devem ser instalados por padrão. yum pode ser
usado para instalar o pacote ou verifique se que ele está instalado.

sudo yum install firewalld -y

Use firewalld para abrir somente as portas necessárias para o aplicativo. Nesse caso, as portas 80 e 443 são
usadas. Os comandos a seguir definem permanentemente as portas 80 e 443 para abrir:

sudo firewall-cmd --add-port=80/tcp --permanent


sudo firewall-cmd --add-port=443/tcp --permanent

Recarrega as configurações de firewall. Verifique os serviços disponíveis e as portas na zona padrão. Opções
estão disponíveis por meio da inspeção firewall-cmd -h .

sudo firewall-cmd --reload


sudo firewall-cmd --list-all

public (default, active)


interfaces: eth0
sources:
services: dhcpv6-client
ports: 443/tcp 80/tcp
masquerade: no
forward-ports:
icmp-blocks:
rich rules:

Configuração do SSL
Para configurar o Apache para SSL, o mod_ssl módulo é usado. Quando o httpd módulo foi instalado, o mod_ssl
módulo também foi instalado. Se ele não foi instalado, use yum para adicioná-lo para a configuração.

sudo yum install mod_ssl

Para impor a SSL, instale o mod_rewrite módulo para habilitar a regravação de URL:

sudo yum install mod_rewrite


Modificar o hellomvc.conf arquivo para habilitar a regravação de URL e proteger a comunicação na porta 443:

<VirtualHost *:80>
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^/?(.*) https://%{SERVER_NAME}/ [R,L]
</VirtualHost>

<VirtualHost *:443>
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:5000/
ProxyPassReverse / http://127.0.0.1:5000/
ErrorLog /var/log/httpd/hellomvc-error.log
CustomLog /var/log/httpd/hellomvc-access.log common
SSLEngine on
SSLProtocol all -SSLv2
SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:!RC4+RSA:+HIGH:+MEDIUM:!LOW:!RC4
SSLCertificateFile /etc/pki/tls/certs/localhost.crt
SSLCertificateKeyFile /etc/pki/tls/private/localhost.key
</VirtualHost>

OBSERVAÇÃO
Este exemplo está usando um certificado gerado localmente. SSLCertificateFile deve ser o arquivo de certificado primário
para o nome de domínio. SSLCertificateKeyFile deve ser o arquivo de chave gerado quando CSR é criado.
SSLCertificateChainFile deve ser o arquivo de certificado intermediário (se houver) que foi fornecido pela autoridade de
certificação.

Salve o arquivo e a configuração de teste:

sudo service httpd configtest

Reinicie o Apache:

sudo systemctl restart httpd

Sugestões adicionais do Apache


Cabeçalhos adicionais
Para proteger contra ataques mal-intencionados, há alguns cabeçalhos que devem ser modificados ou
adicionados. Certifique-se de que o mod_headers módulo está instalado:

sudo yum install mod_headers

Apache seguro contra ataques de clickjacking


Clickjacking, também conhecido como um UI redress ataque, é um ataque mal-intencionado em que o visitante
do site seja levado a clicar em um link ou botão em uma página diferente daquele que está visitando
atualmente. Use X-FRAME-OPTIONS para proteger o site.
Editar o httpd arquivo:

sudo nano /etc/httpd/conf/httpd.conf


Adicione a linha Header append X-FRAME-OPTIONS "SAMEORIGIN" . Salve o arquivo. Reinicie o Apache.
Detecção de tipo MIME
O X-Content-Type-Options cabeçalho impedirá que o Internet Explorer MIME -sniffing (determinando um
arquivo Content-Type do conteúdo do arquivo). Se o servidor define a Content-Type cabeçalho para text/html
com o nosniff opção definida, o Internet Explorer renderiza o conteúdo como text/html , independentemente
do conteúdo do arquivo.
Editar o httpd arquivo:

sudo nano /etc/httpd/conf/httpd.conf

Adicione a linha Header set X-Content-Type-Options "nosniff" . Salve o arquivo. Reinicie o Apache.
Balanceamento de carga
Este exemplo mostra como instalar e configurar o Apache no CentOS 7 e no Kestrel no mesmo computador da
instância. Para não ter um ponto único de falha; usando mod_proxy_balancer e modificando o VirtualHost
permitiria para gerenciar várias instâncias dos aplicativos web por trás do servidor de proxy do Apache.

sudo yum install mod_proxy_balancer

No arquivo de configuração mostrada abaixo, uma instância adicional do hellomvc aplicativo está configurado
para ser executado na porta 5001. O Proxy seção é definida com uma configuração de Balanceador com dois
membros para balancear a carga byrequests.

<VirtualHost *:80>
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^/?(.*) https://%{SERVER_NAME}/ [R,L]
</VirtualHost>

<VirtualHost *:443>
ProxyPass / balancer://mycluster/

ProxyPassReverse / http://127.0.0.1:5000/
ProxyPassReverse / http://127.0.0.1:5001/

<Proxy balancer://mycluster>
BalancerMember http://127.0.0.1:5000
BalancerMember http://127.0.0.1:5001
ProxySet lbmethod=byrequests
</Proxy>

<Location />
SetHandler balancer
</Location>
ErrorLog /var/log/httpd/hellomvc-error.log
CustomLog /var/log/httpd/hellomvc-access.log common
SSLEngine on
SSLProtocol all -SSLv2
SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:!RC4+RSA:+HIGH:+MEDIUM:!LOW:!RC4
SSLCertificateFile /etc/pki/tls/certs/localhost.crt
SSLCertificateKeyFile /etc/pki/tls/private/localhost.key
</VirtualHost>

Limites de taxa
Usando mod_ratelimit, que está incluído o httpd módulo, a largura de banda de clientes pode ser limitada:
sudo nano /etc/httpd/conf.d/ratelimit.conf

O arquivo de exemplo limita a largura de banda como 600 KB/s no local da raiz:

<IfModule mod_ratelimit.c>
<Location />
SetOutputFilter RATE_LIMIT
SetEnv rate-limit 600
</Location>
</IfModule>
Hospedar o ASP.NET Core em contêineres do Docker
10/04/2018 • 2 min to read • Edit Online

Os artigos a seguir estão disponíveis para saber mais sobre como hospedar aplicativos ASP.NET Core no Docker:
Introdução aos contêineres e ao Docker
Veja como o uso de contêineres é uma abordagem de desenvolvimento de software na qual um aplicativo ou
serviço, suas dependências e sua configuração são empacotados juntos como uma imagem de contêiner. A
imagem pode ser testada e, em seguida, implantada em um host.
O que é o Docker?
Descubra como o Docker é um projeto de software livre para automatizar a implantação de aplicativos como
contêineres autossuficientes portáteis que podem ser executados na nuvem ou localmente.
Terminologia do Docker
Aprenda termos e definições para a tecnologia do Docker.
Registros, imagens e contêineres do Docker
Descubra como imagens de contêiner do Docker são armazenadas em um registro de imagem para implantação
consistente entre ambientes.
Compilar imagens do Docker para Aplicativos .NET Core
Saiba como criar um aplicativo ASP.NET Core e colocá-lo no Docker. Explore imagens do Docker mantidas pela
Microsoft e examine os casos de uso.
Ferramentas do Visual Studio para Docker
Descubra como o Visual Studio 2017 permite a criação, depuração e execução de aplicativos ASP.NET Core
direcionados ao .NET Framework ou ao .NET Core no Docker para Windows. Contêineres do Windows e do
Linux são compatíveis.
Publicar em uma imagem do Docker
Saiba como usar a extensão Ferramentas do Visual Studio para Docker para implantar um aplicativo do ASP.NET
Core para um host Docker no Azure usando o PowerShell.
Configurar o ASP.NET Core para trabalhar com servidores proxy e balanceadores de carga
Configuração adicional pode ser necessária para aplicativos hospedados atrás de servidores proxy e
balanceadores de carga. Passar solicitações por meio de um proxy geralmente oculta informações sobre a
solicitação original, como o esquema e o IP de cliente. Talvez seja necessário encaminhar manualmente algumas
informações sobre a solicitação para o aplicativo.
Ferramentas do Visual Studio para Docker com
ASP.NET Core
12/03/2018 • 11 min to read • Edit Online

Visual Studio de 2017 dá suporte à criação, depuração e execução em contêineres ASP.NET Core aplicativos
voltados para o .NET Core. Contêineres do Windows e do Linux são compatíveis.

Pré-requisitos
O Visual Studio 2017 com a carga de trabalho Desenvolvimento de multiplataforma do .NET Core
Docker para Windows

Instalação e configuração
Para a instalação do Docker, examine as informações em Docker for Windows: What to know before you install
(Docker para Windows: o que saber antes de instalar) e instale o Docker para Windows.
As Unidades Compartilhadas do Docker para Windows devem ser configuradas para dar suporte ao
mapeamento do volume e à depuração. Clique duas vezes o ícone de Docker da bandeja do sistema, selecione
configurações... e selecione unidades compartilhadas. Selecione a unidade em que o Docker armazena
arquivos. Selecione aplicar.

DICA
Visual Studio 2017 versões 15.6 e posteriores do prompt quando unidades compartilhadas não estão configurados.

Adicionar suporte do Docker a um aplicativo


Para adicionar suporte de Docker para um projeto do ASP.NET Core, o projeto deve usar o .NET Core. Há suporte
para contêineres do Linux e Windows.
Ao adicionar o suporte de Docker para um projeto, escolha Windows ou um contêiner do Linux. O host do Docker
deve estar executando o mesmo tipo de contêiner. Para alterar o tipo de contêiner na instância do Docker em
execução, clique com o botão direito do mouse no ícone do Docker na Bandeja do Sistema e escolha Alternar
para contêineres do Windows... ou Alternar para contêineres do Linux....
Novo aplicativo
Ao criar um novo aplicativo com os modelos de projeto do Aplicativo Web ASP.NET Core, marque a caixa de
seleção Habilitar Suporte do Docker:

Se a estrutura de destino é o núcleo do .NET, o OS suspensa permite a seleção de um tipo de contêiner.


Aplicativo existente
As Ferramentas do Visual Studio para Docker não dão suporte à adição do Docker a um projeto ASP.NET Core
existente direcionado ao .NET Framework. Para projetos do ASP.NET Core direcionados ao .NET Core, há duas
opções para adicionar o suporte do Docker por meio das ferramentas. Abra o projeto no Visual Studio e escolha
uma das seguintes opções:
Selecione Suporte do Docker no menu Projeto.
Clique com o botão direito do mouse no projeto no Gerenciador de Soluções e selecione Adicionar > Suporte
do Docker.

Visão geral de ativos do Docker


As Ferramentas do Visual Studio para Docker adicionam um projeto docker-compose à solução, que contém o
seguinte:
.dockerignore: contém uma lista de padrões de arquivo e diretório a serem excluídos ao gerar um contexto de
build.
docker-compose.yml: o arquivo base do Docker Compose usado para definir a coleção de imagens que serão
compiladas e executadas com o docker-compose build e docker-compose run , respectivamente.
docker-compose.override.yml: um arquivo opcional, lido pelo Docker Compose, que contém as substituições de
configuração para os serviços. O Visual Studio executa
docker-compose -f "docker-compose.yml" -f "docker-compose.override.yml" para mesclar esses arquivos.

Um Dockerfile, a receita para criar uma imagem final do Docker, é adicionado à raiz do projeto. Consulte
Referência do Dockerfile para compreender seus comandos. Esse Dockerfile específico usa um build de vários
estágios, que contém quatro estágios de build nomeados distintos:
FROM microsoft/aspnetcore:2.0-nanoserver-1709 AS base
WORKDIR /app
EXPOSE 80

FROM microsoft/aspnetcore-build:2.0-nanoserver-1709 AS build


WORKDIR /src
COPY *.sln ./
COPY HelloDockerTools/HelloDockerTools.csproj HelloDockerTools/
RUN dotnet restore
COPY . .
WORKDIR /src/HelloDockerTools
RUN dotnet build -c Release -o /app

FROM build AS publish


RUN dotnet publish -c Release -o /app

FROM base AS final


WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "HelloDockerTools.dll"]

O Dockerfile se baseia na imagem microsoft/aspnetcore. Essa imagem base inclui os pacotes NuGet do ASP.NET
Core, que foram pré-compilados com JIT para melhorar o desempenho de inicialização.
O docker compose.yml arquivo contém o nome da imagem que é criado quando o projeto é executado:

version: '3'

services:
hellodockertools:
image: hellodockertools
build:
context: .
dockerfile: HelloDockerTools\Dockerfile

No exemplo anterior, image: hellodockertools gera a imagem hellodockertools:dev quando o aplicativo é


executado no modo Depuração. A imagem hellodockertools:latest é gerada quando o aplicativo é executado no
modo Versão.
Prefixo do nome de imagem com o Hub do Docker nome de usuário (por exemplo,
dockerhubusername/hellodockertools ) se a imagem será enviada para o registro. Como alternativa, alterar o nome
da imagem para incluir a URL de registro particular (por exemplo, privateregistry.domain.com/hellodockertools )
dependendo da configuração.

Depurar
Selecione Docker no menu suspenso de depuração na barra de ferramentas e inicie a depuração do aplicativo. A
exibição Docker da janela Saída mostra as seguintes ações em andamento:
O microsoft/aspnetcore imagem de tempo de execução é adquirida (se ainda não estiver no cache).
O aspnetcore/microsoft-compilação imagem/publicação de compilação é adquirida (se ainda não estiver no
cache).
A variável de ambiente ASPNETCORE_ENVIRONMENT é definida como Development dentro do contêiner.
A porta 80 é exposta e mapeada para uma porta atribuída dinamicamente para o localhost. A porta é
determinada pelo host do Docker e pode ser consultada com o comando docker ps .
O aplicativo é copiado para o contêiner.
O navegador padrão é iniciado com o depurador anexado ao contêiner usando a porta atribuída
dinamicamente.
A imagem resultante do Docker é o desenvolvimento imagem do aplicativo com o microsoft/aspnetcore imagens
como a imagem base. Execute o comando docker images na janela do PMC (Console do Gerenciador de
Pacotes). As imagens no computador são exibidas:

REPOSITORY TAG IMAGE ID CREATED SIZE


hellodockertools latest f8f9d6c923e2 About an hour ago 391MB
hellodockertools dev 85c5ffee5258 About an hour ago 389MB
microsoft/aspnetcore-build 2.0-nanoserver-1709 d7cce94e3eb0 15 hours ago 1.86GB
microsoft/aspnetcore 2.0-nanoserver-1709 8872347d7e5d 40 hours ago 389MB

OBSERVAÇÃO
A imagem de desenvolvimento não tem o conteúdo do aplicativo, como depurar configurações usam a montagem de
volume para fornecer uma experiência interativa. Para enviar uma imagem por push, use a configuração Versão.

Execute o comando docker ps no PMC. Observe que o aplicativo está em execução usando o contêiner:

CONTAINER ID IMAGE COMMAND CREATED STATUS


PORTS NAMES
baf9a678c88d hellodockertools:dev "C:\\remote_debugge..." 21 seconds ago Up 19 seconds
0.0.0.0:37630->80/tcp dockercompose4642749010770307127_hellodockertools_1

Editar e continuar
As alterações em arquivos estáticos e exibições do Razor são atualizadas automaticamente sem a necessidade de
uma etapa de compilação. Faça a alteração, salve e atualize o navegador para exibir a atualização.
As modificações em arquivos de código exigem a compilação e a reinicialização do Kestrel dentro do contêiner.
Depois de fazer a alteração, use CTRL + F5 para executar o processo e iniciar o aplicativo dentro do contêiner. O
contêiner do Docker não é recriado ou interrompido. Execute o comando docker ps no PMC. Observe que o
contêiner original ainda está em execução como há 10 minutos:

CONTAINER ID IMAGE COMMAND CREATED STATUS


PORTS NAMES
baf9a678c88d hellodockertools:dev "C:\\remote_debugge..." 10 minutes ago Up 10 minutes
0.0.0.0:37630->80/tcp dockercompose4642749010770307127_hellodockertools_1

Publicar imagens do Docker


Depois que o ciclo de desenvolver e depurar o aplicativo for concluído, o Visual Studio Tools para Docker ajudar a
criar a imagem de produção do aplicativo. Altere a lista suspensa de configuração para Versão e compile o
aplicativo. A ferramenta gera a imagem com o mais recente marca, que pode ser enviada para o registro particular
ou o Hub do Docker.
Execute o comando docker images no PMC para ver a lista de imagens:

REPOSITORY TAG IMAGE ID CREATED SIZE


hellodockertools latest 4cb1fca533f0 19 seconds ago 391MB
hellodockertools dev 85c5ffee5258 About an hour ago 389MB
microsoft/aspnetcore-build 2.0-nanoserver-1709 d7cce94e3eb0 16 hours ago 1.86GB
microsoft/aspnetcore 2.0-nanoserver-1709 8872347d7e5d 40 hours ago 389MB
OBSERVAÇÃO
O comando docker images retorna imagens intermediárias com nomes de repositório e marcas identificadas como
<none> (não listadas acima). Essas imagens sem nome são produzidas pelo Dockerfile no build de vários estágios. Elas
melhoram a eficiência da compilação da imagem final — apenas as camadas necessárias são recompiladas quando ocorrem
alterações. Quando as imagens intermediárias não são mais necessários, excluí-los usando o docker rmi comando.

Pode haver uma expectativa de que a imagem de produção ou versão seja menor em comparação com a imagem
dev. O mapeamento do volume, devido a estavam em execução o depurador e o aplicativo no computador local e
não dentro do contêiner. A última imagem empacotou o código do aplicativo necessário para executar o aplicativo
em um computador host. Portanto, o delta é o tamanho do código do aplicativo.
Configurar o ASP.NET Core para trabalhar com
servidores proxy e balanceadores de carga
04/05/2018 • 18 min to read • Edit Online

Por Luke Latham e Ross Carlos


Na configuração recomendada para o ASP.NET Core, o aplicativo é hospedado usando o módulo de núcleo
IIS/ASP.NET, Nginx ou Apache. Servidores proxy, balanceadores de carga e outros dispositivos de rede
geralmente ocultar informações sobre a solicitação antes de alcançar o aplicativo:
Quando solicitações HTTPS são delegadas sobre HTTP, o esquema original (HTTPS ) é perdido e deve ser
encaminhado em um cabeçalho.
Como um aplicativo recebe uma solicitação de proxy e não sua fonte verdadeira na Internet ou rede
corporativa, o endereço IP do cliente de origem também deve ser encaminhado em um cabeçalho.
Essas informações podem ser importantes no processamento de solicitações, por exemplo na
redirecionamentos, autenticação, geração de link, avaliação de política e geoloation do cliente.

Cabeçalhos encaminhados
Por convenção, os proxies encaminham informações em cabeçalhos HTTP.

CABEÇALHO DESCRIÇÃO

X-Forwarded-For Contém informações sobre o cliente que iniciou a solicitação


e proxies subsequentes em uma cadeia de proxies. Esse
parâmetro pode conter IP endereços (e, opcionalmente, os
números de porta). Em uma cadeia de servidores proxy, o
primeiro parâmetro indica o cliente em que a solicitação foi
feita pela primeira vez. Siga os identificadores de proxy
subsequentes. O último proxy na cadeia não está na lista de
parâmetros. Endereço IP do proxy última e, opcionalmente,
um número de porta estão disponíveis como o endereço IP
remoto na camada de transporte.

X-Forwarded-Proto O valor do esquema de origem (HTTP/HTTPS). O valor


também pode ser uma lista de esquemas, se a solicitação
percorreu vários proxies.

Host encaminhados X O valor original do campo de cabeçalho de Host.


Normalmente, os proxies não modifique o cabeçalho de
Host. Consulte Microsoft Security Advisory CVE-2018-0787
para obter informações sobre uma vulnerabilidade de
elevação de privilégios que afeta os sistemas em que o proxy
não valida ou restict cabeçalhos de Host para valores de
BOM conhecidos.

O Middleware de cabeçalhos encaminhados do Microsoft.AspNetCore.HttpOverrides empacotar, lê esses


cabeçalhos e preenche os campos associados em HttpContext.
As atualizações de middleware:
HttpContext.Connection.RemoteIpAddress – definido usando o X-Forwarded-For o valor do cabeçalho.
Configurações adicionais influenciam como o middleware define RemoteIpAddress . Para obter detalhes,
consulte o opções de Middleware de cabeçalhos encaminhados.
HttpContext.Request.Scheme – definido usando o X-Forwarded-Proto o valor do cabeçalho.
HttpContext.Request.Host – definido usando o X-Forwarded-Host o valor do cabeçalho.
Observe que nem todos os dispositivos de rede adicionar o X-Forwarded-For e X-Forwarded-Proto cabeçalhos
sem configuração adicional. Consulte o guia do fabricante do dispositivo se as solicitações de proxies não tiver
esses cabeçalhos quando atingem o aplicativo.
Encaminhado cabeçalhos Middleware configurações padrão pode ser configurado. As configurações padrão
são:
Há apenas um proxy entre o aplicativo e a fonte das solicitações.
Somente os endereços de loopback são configurados para proxies conhecidos e conhecidos redes.

O IIS/IIS Express e o módulo principal do ASP.NET


Encaminhada Middleware de cabeçalhos é habilitado por padrão pelo Middleware de integração do IIS quando
o aplicativo é executado por trás do IIS e o módulo do ASP.NET Core. Middleware de cabeçalhos encaminhada
está ativado para ser executado primeiro no pipeline de middleware com uma configuração restrita específica
para o módulo do núcleo do ASP.NET devido a questões de confiança com cabeçalhos encaminhados (por
exemplo, falsificação de IP ). O middleware está configurado para encaminhar o X-Forwarded-For e
X-Forwarded-Proto cabeçalhos e é restrita a um proxy localhost único. Se a configuração adicional é necessária,
consulte o opções de Middleware de cabeçalhos encaminhados.

Outro servidor de proxy e cenários de Balanceador de carga


Além do uso de Middleware de integração do IIS, encaminhados cabeçalhos Middleware não está habilitado
por padrão. Encaminhada Middleware de cabeçalhos deve ser habilitado para um aplicativo para processar
encaminhado cabeçalhos com UseForwardedHeaders. Depois de habilitar o middleware se nenhum
ForwardedHeadersOptions são especificados para o middleware, o padrão
ForwardedHeadersOptions.ForwardedHeaders são ForwardedHeaders.None .
Configurar o middleware com ForwardedHeadersOptions para encaminhar o X-Forwarded-For e
X-Forwarded-Proto cabeçalhos no Startup.ConfigureServices . Invocar o UseForwardedHeaders método
Startup.Configure antes de chamar outro middleware:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();

services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)


{
app.UseForwardedHeaders();

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}

app.UseStaticFiles();
// In ASP.NET Core 1.x, replace the following line with: app.UseIdentity();
app.UseAuthentication();
app.UseMvc();
}

OBSERVAÇÃO
Se nenhum ForwardedHeadersOptions são especificadas em Startup.ConfigureServices ou diretamente para o
método de extensão com UseForwardedHeaders (IApplicationBuilder, ForwardedHeadersOptions), o padrão cabeçalhos
para encaminhar são ForwardedHeaders.None. O ForwardedHeadersOptions.ForwardedHeaders propriedade deve ser
configurada com os cabeçalhos para encaminhar.

Opções de Middleware cabeçalhos encaminhadas


ForwardedHeadersOptions controlar o comportamento do Middleware encaminhados cabeçalhos:

services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardLimit = 2;
options.KnownProxies.Add(IPAddress.Parse("127.0.10.1"));
options.ForwardedForHeaderName = "X-Forwarded-For-Custom-Header-Name";
});

OPÇÃO DESCRIÇÃO

ForwardedForHeaderName Use o cabeçalho especificado por essa propriedade, em vez


de um especificado por
ForwardedHeadersDefaults.XForwardedForHeaderName.

O padrão é X-Forwarded-For .
OPÇÃO DESCRIÇÃO

ForwardedHeaders Identifica qual encaminhadores devem ser processados.


Consulte o ForwardedHeaders Enum para a lista de campos
que se aplicam. Os valores típicos atribuídos a essa
propriedade são
ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto
.

O valor padrão é ForwardedHeaders.None.

ForwardedHostHeaderName Use o cabeçalho especificado por essa propriedade, em vez


de um especificado por
ForwardedHeadersDefaults.XForwardedHostHeaderName.

O padrão é X-Forwarded-Host .

ForwardedProtoHeaderName Use o cabeçalho especificado por essa propriedade, em vez


de um especificado por
ForwardedHeadersDefaults.XForwardedProtoHeaderName.

O padrão é X-Forwarded-Proto .

ForwardLimit Limita o número de entradas nos cabeçalhos que são


processados. Definido como null desabilitar o limite, mas
isso só deve ser feito se KnownProxies ou KnownNetworks
estão configurados.

O padrão é 1.

KnownNetworks Intervalos de proxies conhecidos para aceitar encaminhados


cabeçalhos de endereços. Forneça os intervalos IP usando
notação de roteamento entre domínios (CIDR).

O padrão é um IList<IPNetwork> que contém uma única


entrada para IPAddress.Loopback .

KnownProxies Endereços de proxies conhecidos para aceitar cabeçalhos


encaminhados do. Use KnownProxies especificar o
endereço IP corresponde.

O padrão é um IList<IPAddress> que contém uma única


entrada para IPAddress.IPv6Loopback .

OriginalForHeaderName Use o cabeçalho especificado por essa propriedade, em vez


de um especificado por
ForwardedHeadersDefaults.XOriginalForHeaderName.

O padrão é X-Original-For .

OriginalHostHeaderName Use o cabeçalho especificado por essa propriedade, em vez


de um especificado por
ForwardedHeadersDefaults.XOriginalHostHeaderName.

O padrão é X-Original-Host .
OPÇÃO DESCRIÇÃO

OriginalProtoHeaderName Use o cabeçalho especificado por essa propriedade, em vez


de um especificado por
ForwardedHeadersDefaults.XOriginalProtoHeaderName.

O padrão é X-Original-Proto .

RequireHeaderSymmetry Exigem o número de valores de cabeçalho a serem


sincronizados entre o
ForwardedHeadersOptions.ForwardedHeaders sendo
processada.

O padrão no núcleo do ASP.NET 1. x é true . O padrão do


ASP.NET Core 2.0 ou posterior é false .

OPÇÃO DESCRIÇÃO

AllowedHosts Restringe os hosts com o X-Forwarded-Host cabeçalho


para os valores fornecidos.
Os valores são comparados usando case ignorar
ordinal.
Números de porta devem ser excluídos.
Se a lista estiver vazia, todos os hosts são permitidos.
Um curinga de nível superior * permite que todos
os hosts de não vazio.
Subdomínio curingas são permitidos, mas não
coincidir com o domínio raiz. Por exemplo,
*.contoso.com corresponde o subdomínio
foo.contoso.com , mas não o domínio raiz
contoso.com .
Nomes de host de Unicode são permitidos, mas são
convertidos em Punycode para correspondência.
Endereços IPv6 deve incluir delimitadora colchetes e
estar em formulário convencional (por exemplo,
[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789] ).
Endereços IPv6 não especial de maiusculas e
minúsculas para verificar se há igualdade lógica entre
formatos diferentes, e nenhuma conversão em
formato canônico é executada.
Falha para restringir os hosts permitidos pode
permitir que um invasor falsificar links gerado pelo
serviço.
O valor padrão é vazio IList<cadeia de caracteres >.

ForwardedForHeaderName Use o cabeçalho especificado por essa propriedade, em vez


de um especificado por
ForwardedHeadersDefaults.XForwardedForHeaderName.

O padrão é X-Forwarded-For .
OPÇÃO DESCRIÇÃO

ForwardedHeaders Identifica qual encaminhadores devem ser processados.


Consulte o ForwardedHeaders Enum para a lista de campos
que se aplicam. Os valores típicos atribuídos a essa
propriedade são
ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto
.

O valor padrão é ForwardedHeaders.None.

ForwardedHostHeaderName Use o cabeçalho especificado por essa propriedade, em vez


de um especificado por
ForwardedHeadersDefaults.XForwardedHostHeaderName.

O padrão é X-Forwarded-Host .

ForwardedProtoHeaderName Use o cabeçalho especificado por essa propriedade, em vez


de um especificado por
ForwardedHeadersDefaults.XForwardedProtoHeaderName.

O padrão é X-Forwarded-Proto .

ForwardLimit Limita o número de entradas nos cabeçalhos que são


processados. Definido como null desabilitar o limite, mas
isso só deve ser feito se KnownProxies ou KnownNetworks
estão configurados.

O padrão é 1.

KnownNetworks Intervalos de proxies conhecidos para aceitar encaminhados


cabeçalhos de endereços. Forneça os intervalos IP usando
notação de roteamento entre domínios (CIDR).

O padrão é um IList<IPNetwork> que contém uma única


entrada para IPAddress.Loopback .

KnownProxies Endereços de proxies conhecidos para aceitar cabeçalhos


encaminhados do. Use KnownProxies especificar o
endereço IP corresponde.

O padrão é um IList<IPAddress> que contém uma única


entrada para IPAddress.IPv6Loopback .

OriginalForHeaderName Use o cabeçalho especificado por essa propriedade, em vez


de um especificado por
ForwardedHeadersDefaults.XOriginalForHeaderName.

O padrão é X-Original-For .

OriginalHostHeaderName Use o cabeçalho especificado por essa propriedade, em vez


de um especificado por
ForwardedHeadersDefaults.XOriginalHostHeaderName.

O padrão é X-Original-Host .
OPÇÃO DESCRIÇÃO

OriginalProtoHeaderName Use o cabeçalho especificado por essa propriedade, em vez


de um especificado por
ForwardedHeadersDefaults.XOriginalProtoHeaderName.

O padrão é X-Original-Proto .

RequireHeaderSymmetry Exigem o número de valores de cabeçalho a serem


sincronizados entre o
ForwardedHeadersOptions.ForwardedHeaders sendo
processada.

O padrão no núcleo do ASP.NET 1. x é true . O padrão do


ASP.NET Core 2.0 ou posterior é false .

Cenários e casos de uso


Quando não é possível adicionar encaminhada cabeçalhos e todas as solicitações são seguras
Em alguns casos, pode não ser possível adicionar cabeçalhos encaminhados para as solicitações de proxies para
o aplicativo. Se o proxy é impor que todas as solicitações externas públicas são HTTPS, o esquema pode ser
definido manualmente em Startup.Configure antes de usar qualquer tipo de middleware:

app.Use((context, next) =>


{
context.Request.Scheme = "https";
return next();
});

Esse código pode ser desabilitado com uma variável de ambiente ou outra configuração em um ambiente de
preparo ou de desenvolvimento.
Lidar com o caminho base e proxies que alterar o caminho da solicitação
Alguns proxies passam o caminho intactos, mas com um aplicativo caminho base deve ser removido para que o
roteamento funcione corretamente. UsePathBaseExtensions.UsePathBase middleware divide o caminho em
HttpRequest.Path e o caminho base do aplicativo em HttpRequest.PathBase.
Se /foo é o caminho base do aplicativo para um caminho de proxy passado como /foo/api/1 , os conjuntos
de middleware Request.PathBase para /foo e Request.Path para /api/1 com o seguinte comando:

app.UsePathBase("/foo");

O caminho original e o caminho base são reaplicadas quando o middleware é chamado novamente na ordem
inversa. Para obter mais informações sobre o processamento de pedidos de middleware, consulte Middleware.
Se o proxy corta o caminho (por exemplo, encaminhamento /foo/api/1 para /api/1 ), correção redireciona e
links, definindo a solicitação PathBase propriedade:

app.Use((context, next) =>


{
context.Request.PathBase = new PathString("/foo");
return next();
});

Se o proxy está adicionando dados de caminho, descarte a parte do caminho para corrigir redirecionamentos e
links usando StartsWithSegments (PathString, PathString) e de atribuição para o caminho propriedade:

app.Use((context, next) =>


{
if (context.Request.Path.StartsWithSegments("/foo", out var remainder))
{
context.Request.Path = remainder;
}

return next();
});

Solução de problemas
Quando os cabeçalhos não são encaminhados conforme o esperado, habilite log. Se os logs não fornecerem
informações suficientes para solucionar o problema, enumere os cabeçalhos de solicitação recebidos pelo
servidor. Os cabeçalhos podem ser gravados em uma resposta de aplicativo usando middleware embutido:

public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)


{
app.Run(async (context) =>
{
context.Response.ContentType = "text/plain";

// Request method, scheme, and path


await context.Response.WriteAsync(
$"Request Method: {context.Request.Method}{Environment.NewLine}");
await context.Response.WriteAsync(
$"Request Scheme: {context.Request.Scheme}{Environment.NewLine}");
await context.Response.WriteAsync(
$"Request Path: {context.Request.Path}{Environment.NewLine}");

// Headers
await context.Response.WriteAsync($"Request Headers:{Environment.NewLine}");

foreach (var header in context.Request.Headers)


{
await context.Response.WriteAsync($"{header.Key}: " +
$"{header.Value}{Environment.NewLine}");
}

await context.Response.WriteAsync(Environment.NewLine);

// Connection: RemoteIp
await context.Response.WriteAsync(
$"Request RemoteIp: {context.Connection.RemoteIpAddress}");
}
}

Certifique-se de que o X - encaminhado-* cabeçalhos são recebidos pelo servidor com os valores esperados. Se
houver vários valores em um determinado cabeçalho, observe encaminhados cabeçalhos Middleware
processos cabeçalhos na ordem inversa da direita para esquerda.
IP de remoto original da solicitação deve corresponder a uma entrada no KnownProxies ou KnownNetworks lista
antes de X-encaminhado-para serem processado. Isso limita a falsificação de cabeçalho por não aceitar
encaminhadores de proxies não confiáveis.

Recursos adicionais
Microsoft Security Advisory CVE -2018-0787: ASP.NET Core vulnerabilidade de elevação de privilégio
Perfis para a implantação de aplicativo do ASP.NET
Core de publicação do Visual Studio
27/04/2018 • 21 min to read • Edit Online

Por Sayed Hashimi de Ibrahim e Rick Anderson


Este documento se concentra no uso de 2017 do Visual Studio para criar e usar perfis de publicação. Os perfis de
publicação criados com o Visual Studio podem ser executados do MSBuild e do Visual Studio 2017. Consulte
Publicar um aplicativo Web ASP.NET Core no Serviço de Aplicativo do Azure usando o Visual Studio para obter
instruções sobre a publicação no Azure.
O seguinte arquivo de projeto foi criado com o comando dotnet new mvc :
ASP.NET Core 2.x
ASP.NET Core 1.x

<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>

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

</Project>

O <Project> do elemento Sdk atributo realiza as seguintes tarefas:


Importa o arquivo de propriedades de $ (MSBuildSDKsPath)\Microsoft.NET.Sdk.Web\Sdk\Sdk.Props no início.
Importa o arquivo de destino de $ (MSBuildSDKsPath)\Microsoft.NET.Sdk.Web\Sdk\Sdk.targets no fim.
O local padrão para MSBuildSDKsPath (com o Visual Studio Enterprise 2017) é a pasta
%programfiles(x86 )%\Microsoft Visual Studio\2017\Enterprise\MSBuild\Sdks.
O Microsoft.NET.Sdk.Web depende do SDK:
Microsoft.NET.Sdk.Web.ProjectSystem
Microsoft.NET.Sdk.Publish
Que faz com que as propriedades e os destinos a serem importados a seguir:
$ (MSBuildSDKsPath)\Microsoft.NET.Sdk.Web.ProjectSystem\Sdk\Sdk.Props
$ (MSBuildSDKsPath)\Microsoft.NET.Sdk.Web.ProjectSystem\Sdk\Sdk.targets
$ (MSBuildSDKsPath)\Microsoft.NET.Sdk.Publish\Sdk\Sdk.Props
$ (MSBuildSDKsPath)\Microsoft.NET.Sdk.Publish\Sdk\Sdk.targets
Publica o direito de conjunto de destinos com base no método de publicação usado de importação de destinos.
Quando o MSBuild ou o Visual Studio carrega um projeto, ocorrem as seguintes ações de alto nível:
Compilar projeto
Computar arquivos a publicar
Publicar arquivos para o destino

Itens de projeto de computação


Quando o projeto é carregado, os itens de projeto (arquivos) são computados. O atributo item type determina
como o arquivo é processado. Por padrão, os arquivos .cs são incluídos na lista de itens Compile . Os arquivos na
lista de itens Compile são compilados.
O Content à lista de itens contém arquivos que são publicados além das saídas de compilação. Por padrão,
arquivos correspondentes ao padrão wwwroot/** são incluídos no Content item. O wwwroot/\*\* padrão de
globalização corresponde a todos os arquivos de wwwroot pasta e subpastas. Para adicionar explicitamente um
arquivo à lista de publicação, adicione o arquivo diretamente no . csproj arquivo conforme mostrado na arquivos
de inclusão.
Ao selecionar o publicar botão no Visual Studio ou a publicação da linha de comando:
Os itens/propriedades são calculados (os arquivos necessários para compilar).
O Visual Studio só: pacotes do NuGet serão restaurados. (A restauração precisa ser explícita pelo usuário na
CLI.)
O projeto é compilado.
Os itens de publicação são computados (os arquivos necessários para a publicação).
O projeto é publicado (computados arquivos são copiados para o destino de publicação).
Quando faz referência a um projeto do ASP.NET Core Microsoft.NET.Sdk.Web no arquivo de projeto, um
app_offline.htm arquivo é colocado na raiz do diretório de aplicativo da web. Quando o arquivo estiver presente, o
módulo do ASP.NET Core apenas desligará o aplicativo e servirá o arquivo app_offline.htm durante a
implantação. Para obter mais informações, consulte Referência de configuração do módulo do ASP.NET Core.

Publicação de linha de comando básica


Publicação de linha de comando funciona em todas as plataformas com suporte ao .NET Core e não requer o
Visual Studio. No exemplo abaixo, o dotnet publicar comando é executado no diretório do projeto (que contém o .
csproj arquivo). Se não estiver na pasta do projeto, passar explicitamente no caminho do arquivo de projeto. Por
exemplo:

dotnet publish C:\Webs\Web1

Execute os comandos a seguir para criar e publicar um aplicativo Web:


ASP.NET Core 2.x
ASP.NET Core 1.x

dotnet new mvc


dotnet publish

O dotnet publicar comando produz saída semelhante à seguinte:


C:\Webs\Web1>dotnet publish
Microsoft (R) Build Engine version 15.3.409.57025 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

Web1 -> C:\Webs\Web1\bin\Debug\netcoreapp2.0\Web1.dll


Web1 -> C:\Webs\Web1\bin\Debug\netcoreapp2.0\publish\

A pasta de publicação padrão é bin\$(Configuration)\netcoreapp<version>\publish . O padrão para


$(Configuration) é depurar. No exemplo anterior, o <TargetFramework> é netcoreapp2.0 .

dotnet publish -h exibe informações de ajuda para publicação.


O comando a seguir especifica um build de Release e o diretório de publicação:

dotnet publish -c Release -o C:\MyWebs\test

O dotnet publicar comando chamadas MSBuild, que chama o Publish destino. Todos os parâmetros passados
para dotnet publish são passados para o MSBuild. O parâmetro -c é mapeado para a propriedade do MSBuild
Configuration . O parâmetro -o é mapeado para OutputPath .

Propriedades MSBuild podem ser passadas usando qualquer um dos seguintes formatos:
p:<NAME>=<VALUE>
/p:<NAME>=<VALUE>

O comando a seguir publica um build de Release para um compartilhamento de rede:


dotnet publish -c Release /p:PublishDir=//r8/release/AdminWeb

O compartilhamento de rede é especificado com barras "/" (//r8/) e funciona em todas as plataformas com
suporte do .NET Core.
Confirme que o aplicativo publicado para implantação não está em execução. Os arquivos da pasta publish são
bloqueados quando o aplicativo está em execução. A implantação não pode ocorrer porque arquivos bloqueados
não podem ser copiados.

Perfis de publicação
Esta seção usa 2017 do Visual Studio para criar um perfil de publicação. Depois de criado, a publicação do Visual
Studio ou a linha de comando está disponível.
Publicar perfis podem simplificar o processo de publicação, e podem existir vários perfis. Crie um perfil de
publicação no Visual Studio escolhendo um dos seguintes caminhos:
Clique com botão direito no projeto no Gerenciador de soluções e selecione publicar.
Selecione publicar <project_name> do criar menu.
O publicar guia da página de recursos do aplicativo é exibida. Se o projeto não tiver um perfil de publicação, a
página seguinte é exibida:
Quando pasta é selecionada, especifique um caminho de pasta para armazenar os recursos publicados. A pasta
padrão é bin\Release\PublishOutput. Clique o criar perfil botão para concluir.
Depois de criar um perfil de publicação, o publicar guia alterações. O perfil criado recentemente é exibida em
uma lista suspensa. Clique em criar novo perfil para criar outro novo perfil.
O Assistente de publicação dá suporte aos destinos de publicação a seguir:
Serviço de Aplicativo do Azure
Máquinas Virtuais do Azure
IIS, FTP, etc. (para qualquer servidor web)
Pasta
Importar perfil
Para obter mais informações, consulte quais opções de publicação são certos para mim.
Ao criar um perfil de publicação com o Visual Studio, um propriedades/PublishProfiles/<profile_name>. pubxml
arquivo MSBuild é criado. O . pubxml arquivo é um arquivo MSBuild e contém definições de configuração de
publicação. Esse arquivo pode ser alterado para personalizar a compilação e o processo de publicação. Esse
arquivo é lido pelo processo de publicação. <LastUsedBuildConfiguration> é especial porque é uma propriedade
global e não deve estar em qualquer arquivo que será importado no build. Consulte MSBuild: como definir a
propriedade de configuração para obter mais informações.
Ao publicar em um destino do Azure, o . pubxml arquivo contém o identificador de assinatura do Azure. Com esse
tipo de destino, adicionar esse arquivo ao controle de origem não é recomendado. Ao publicar em um destino não
Azure, é seguro fazer check-in a . pubxml arquivo.
Informações confidenciais (como a senha de publicação) são criptografadas em um nível de usuário/computador.
Ele é armazenado na propriedades/PublishProfiles/<profile_name>. pubxml.user arquivo. Como esse arquivo
pode armazenar informações confidenciais, não deve ser verificado no controle de origem.
Para obter uma visão geral de como publicar um aplicativo web do ASP.NET Core, consulte Host e implantar. As
tarefas de MSBuild e os destinos necessários para publicar um aplicativo do ASP.NET Core são o código-fonte
aberto no https://github.com/aspnet/websdk.
dotnet publish pode usar a pasta, MSDeploy, e Kudu perfis de publicação:
Pasta (funciona entre plataformas):

dotnet publish WebApplication.csproj /p:PublishProfile=<FolderProfileName>

MSDeploy (atualmente isso só funciona no Windows como MSDeploy não plataforma cruzada):

dotnet publish WebApplication.csproj /p:PublishProfile=<MsDeployProfileName> /p:Password=<DeploymentPassword>

Pacote MSDeploy (atualmente isso só funciona no Windows como MSDeploy não plataforma cruzada):

dotnet publish WebApplication.csproj /p:PublishProfile=<MsDeployPackageProfileName>

Nos exemplos anteriores, não passar deployonbuild para dotnet publish .


Para obter mais informações, consulte Microsoft.NET.Sdk.Publish.
dotnet publish oferece suporte a APIs do Kudu para publicar no Azure de qualquer plataforma. O Visual Studio
publique dá suporte a APIs do Kudu, mas ele é suportado por WebSDK para várias plataformas publicar no
Azure.
Adicionar um perfil de publicação para o propriedades/PublishProfiles pasta com o seguinte conteúdo:

<Project>
<PropertyGroup>
<PublishProtocol>Kudu</PublishProtocol>
<PublishSiteName>nodewebapp</PublishSiteName>
<UserName>username</UserName>
<Password>password</Password>
</PropertyGroup>
</Project>

Execute o seguinte comando para compactar o conteúdo de publicar e publicá-lo no Azure usando as APIs de
Kudu:

dotnet publish /p:PublishProfile=Azure /p:Configuration=Release

Defina as seguintes propriedades de MSBuild ao usar um perfil de publicação:


DeployOnBuild=true
PublishProfile=<Publish profile name>

Durante a publicação com um perfil chamado FolderProfile, qualquer um dos comandos a seguir podem ser
executado:
dotnet build /p:DeployOnBuild=true /p:PublishProfile=FolderProfile
msbuild /p:DeployOnBuild=true /p:PublishProfile=FolderProfile

Ao invocar dotnet compilação, ele chama msbuild para executar a compilação e o processo de publicação.
Chamar dotnet build ou msbuild é equivalente ao passar em um perfil de pasta. Ao chamar o MSBuild
diretamente no Windows, a versão do .NET Framework do MSBuild é usada. O MSDeploy é atualmente limitado
a computadores Windows para a publicação. Chamar dotnet build em um perfil não de pasta invoca o MSBuild
que, por sua vez, usa MSDeploy em perfis não de pasta. Chamar dotnet build em um perfil não de pasta invoca
o MSBuild (usando MSDeploy) e resulta em uma falha (mesmo quando em execução em uma plataforma do
Windows). Para publicar com um perfil não de pasta, chame o MSBuild diretamente.
A pasta de perfil de publicação a seguir foi criada com o Visual Studio e publica em um compartilhamento de
rede:

<?xml version="1.0" encoding="utf-8"?>


<!--
This file is used by the publish/package process of your Web project.
You can customize the behavior of this process by editing this
MSBuild file.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WebPublishMethod>FileSystem</WebPublishMethod>
<PublishProvider>FileSystem</PublishProvider>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish />
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<ExcludeApp_Data>False</ExcludeApp_Data>
<PublishFramework>netcoreapp1.1</PublishFramework>
<ProjectGuid>c30c453c-312e-40c4-aec9-394a145dee0b</ProjectGuid>
<publishUrl>\\r8\Release\AdminWeb</publishUrl>
<DeleteExistingFiles>False</DeleteExistingFiles>
</PropertyGroup>
</Project>

Observe que <LastUsedBuildConfiguration> é definido como Release . Ao publicar no Visual Studio, o valor da
propriedade de configuração <LastUsedBuildConfiguration> é definido usando o valor quando o processo de
publicação é iniciado. O <LastUsedBuildConfiguration> propriedade de configuração é especial e não deve ser
substituída em um arquivo importado do MSBuild. Essa propriedade pode ser substituída na linha de comando.
Usando o .NET Core CLI:

dotnet build -c Release /p:DeployOnBuild=true /p:PublishProfile=FolderProfile

Usando o MSBuild:

msbuild /p:Configuration=Release /p:DeployOnBuild=true /p:PublishProfile=FolderProfile

Publicar em um ponto de extremidade do MSDeploy da linha de


comando
A publicação pode ser feita usando o .NET Core CLI ou o MSBuild. dotnet publish é executado no contexto do
.NET Core. O msbuild comando requer o .NET Framework, que limita a ambientes de Windows.
A maneira mais fácil publicar com MSDeploy é primeiro criar um perfil de publicação no Visual Studio de 2017 e
usar o perfil da linha de comando.
No exemplo a seguir, é criado um aplicativo web do ASP.NET Core (usando dotnet new mvc ), e um perfil de
publicação do Azure é adicionado com o Visual Studio.
Executar msbuild de um Prompt de comando do desenvolvedor para VS 2017. O Prompt de comando do
desenvolvedor tem corretas msbuild.exe em seu caminho com um conjunto de variáveis do MSBuild.
O MSBuild usa a sintaxe a seguir:
msbuild <path-to-project-file> /p:DeployOnBuild=true /p:PublishProfile=<Publish Profile> /p:Username=
<USERNAME> /p:Password=<PASSWORD>

Obter o Password do <nome da publicação >. PublishSettings arquivo. Baixe o . PublishSettings arquivo do:
Gerenciador de soluções: Clique no aplicativo Web e selecione baixar perfil de publicação.
Portal do Azure: clique em obter perfil de publicação em seu aplicativo Web visão geral painel.
Username pode ser encontrado no perfil de publicação.
O exemplo a seguir usa o Web11112 - implantação da Web perfil de publicação:

msbuild "C:\Webs\Web1\Web1.csproj" /p:DeployOnBuild=true


/p:PublishProfile="Web11112 - Web Deploy" /p:Username="$Web11112"
/p:Password="<password removed>"

Excluir arquivos
Ao publicar aplicativos Web do ASP.NET Core, os artefatos de build e o conteúdo da pasta wwwroot são
incluídos. msbuild dá suporte a padrões de caractere curinga. Por exemplo, a seguinte <Content> elemento exclui
todo o texto (. txt) de arquivos do wwwroot/content pasta e todas as suas subpastas.

<ItemGroup>
<Content Update="wwwroot/content/**/*.txt" CopyToPublishDirectory="Never" />
</ItemGroup>

A marcação anterior pode ser adicionada a um perfil de publicação ou o . csproj arquivo. Quando adicionada ao
arquivo .csproj, a regra será adicionada a todos os perfis de publicação no projeto.
O seguinte <MsDeploySkipRules> elemento exclui todos os arquivos da wwwroot/content pasta:

<ItemGroup>
<MsDeploySkipRules Include="CustomSkipFolder">
<ObjectName>dirPath</ObjectName>
<AbsolutePath>wwwroot\\content</AbsolutePath>
</MsDeploySkipRules>
</ItemGroup>

<MsDeploySkipRules> Não exclua o ignorar destinos do site de implantação. <Content> pastas e arquivos de
destino são excluídas do site de implantação. Por exemplo, suponha que um aplicativo da web implantados tinha
os seguintes arquivos:
Views/Home/About1.cshtml
Views/Home/About2.cshtml
Views/Home/About3.cshtml
Se o seguinte <MsDeploySkipRules> elementos são adicionados, esses arquivos não serão excluídos no site de
implantação.
<ItemGroup>
<MsDeploySkipRules Include="CustomSkipFile">
<ObjectName>filePath</ObjectName>
<AbsolutePath>Views\\Home\\About1.cshtml</AbsolutePath>
</MsDeploySkipRules>

<MsDeploySkipRules Include="CustomSkipFile">
<ObjectName>filePath</ObjectName>
<AbsolutePath>Views\\Home\\About2.cshtml</AbsolutePath>
</MsDeploySkipRules>

<MsDeploySkipRules Include="CustomSkipFile">
<ObjectName>filePath</ObjectName>
<AbsolutePath>Views\\Home\\About3.cshtml</AbsolutePath>
</MsDeploySkipRules>
</ItemGroup>

Anterior <MsDeploySkipRules> elementos impedir o ignorada arquivos que está sendo implantado. Ele não
excluirá os arquivos depois que são implantados.
O seguinte <Content> elemento exclui os arquivos de destino no local de implantação:

<ItemGroup>
<Content Update="Views/Home/About?.cshtml" CopyToPublishDirectory="Never" />
</ItemGroup>

Com a implantação de linha de comando anterior <Content> elemento produz o seguinte resultado:

MSDeployPublish:
Starting Web deployment task from source:
manifest(C:\Webs\Web1\obj\Release\netcoreapp1.1\PubTmp\Web1.SourceManifest.
xml) to Destination: auto().
Deleting file (Web11112\Views\Home\About1.cshtml).
Deleting file (Web11112\Views\Home\About2.cshtml).
Deleting file (Web11112\Views\Home\About3.cshtml).
Updating file (Web11112\web.config).
Updating file (Web11112\Web1.deps.json).
Updating file (Web11112\Web1.dll).
Updating file (Web11112\Web1.pdb).
Updating file (Web11112\Web1.runtimeconfig.json).
Successfully executed Web deployment task.
Publish Succeeded.
Done Building Project "C:\Webs\Web1\Web1.csproj" (default targets).

Incluir arquivos
A seguinte marcação inclui um imagens pasta fora do diretório do projeto para o wwwroot/imagens pasta do site
de publicação:

<ItemGroup>
<_CustomFiles Include="$(MSBuildProjectDirectory)/../images/**/*" />
<DotnetPublishFiles Include="@(_CustomFiles)">
<DestinationRelativePath>wwwroot/images/%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
</DotnetPublishFiles>
</ItemGroup>

A marcação pode ser adicionada ao arquivo .csproj ou ao perfil de publicação. Se ela for adicionada para o . csproj
arquivo, ele está incluído em cada perfil de publicação no projeto.
A marcação realçada a seguir mostra como:
Copiar um arquivo de fora do projeto para a pasta wwwroot.
Excluir a pasta wwwroot\Content.
Excluir Views\Home\About2.cshtml.

<?xml version="1.0" encoding="utf-8"?>


<!--
This file is used by the publish/package process of your Web project.
You can customize the behavior of this process by editing this
MSBuild file.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WebPublishMethod>FileSystem</WebPublishMethod>
<PublishProvider>FileSystem</PublishProvider>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish />
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<ExcludeApp_Data>False</ExcludeApp_Data>
<PublishFramework />
<ProjectGuid>afa9f185-7ce0-4935-9da1-ab676229d68a</ProjectGuid>
<publishUrl>bin\Release\PublishOutput</publishUrl>
<DeleteExistingFiles>False</DeleteExistingFiles>
</PropertyGroup>
<ItemGroup>
<ResolvedFileToPublish Include="..\ReadMe2.MD">
<RelativePath>wwwroot\ReadMe2.MD</RelativePath>
</ResolvedFileToPublish>

<Content Update="wwwroot\Content\**\*" CopyToPublishDirectory="Never" />


<Content Update="Views\Home\About2.cshtml" CopyToPublishDirectory="Never" />

</ItemGroup>
</Project>

Consulte o Leiame do WebSDK para obter mais amostras de implantação.

Executar um destino antes ou depois da publicação


O interno BeforePublish e AfterPublish destinos executam um destino antes ou após o destino de publicação.
Adicione os seguintes elementos para o perfil de publicação para registrar em log mensagens de console antes e
após a publicação:

<Target Name="CustomActionsBeforePublish" BeforeTargets="BeforePublish">


<Message Text="Inside BeforePublish" Importance="high" />
</Target>
<Target Name="CustomActionsAfterPublish" AfterTargets="AfterPublish">
<Message Text="Inside AfterPublish" Importance="high" />
</Target>

Publicar em um servidor usando um certificado não confiável


Adicionar o <AllowUntrustedCertificate> propriedade com um valor de True para o perfil de publicação:

<PropertyGroup>
<AllowUntrustedCertificate>True</AllowUntrustedCertificate>
</PropertyGroup>
O serviço Kudu
Para exibir os arquivos em uma implantação de aplicativo web do serviço de aplicativo do Azure, use o service
Kudu. Acrescente a scm token para o nome do aplicativo web. Por exemplo:

URL RESULTADO

http://mysite.azurewebsites.net/ Aplicativo Web

http://mysite.scm.azurewebsites.net/ Serviço kudu

Selecione o Console depuração item de menu para exibir, editar, excluir ou adicionar arquivos.

Recursos adicionais
Web Deploy (MSDeploy) simplifica a implantação de aplicativos web e sites para servidores IIS.
https://github.com/aspnet/websdk: Problemas de arquivos e recursos para implantação de solicitação.
Estrutura de diretórios do ASP.NET Core
27/04/2018 • 3 min to read • Edit Online

Por Luke Latham


No núcleo do ASP.NET, o diretório de aplicativo publicado, publicar, é composta de arquivos de aplicativo, os
arquivos de configuração, ativos estáticos, pacotes e o tempo de execução (para implantações autossuficientes).

TIPO DE APLICATIVO ESTRUTURA DE DIRETÓRIOS

Dependente de estrutura de implantação Publicar†


logs† (opcional, a menos que necessário para
receber os logs de stdout)
Modos de exibição† (aplicativos MVC; se os
modos de exibição não pré-compilado)
Páginas† (MVC ou páginas Razor aplicativos;
se páginas não pré-compilado)
wwwroot†
*.arquivos DLL
<nome do assembly >. deps.json
<nome do assembly >. dll
<nome do assembly >. PDB
<nome do assembly >. PrecompiledViews.dll
<nome do assembly >.
PrecompiledViews.pdb
<nome do assembly >. runtimeconfig.json
Web. config (as implantações do IIS)

Independente de implantação Publicar†


logs† (opcional, a menos que necessário para
receber os logs de stdout)
refs†
Modos de exibição† (aplicativos MVC; se os
modos de exibição não pré-compilado)
Páginas† (MVC ou páginas Razor aplicativos;
se páginas não pré-compilado)
wwwroot†
*arquivos. dll
<nome do assembly >. deps.json
<nome do assembly > .exe
<nome do assembly >. PDB
<nome do assembly >. PrecompiledViews.dll
<nome do assembly >.
PrecompiledViews.pdb
<nome do assembly >. runtimeconfig.json
Web. config (as implantações do IIS)

†Indica um diretório
O publicar diretório representa o caminho raiz de conteúdo, também chamado de caminho base do aplicativo,
da implantação. O nome que é fornecido para o publicar serve de seu local de diretório do aplicativo
implantado no servidor, como a caminho físico do servidor para o aplicativo hospedado.
O wwwroot diretório, se presente, contém ativos estáticos somente.
Stdout logs diretório pode ser criado a implantação usando uma das duas abordagens a seguir:
Adicione o seguinte <Target> elemento ao arquivo de projeto:

<Target Name="CreateLogsFolder" AfterTargets="Publish">


<MakeDir Directories="$(PublishDir)Logs"
Condition="!Exists('$(PublishDir)Logs')" />
<WriteLinesToFile File="$(PublishDir)Logs\.log"
Lines="Generated file"
Overwrite="True"
Condition="!Exists('$(PublishDir)Logs\.log')" />
</Target>

O <MakeDir> elemento cria vazio Logs pasta na saída publicada. O elemento usa o PublishDir
propriedade para determinar o local de destino para criar a pasta. Vários métodos de implantação, como
a implantação da Web, ignore as pastas vazias durante a implantação. O <WriteLinesToFile> elemento
gera um arquivo no Logs pasta, o que garante a implantação da pasta para o servidor. Observe que a
criação de pasta ainda pode falhar se o processo do operador não tem acesso de gravação para a pasta
de destino.
Criar fisicamente o Logs diretório no servidor na implantação.
O diretório de implantação requer permissões de leitura/execução. O Logs directory requer permissões de
leitura/gravação. Diretórios adicionais onde os arquivos são gravados exigem permissões de leitura/gravação.
Referência de erros comuns para o serviço de
aplicativo do Azure e o IIS com o ASP.NET Core
27/04/2018 • 18 min to read • Edit Online

Por Luke Latham


A lista a seguir não é uma lista completa de erros. Se você encontrar um erro não listado aqui, abrir um novo
problema com instruções detalhadas para reproduzir o erro.
Colete as seguintes informações:
Comportamento do navegador
Entradas de Log de eventos do aplicativo
Entradas de log do ASP.NET Core módulo stdout
Compare as informações para os seguintes erros comuns. Se uma correspondência for encontrada, siga os
avisos de solução de problemas.

IMPORTANTE
Aviso para usando versões de visualização do ASP.NET Core 2.1
Consulte versão de visualização de implantar o ASP.NET Core para o serviço de aplicativo do Azure.

O instalador não pode obter os Pacotes Redistribuíveis do VC++


Exceção do Instalador: 0x80072efd ou 0x80072f76 – erro não especificado
Exceção do Log do Instalador†: erro 0x80072efd ou 0x80072f76: falha ao executar o pacote EXE
†O log está localizado em C:\Users\
{USER }\AppData\Local\Temp\dd_DotNetCoreWinSvrHosting__{timestamp}.log.

Solução de problemas:
Se o sistema não tiver acesso à Internet durante a instalação do pacote de hospedagem, essa exceção ocorre
quando o instalador não poderão obter o Microsoft Visual C++ 2015 redistribuível. Obter um instalador do
Microsoft Download Center. Se o instalador falhar, o servidor pode não receber o tempo de execução do
.NET Core necessário para hospedar uma implantação de framework dependente (FDD ). Se hospedando um
FDD, confirme que o tempo de execução é instalado em programas & recursos. Se necessário, obtenha um
instalador de tempo de execução de .NET todos os Downloads. Depois de instalar o tempo de execução,
reinicie o sistema ou o IIS executando net stop was /y seguido por net start w3svc em um prompt de
comando.

O upgrade do sistema operacional removeu o Módulo do ASP.NET


Core de 32 bits
Log do Aplicativo: a DLL do Módulo C:\WINDOWS\system32\inetsrv\aspnetcore.dll falhou ao ser
carregada. Os dados são o erro.
Solução de problemas:
Arquivos que não são do sistema operacional no diretório C:\Windows\SysWOW64\inetsrv não são
preservados durante um upgrade do sistema operacional. Se o módulo de núcleo do ASP.NET está instalado
antes de uma atualização do sistema operacional e, em seguida, qualquer AppPool é executado no modo de
32 bits depois de uma atualização do sistema operacional, esse problema é encontrado. Após um upgrade do
sistema operacional, repare o Módulo do ASP.NET Core. Consulte instalar o pacote de hospedagem do .NET
Core. Selecione reparo quando o instalador é executado.

Conflitos de plataforma com o RID


Navegador: 502.5 Erro HTTP – falha do processo
Log de aplicativo: aplicativo ' MACHINE/WEBROOT/APPHOST / {ASSEMBLY }' com raiz física ' c:
{caminho}' Falha ao iniciar o processo com linha de comando ' "c:\{caminho} {assembly}. { exe | dll} "',
código de erro = ' 0x80004005: ff.
Log de módulo do ASP.NET Core: exceção sem tratamento: System. BadImageFormatException: não
foi possível carregar arquivo ou assembly '{assembly}. dll'. Foi feita uma tentativa de carregar um
programa com um formato incorreto.
Solução de problemas:
Confirme se o aplicativo é executado localmente no Kestrel. Uma falha do processo pode ser o resultado
de um problema no aplicativo. Para obter mais informações, consulte solução de problemas.
Confirme se o <PlatformTarget> no . csproj não entra em conflito com o RID. Por exemplo, não
especifique um <PlatformTarget> de x86 e publicar com uma RID da win10-x64 , usando dotnet publicar
- c - r de versão para win10 -x64 ou definindo o <RuntimeIdentifiers> no . csproj para win10-x64 . O
projeto é publicado sem avisos nem erros, mas falha com as exceções registradas acima no sistema.
Se essa exceção ocorre para uma implantação de aplicativos do Azure ao atualizar um aplicativo e
implantando assemblies mais recentes, exclua manualmente todos os arquivos de implantação anterior.
Assemblies incompatíveis remanescentes podem resultar em uma exceção
System.BadImageFormatException durante a implantação de um aplicativo atualizado.

Ponto de extremidade de URI incorreto ou site interrompido


Navegador: ERR_CONNECTION_REFUSED
Log do Aplicativo: nenhuma entrada
Log do Módulo do ASP.NET Core: arquivo de log não criado
Solução de problemas:
Confirme se que o ponto de extremidade URI correto para o aplicativo está sendo usado. Verifique as
associações.
Confirme que o site do IIS não está no parado estado.

Recursos do servidor CoreWebEngine ou W3SVC desabilitados


Exceção do Sistema Operacional: os recursos CoreWebEngine e W3SVC do IIS 7.0 devem ser instalados
para usar o Módulo do ASP.NET Core.
Solução de problemas:
Confirme se a função adequada e os recursos estão habilitados. Consulte Configuração do IIS.
Caminho físico do site incorreto ou ausente do aplicativo
Navegador: 403 Proibido – acesso negado OU 403.14 Proibido – o servidor Web está configurado para
não listar o conteúdo deste diretório.
Log do Aplicativo: nenhuma entrada
Log do Módulo do ASP.NET Core: arquivo de log não criado
Solução de problemas:
Verifique o site do IIS configurações básicas e a pasta física do aplicativo. Confirme se o aplicativo está na
pasta em que o site do IIS caminho físico.

Função incorreta, módulo não instalado ou permissões incorretas


Navegador: 500.19 Erro interno do servidor – a página solicitada não pode ser acessada porque os
dados de configuração relacionados da página são inválidos.
Log do Aplicativo: nenhuma entrada
Log do Módulo do ASP.NET Core: arquivo de log não criado
Solução de problemas:
Confirme que a função adequada está habilitada. Consulte Configuração do IIS.
Verifique Programas & Recursos e confirme se o Módulo do Microsoft ASP.NET Core foi instalado.
Se o Módulo do Microsoft ASP.NET Core não estiver presente na lista de programas instalados,
instale o módulo. Consulte instalar o .NET Core hospedagem pacote.
Verifique se o Pool de aplicativos > modelo de processo > identidade é definido como
ApplicationPoolIdentity ou a identidade personalizada tem as permissões corretas para acessar a pasta
de implantação do aplicativo.

ProcessPath incorreto, variável de caminho ausente, o pacote de


hospedagem não instalado, não reiniciado sistema/IIS, VC + +
redistribuível não está instalado ou dotnet.exe violação de acesso
Navegador: 502.5 Erro HTTP – falha do processo
Log de aplicativo: aplicativo ' MACHINE/WEBROOT/APPHOST / {ASSEMBLY }' com raiz física ' c:\
{caminho}' Falha ao iniciar o processo com linha de comando ' ".{ assembly} .exe"', código de erro = '
0x80070002: 0.
Log do Módulo do ASP.NET Core: arquivo de log criado, mas vazio
Solução de problemas:
Confirme se o aplicativo é executado localmente no Kestrel. Uma falha do processo pode ser o resultado
de um problema no aplicativo. Para obter mais informações, consulte solução de problemas.
Verifique o processPath atributo no <aspNetCore> elemento Web. config para confirmar se ele está dotnet
para uma implantação do framework dependente (FDD ) ou . {assembly } .exe para uma implantação
autossuficiente (SCD ).
Para uma FDD, o dotnet.exe pode não estar acessível por meio das configurações de PATH. Confirme se
*C:\Program Files\dotnet* existe nas configurações de PATH do Sistema.
Para uma FDD, o dotnet.exe pode não estar acessível para a identidade do usuário do Pool de aplicativos.
Confirme se a identidade do usuário do AppPool tem acesso ao diretório C:\Program Files\dotnet.
Confirme que não há nenhuma regra de negação configurada para a identidade do usuário AppPool no
Files\dotnet C:\Program e diretórios do aplicativo.
Um FDD pode ter sido implantado e .NET Core instalado sem a reinicialização do IIS. Reinicie o servidor
ou o IIS executando net stop was /y seguido por net start w3svc em um prompt de comando.
Um FDD pode ter sido implantado sem instalar o tempo de execução do .NET Core no sistema de
hospedagem. Se o tempo de execução do .NET Core ainda não foi instalado, execute o instalador do
pacote de hospedagem do .NET Core no sistema. Consulte instalar o .NET Core hospedagem pacote.
Se você tentar instalar o runtime do .NET Core em um sistema sem uma conexão de Internet, obter o
tempo de execução de .NET todos os Downloads e execute o instalador do pacote de hospedagem para
instalar o módulo do ASP.NET Core. Conclua a instalação reiniciando o sistema ou o IIS executando net
stop was /y seguido por net start w3svc em um prompt de comando.
Um FDD pode ter sido implantado e o Microsoft Visual C++ 2015 redistribuível (x64 ) não está instalado
no sistema. Obter um instalador do Microsoft Download Center.

Argumentos incorretos do elemento <aspNetCore>


Navegador: 502.5 Erro HTTP – falha do processo
Log de aplicativo: aplicativo ' MACHINE/WEBROOT/APPHOST / {ASSEMBLY }' com raiz física ' c:\
{caminho}' Falha ao iniciar o processo com linha de comando ' "dotnet".{ . dll de assembly}', código de
erro = ' 0x80004005: 80008081.
Log de módulo do ASP.NET Core: a execução do aplicativo não existe: ' caminho{assembly}. dll '
Solução de problemas:
Confirme se o aplicativo é executado localmente no Kestrel. Uma falha do processo pode ser o resultado
de um problema no aplicativo. Para obter mais informações, consulte solução de problemas.
Examine o argumentos atributo no <aspNetCore> elemento Web. config para confirmar se ele é (a) .{ . dll
de assembly } para uma implantação de framework dependente (FDD ); ou (b) não está presente, uma
cadeia de caracteres vazia (argumentos = ""), ou uma lista de argumentos do aplicativo (argumentos =
"arg1, arg2,...") para uma implantação autossuficiente (SCD ).

Versão do .NET Framework ausente


Navegador: 502.3 Gateway incorreto – Erro de conexão ao tentar encaminhar a solicitação.
Log de aplicativo: ErrorCode = aplicativo ' MACHINE/WEBROOT/APPHOST / {ASSEMBLY }' com raiz
física ' c:\{caminho}' Falha ao iniciar o processo com linha de comando ' "dotnet".{ . dll de assembly}',
código de erro = ' 0x80004005: 80008081.
Log do Módulo do ASP.NET Core: exceção do método, arquivo ou assembly ausente. O método, o
arquivo ou o assembly especificado na exceção é um método, arquivo ou assembly do .NET Framework.
Solução de problemas:
Instale a versão do .NET Framework ausente no sistema.
Para uma implantação do framework dependente (FDD ), confirme que o tempo de execução correto
instalada no sistema. Se o projeto é atualizado de 1.1 para 2.0, o sistema host, implantados e os
resultados dessa exceção, verifique se o framework 2.0 está no sistema de hospedagem.
Pool de aplicativos interrompido
Navegador: 503 Serviço não disponível
Log do Aplicativo: nenhuma entrada
Log do Módulo do ASP.NET Core: arquivo de log não criado
Solução de problemas
Confirme que o Pool de aplicativos não está no parado estado.

Middleware de Integração do IIS não implementado


Navegador: 502.5 Erro HTTP – falha do processo
Log de aplicativo: aplicativo ' MACHINE/WEBROOT/APPHOST / {ASSEMBLY }' com raiz física ' c:\
{caminho}' criou um processo com linha de comando ' "c:\{caminho}{assembly}. { exe | dll} "' mas o falhou
ou foi resposta não ou não escutar na porta determinada '{PORT}', código de erro = '0x800705b4'
Log do Módulo do ASP.NET Core: arquivo de log criado, mostrando uma operação normal.
Solução de problemas
Confirme se o aplicativo é executado localmente no Kestrel. Uma falha do processo pode ser o resultado
de um problema no aplicativo. Para obter mais informações, consulte solução de problemas.
Confirme se ou:
O middleware de integração de IIS for referencedby chamando o UseIISIntegration método do
aplicativo WebHostBuilder (ASP.NET Core 1. x)
As aplicativos usa o CreateDefaultBuilder método (ASP.NET Core 2. x).
Consulte Hospedando no ASP.NET Core para obter mais detalhes.

O subaplicativo inclui uma seção <manipuladores>


Navegador: 500.19 Erro HTTP – erro interno do servidor
Log do Aplicativo: nenhuma entrada
Log de módulo do ASP.NET Core: arquivo de Log criado e mostra uma operação normal para o
aplicativo raiz. Arquivo de log não foi criado para o aplicativo sub.
Solução de problemas
Confirme se o arquivo web.config do subaplicativo não inclui uma seção <handlers> .

caminho do log stdout incorreto


Navegador: o aplicativo normalmente responde.
Log de aplicativo: Aviso: não foi possível criar o stdoutLogFile \?
\C:_apps\app_folder\bin\Release\netcoreapp2.0\win10-
x64\publish\logs\path_doesnt_exist\stdout_8748_201831835937.log, ErrorCode = - 2147024893.
Log do Módulo do ASP.NET Core: arquivo de log não criado
Solução de problemas
O stdoutLogFile caminho especificado no <aspNetCore> elemento Web. config não existe. Para obter mais
informações, consulte o criação e redirecionamento de Log seção do tópico de referência de configuração
ASP.NET Core módulo.

Problema geral de configuração do aplicativo


Navegador: 502.5 Erro HTTP – falha do processo
Log de aplicativo: aplicativo ' MACHINE/WEBROOT/APPHOST / {ASSEMBLY }' com raiz física ' c:\
{caminho}' criou um processo com linha de comando ' "c:\{caminho}{assembly}. { exe | dll} "' mas o falhou
ou foi resposta não ou não escutar na porta determinada '{PORT}', código de erro = '0x800705b4'
Log do Módulo do ASP.NET Core: arquivo de log criado, mas vazio
Solução de problemas
Essa exceção geral indica que o processo não pôde iniciar, provavelmente devido a um problema de
configuração do aplicativo. Referindo-se a estrutura de diretórios, confirme que o aplicativo implantado
arquivos e pastas são apropriadas e que os arquivos de configuração do aplicativo estão presentes e conter
as configurações corretas para o aplicativo e o ambiente. Para obter mais informações, consulte solução de
problemas.
Adicionar recursos de aplicativo com uma
configuração específica de plataforma no núcleo do
ASP.NET
04/05/2018 • 10 min to read • Edit Online

Por Luke Latham


Um IHostingStartup implementação permite adicionar recursos a um aplicativo durante a inicialização de um
assembly externo fora do aplicativo Startup classe. Por exemplo, uma biblioteca de ferramentas externas pode
usar um IHostingStartup implementação para fornecer serviços a um aplicativo ou provedores de configuração
adicionais. IHostingStartup está disponível no ASP.NET Core 2.0 e posterior.
Exibir ou baixar código de exemplo (como baixar)

Descobrir assemblies carregados de hospedagem inicialização


Para descobrir a hospedagem assemblies de inicialização carregados pelo aplicativo ou bibliotecas, habilite o log e
verifique os logs de aplicativo. Erros que ocorrem durante o carregamento de assemblies são registrados.
Assemblies de inicialização de hospedagem carregados são registrados no nível de depuração e todos os erros são
registrados.
As leituras de aplicativo de exemplo do HostingStartupAssembliesKey em um string de matriz e exibe o
resultado na página de índice do aplicativo:

public class IndexModel : PageModel


{
private readonly IConfiguration _config;

public IndexModel(IConfiguration config)


{
_config = config;
}

public string[] LoadedHostingStartupAssemblies { get; private set; }

public void OnGet()


{
LoadedHostingStartupAssemblies =
_config[WebHostDefaults.HostingStartupAssembliesKey]
.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) ?? new string[0];
}
}

Desabilitar o carregamento automático de hospedagem assemblies de


inicialização
Há duas maneiras para desabilitar o carregamento automático de hospedagem assemblies de inicialização:
Definir o impedir a inicialização de hospedagem configuração de host.
Definir o ASPNETCORE_preventHostingStartup variável de ambiente.
Quando a configuração do host ou a variável de ambiente é definida como true ou 1 , hospedagem assemblies
de inicialização não são carregados automaticamente. Se ambas estiverem definidas, a configuração do host
controla o comportamento.
Desabilitando hospedagem assemblies de inicialização usando a variável de ambiente ou configuração de host
desabilita globalmente e poderá desabilitar os vários recursos de um aplicativo. Não é possível desabilitar
seletivamente um assembly de inicialização de hospedagem adicionado por uma biblioteca, a menos que a
biblioteca oferece sua própria opção de configuração no momento. Uma versão futura oferecem a capacidade de
desabilitar seletivamente a hospedagem assemblies de inicialização (consulte GitHub emitir aspnet/hospedagem
#1243).

Implementar recursos de IHostingStartup


Criar o assembly
Um IHostingStartup recurso é implantado como um assembly com base em um aplicativo de console sem um
ponto de entrada. As referências de assembly a Microsoft.AspNetCore.Hosting.Abstractions pacote:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions"
Version="2.0.0" />
</ItemGroup>

</Project>

Um HostingStartup atributo identifica uma classe como uma implementação de IHostingStartup para
carregamento e execução ao compilar o IWebHost. O exemplo a seguir, o namespace é StartupFeature , e a classe
é StartupFeatureHostingStartup :

[assembly: HostingStartup(typeof(StartupFeature.StartupFeatureHostingStartup))]

Uma classe implementa IHostingStartup . A classe configurar método usa um IWebHostBuilder para adicionar
recursos a um aplicativo:

namespace StartupFeature
{
public class StartupFeatureHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
// Use the IWebHostBuilder to add app features.
}
}
}

Ao criar um IHostingStartup do projeto, o arquivo de dependências (*. deps.json) define o runtime localização do
assembly para o bin pasta:
"targets": {
".NETCoreApp,Version=v2.0": {
"StartupFeature/1.0.0": {
"dependencies": {
"Microsoft.AspNetCore.Hosting.Abstractions": "2.0.0"
},
"runtime": {
"StartupFeature.dll": {}
}
}
}
}

Apenas uma parte do arquivo é mostrada. É o nome do assembly no exemplo StartupFeature .


Atualizar o arquivo de dependências
O local de tempo de execução é especificado no *. deps.json arquivo. Ativa o recurso de runtime elemento deve
especificar o local do assembly de tempo de execução do recurso. Prefixo de runtime local com
lib/netcoreapp2.0/ :

"targets": {
".NETCoreApp,Version=v2.0": {
"StartupFeature/1.0.0": {
"dependencies": {
"Microsoft.AspNetCore.Hosting.Abstractions": "2.0.0"
},
"runtime": {
"lib/netcoreapp2.0/StartupFeature.dll": {}
}
}
}
}

No aplicativo de exemplo, a modificação do *. deps.json arquivo é executado por um PowerShell script. O script do
PowerShell é acionado automaticamente por um destino de compilação no arquivo de projeto.
Ativação de recurso
Coloque o arquivo de assembly
O IHostingStartup arquivo de assembly de implementação deve ser bin-implantada no aplicativo ou colocadas no
repositório tempo de execução:
Para uso por usuário, coloque o assembly no repositório de tempo de execução do perfil do usuário em:

<DRIVE>\Users\<USER>\.dotnet\store\x64\netcoreapp2.0\<FEATURE_ASSEMBLY_NAME>\
<FEATURE_VERSION>\lib\netcoreapp2.0\

Para uso global, coloque o assembly no repositório de tempo de execução da instalação de núcleo do .NET:

<DRIVE>\Program Files\dotnet\store\x64\netcoreapp2.0\<FEATURE_ASSEMBLY_NAME>\
<FEATURE_VERSION>\lib\netcoreapp2.0\

Ao implantar o assembly no armazenamento de tempo de execução, o arquivo de símbolos pode ser implantado
também, mas não é necessário para o recurso funcione.
Coloque o arquivo de dependências
A implementação *. deps.json arquivo deve estar em um local acessível.
Para uso por usuário, coloque o arquivo no additonalDeps pasta do perfil do usuário .dotnet configurações:

<DRIVE>\Users\<USER>\.dotnet\x64\additionalDeps\<FEATURE_ASSEMBLY_NAME>\shared\Microsoft.NETCore.App\2.0.0\

Para uso global, coloque o arquivo no additonalDeps pasta de instalação do núcleo do .NET:

<DRIVE>\Program Files\dotnet\additionalDeps\<FEATURE_ASSEMBLY_NAME>\shared\Microsoft.NETCore.App\2.0.0\

Observe a versão 2.0.0 , reflete a versão de tempo de execução compartilhada que usa o aplicativo de destino. O
tempo de execução compartilhado é mostrado no *. runtimeconfig.json arquivo. O aplicativo de exemplo, o tempo
de execução compartilhado é especificado no HostingStartupSample.runtimeconfig.json arquivo.
Conjunto de variáveis de ambiente
Defina as seguintes variáveis de ambiente no contexto do aplicativo que usa o recurso.
ASPNETCORE_HOSTINGSTARTUPASSEMBLIES
Apenas assemblies de inicialização de hospedagem são verificados para o HostingStartupAttribute . O nome do
assembly da implementação é fornecido nessa variável de ambiente. O aplicativo de exemplo define esse valor
como StartupDiagnostics .
O valor também pode ser definido usando o Assemblies de inicialização de hospedagem configuração de host.
DOTNET_ADICIONAIS_DEPS
O local da implementação do *. deps.json arquivo.
Se o arquivo será colocado no perfil do usuário .dotnet pasta para uso por usuário:

<DRIVE>\Users\<USER>\.dotnet\x64\additionalDeps\

Se o arquivo é colocado na instalação do núcleo do .NET para uso global, forneça o caminho completo para o
arquivo:

<DRIVE>\Program Files\dotnet\additionalDeps\<FEATURE_ASSEMBLY_NAME>\shared\Microsoft.NETCore.App\2.0.0\
<FEATURE_ASSEMBLY_NAME>.deps.json

O aplicativo de exemplo define esse valor como:

%UserProfile%\.dotnet\x64\additionalDeps\StartupDiagnostics\

Para obter exemplos de como definir variáveis de ambiente para vários sistemas operacionais, consulte trabalhar
com vários ambientes.

Aplicativo de exemplo
O aplicativo de exemplo (como baixar) usa IHostingStartup para criar uma ferramenta de diagnóstico. A
ferramenta adiciona dois middlewares para o aplicativo na inicialização que fornecem informações de diagnóstico:
Serviços registrados
Endereço: esquema, host, caminho de base, caminho, cadeia de caracteres de consulta
Conexão: IP remoto, a porta remota, IP local, porta local, o certificado de cliente
Cabeçalhos de solicitação
Variáveis de ambiente
Para executar o exemplo:
1. O projeto de inicialização de diagnóstico usa PowerShell para modificar seu StartupDiagnostics.deps.json
arquivo. PowerShell é instalado por padrão em um sistema operacional do Windows a partir do Windows 7
SP1 e Windows Server 2008 R2 SP1. Para obter o PowerShell em outras plataformas, consulte instalando o
Windows PowerShell.
2. Compile o projeto de inicialização de diagnóstico. Um destino de compilação no arquivo de projeto:
Move o assembly e símbolos de arquivos para o repositório de tempo de execução do perfil do usuário.
Aciona o script do PowerShell para modificar o StartupDiagnostics.deps.json arquivo.
Move o StartupDiagnostics.deps.json arquivo para o perfil de usuário additionalDeps pasta.
3. Defina as variáveis de ambiente:
ASPNETCORE_HOSTINGSTARTUPASSEMBLIES : StartupDiagnostics
DOTNET_ADDITIONAL_DEPS : %UserProfile%\.dotnet\x64\additionalDeps\StartupDiagnostics\
4. Execute o aplicativo de exemplo.
5. Solicitar o /services ponto de extremidade para ver o aplicativo registrado serviços. Solicitar o /diag ponto
de extremidade para ver as informações de diagnóstico.
Visão geral sobre a segurança do ASP.NET Core
10/04/2018 • 6 min to read • Edit Online

O ASP.NET Core permite que desenvolvedores configurem e gerenciem facilmente a segurança de seus
aplicativos. O ASP.NET Core contém recursos para gerenciamento de autenticação, autorização, proteção de dados,
imposição de SSL, segredos de aplicativo, proteção contra falsificação de solicitação e gerenciamento de CORS.
Esses recursos de segurança permitem que você crie aplicativos de ASP.NET Core robustos e seguros ao mesmo
tempo.

Recursos de segurança do ASP.NET Core


O ASP.NET Core fornece várias ferramentas e bibliotecas para proteger seus aplicativos, incluindo provedores de
identidade internos, mas você pode usar serviços de identidade de terceiros, como Facebook, Twitter e LinkedIn.
Com o ASP.NET Core, você pode gerenciar facilmente os segredos do aplicativo, que são uma maneira de
armazenar e usar informações confidenciais sem a necessidade de expô-los no código.

Autenticação versus Autorização


A autenticação é um processo em que um usuário fornece credenciais que são comparadas àquelas armazenadas
em um sistema operacional, num banco de dados, no aplicativo ou no recurso. Se elas corresponderem, os
usuários se autenticarão com êxito e, assim, poderão realizar ações para as quais são autorizados, durante um
processo de autorização. A autorização é o processo que determina o que um usuário pode fazer.
Outra forma de pensar na autenticação é considerá-la como uma maneira de entrar em um espaço, como um
servidor, um banco de dados, um aplicativo ou um recurso, ao passo que a autorização refere-se a quais ações o
usuário poderá executar em que objetos dentro desse espaço (servidor, banco de dados ou aplicativo).

Vulnerabilidades comuns no software


O ASP.NET Core e o EF contêm recursos que ajudam a proteger seus aplicativos e impedir violações de segurança.
A seguinte lista de links leva à documentação com detalhe de técnicas para evitar as vulnerabilidades de segurança
mais comuns em aplicativos Web:
Ataques de script entre sites
Ataques de injeção de SQL
CSRF (solicitação intersite forjada)
Ataques de redirecionamento aberto
Há mais vulnerabilidades sobre as quais você deve estar atento. Para obter mais informações, consulte a seção
neste documento em Documentação de segurança do ASP.NET.

Documentação de segurança do ASP.NET


Autenticação
Introdução ao Identity
Habilitar a autenticação usando o Facebook, o Google e outros provedores externos
Habilitar a autenticação com o Web Services Federation
Configurar a Autenticação do Windows
Confirmação de conta e recuperação de senha
Autenticação de dois fatores com SMS
Usar a autenticação de cookie sem o Identity
Azure Active Directory
Integrar o Azure AD em um aplicativo Web ASP.NET Core
Chamar uma API Web ASP.NET Core em um aplicativo do WPF usando o Azure AD
Chamar uma API Web em um aplicativo Web ASP.NET Core usando o Azure AD
Um aplicativo Web ASP.NET Core com o Azure AD B2C
Proteger aplicativos ASP.NET Core com o IdentityServer4
Autorização
Introdução
Criar um aplicativo com os dados do usuário protegidos por autorização
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
Visão geral das APIs de consumidor
Cadeias de caracteres de finalidade
Multilocação e hierarquia de finalidade
Senhas hash
Limitar o tempo de vida de cargas protegidas
Desproteger cargas cujas chaves foram revogadas
Configuração
Configurar a proteção de dados
Configurações padrão
Política ampla de computador
Cenários sem reconhecimento de DI
APIs de extensibilidade
Extensibilidade da criptografia básica
Extensibilidade de gerenciamento de chaves
APIs diversas
Implementação
Detalhes de criptografia autenticada
Derivação de subchaves e criptografia autenticada
Cabeçalhos de contexto
Gerenciamento de chaves
Provedores de armazenamento de chaves
Criptografia de chave em repouso
Imutabilidade de chave e configurações
Formato do armazenamento de chaves
Provedores de proteção de dados efêmeros
Compatibilidade
Substitua no ASP.NET
Criar um aplicativo com os dados do usuário protegidos por autorização
Armazenamento seguro dos segredos do aplicativo no desenvolvimento
Provedor de configuração do Azure Key Vault
Impor o SSL
Falsificação anti-solicitação
Prevenir ataques de redirecionamento abertos
Evitar scripts entre sites
Habilitar o CORS (Solicitações Entre Origens)
Compartilhar cookies entre aplicativos
Autenticação no ASP.NET Core
19/03/2018 • 1 min to read • Edit Online

Opções de autenticação de OSS da comunidade


Introdução ao Identity
Habilitar a autenticação usando o Facebook, o Google e outros provedores externos
Habilitar a autenticação com o Web Services Federation
Habilitar a geração de código QR no Identity
Configurar a Autenticação do Windows
Confirmação de conta e recuperação de senha
Autenticação de dois fatores com SMS
Usar a autenticação de cookie sem o Identity
Azure Active Directory
Integrar o Azure AD em um aplicativo Web ASP.NET Core
Integrar o Azure AD B2C em um aplicativo Web do ASP.NET Core voltado para o cliente
Integrar o Azure AD B2C em uma API Web do ASP.NET Core
Chamar uma API Web ASP.NET Core em um aplicativo do WPF usando o Azure AD
Chamar uma API Web em um aplicativo Web ASP.NET Core usando o Azure AD
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)
Artigos baseados em projetos criados com contas de usuário individuais
Opções de autenticação de comunidade OSS para
ASP.NET Core
15/03/2018 • 1 min to read • Edit Online

Esta página contém as opções de autenticação fornecido pela comunidade de código aberto para o ASP.NET Core.
Esta página é atualizada periodicamente como novos provedores de se tornar disponíveis.

Provedores de autenticação de sistemas operacionais


A lista a seguir está classificada alfabeticamente.

NOME DESCRIÇÃO

AspNet.Security.OpenIdConnect.Server (ASOS) ASOS é um nível baixo, o primeiro protocolo OpenID Connect


server framework para ASP.NET Core e OWIN/Katana.

IdentityServer IdentityServer é uma estrutura de OpenID Connect e OAuth


2.0 para ASP.NET Core, certificada oficialmente a base de
OpenID e sob controle do Foundation .NET. Para obter mais
informações, consulte bem-vindo ao IdentityServer4
(documentação).

OpenIddict OpenIddict é um servidor de OpenID Connect de fácil de usar


para ASP.NET Core.

Cierge Cierge é um servidor de OpenID Connect que manipula a


inscrição de usuário, logon, perfis, gerenciamento, logons
sociais e muito mais.

Para adicionar um provedor, editar esta página.


Introdução à identidade do ASP.NET Core
04/05/2018 • 16 min to read • Edit Online

Por Pranav Rastogi, Rick Anderson, Tom Dykstra, Jon Galloway Erik Reitan, e Steve Smith
A identidade do ASP.NET Core é um sistema de associação que permite que você adicione a funcionalidade de
login ao seu aplicativo. Os usuários podem criar uma conta e um logon com nome de usuário e senha ou usar
um provedor de logon externo, como Facebook, Google, Microsoft Account, Twitter ou outros.
Você pode configurar a identidade do ASP.NET Core para usar um banco de dados do SQL Server para
armazenar os nomes de usuário, senhas e dados de perfil. Como alternativa, você pode usar seu próprio
armazenamento persistente, por exemplo, um armazenamento de tabela do Azure. Este documento contém
instruções para Visual Studio e usando a CLI.
Exibir ou baixar o código de exemplo. (Como fazer o download)

Visão geral da identidade


Neste tópico, você vai aprender a usar a identidade do ASP.NET Core para adicionar a funcionalidade para se
registrar, fazer logon e fazer logoff de um usuário. Para obter instruções mais detalhadas sobre a criação de
aplicativos usando a identidade do ASP.NET Core, consulte a seção Próximas etapas no final deste artigo.
1. Crie um projeto de aplicativo Web do ASP.NET Core com contas de usuário individuais.
Visual Studio
CLI do .NET Core
No Visual Studio, selecione arquivo > novo > projeto. Selecione aplicativo Web do ASP.NET Core
e clique em Okey.

Selecione um ASP.NET Core aplicativo Web (Model-View-Controller) do ASP.NET Core 2. x, em


seguida, selecione alterar autenticação.

Oferta de será exibida uma caixa de diálogo Opções de autenticação. Selecione contas de usuário
individuais e clique em Okey para retornar à caixa de diálogo anterior.

Selecionando contas de usuário individuais direciona o Visual Studio para criar modelos,
ViewModels, modos de exibição, controladores e outros ativos necessários para a autenticação como
parte do modelo de projeto.

2. Configurar os serviços de identidade e adicionar middleware em Startup .


Os serviços de identidade são adicionados ao aplicativo o ConfigureServices método o Startup classe:
ASP.NET Core 2.x
ASP.NET Core 1.x
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = false;
options.Password.RequiredUniqueChars = 6;

// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;

// User settings
options.User.RequireUniqueEmail = true;
});

services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
// If the LoginPath isn't set, ASP.NET Core defaults
// the path to /Account/Login.
options.LoginPath = "/Account/Login";
// If the AccessDeniedPath isn't set, ASP.NET Core defaults
// the path to /Account/AccessDenied.
options.AccessDeniedPath = "/Account/AccessDenied";
options.SlidingExpiration = true;
});

// Add application services.


services.AddTransient<IEmailSender, EmailSender>();

services.AddMvc();
}

Esses serviços são disponibilizados para o aplicativo por meio de injeção de dependência.
Identidade é habilitada para o aplicativo chamando UseAuthentication no Configure método.
UseAuthentication Adiciona autenticação middleware para o pipeline de solicitação.
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}

app.UseStaticFiles();

app.UseAuthentication();

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

Para obter mais informações sobre o processo de inicialização do aplicativo, consulte inicialização do aplicativo.
3. Crie um usuário.
Inicie o aplicativo e, em seguida, clique no link Registrar.
Se esta for a primeira vez em que você estiver executando essa ação, talvez seja necessário para executar
migrações. O aplicativo solicita que você migrações aplicar. Se necessário, atualize a página.

Como alternativa, você pode testar usando a identidade do ASP.NET Core com seu aplicativo sem um
banco de dados persistente usando um banco de dados na memória. Para usar um banco de dados na
memória, adicione o Microsoft.EntityFrameworkCore.InMemory do pacote para seu aplicativo e modifique
a chamada do aplicativo para AddDbContext na ConfigureServices da seguinte maneira:

services.AddDbContext<ApplicationDbContext>(options =>
options.UseInMemoryDatabase(Guid.NewGuid().ToString()));

Quando o usuário clica o registrar link, o Register ação é invocada no AccountController . O Register
ação cria o usuário chamando CreateAsync no _userManager objeto (fornecido para AccountController
por injeção de dependência):
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// For more information on how to enable account confirmation and password reset please
visit http://go.microsoft.com/fwlink/?LinkID=532713
// Send an email with this link
//var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
//var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code =
code }, protocol: HttpContext.Request.Scheme);
//await _emailSender.SendEmailAsync(model.Email, "Confirm your account",
// "Please confirm your account by clicking this link: <a href=\"" + callbackUrl +
"\">link</a>");
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(3, "User created a new account with password.");
return RedirectToAction(nameof(HomeController.Index), "Home");
}
AddErrors(result);
}

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


return View(model);
}

Se o usuário foi criado com êxito, o usuário é conectado pela chamada para _signInManager.SignInAsync .
Observação: consulte a confirmação de conta para verificar as etapas para impedir o logon imediato no
registro.
4. Iniciar sessão.
Os usuários podem entrar clicando no login, no link na parte superior do site, ou podem ser
direcionados para a página de login se tentarem acessar uma parte do site que requer autorização.
Quando o usuário envia o formulário na página de login, a ação AccountController Login é chamada.
O Login faz chamadas PasswordSignInAsync no objeto _signInManager (fornecido pelo
AccountController por injeção de dependência).
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(model.Email,
model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation(1, "User logged in.");
return RedirectToLocal(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe =
model.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning(2, "User account locked out.");
return View("Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
}

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


return View(model);
}

A base de Controller classe expõe um User propriedade que você pode acessar de métodos do
controlador. Por exemplo, você pode enumerar User.Claims e tomar decisões de autorização. Para obter
mais informações, consulte autorização.
5. Faça logoff.
Clicando o logoff link chamadas a LogOut ação.

//
// POST: /Account/LogOut
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> LogOut()
{
await _signInManager.SignOutAsync();
_logger.LogInformation(4, "User logged out.");
return RedirectToAction(nameof(HomeController.Index), "Home");
}

O código anterior acima chama o _signInManager.SignOutAsync método. O SignOutAsync método limpa


declarações do usuário armazenadas em um cookie.
6. Configuração.
Identidade tem alguns comportamentos padrão que podem ser substituídos na classe de inicialização do
aplicativo. IdentityOptions não precisa ser configurado ao usar os comportamentos padrão. O código a seguir
define várias opções de força de senha:
ASP.NET Core 2.x
ASP.NET Core 1.x

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = false;
options.Password.RequiredUniqueChars = 6;

// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;

// User settings
options.User.RequireUniqueEmail = true;
});

services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
// If the LoginPath isn't set, ASP.NET Core defaults
// the path to /Account/Login.
options.LoginPath = "/Account/Login";
// If the AccessDeniedPath isn't set, ASP.NET Core defaults
// the path to /Account/AccessDenied.
options.AccessDeniedPath = "/Account/AccessDenied";
options.SlidingExpiration = true;
});

// Add application services.


services.AddTransient<IEmailSender, EmailSender>();

services.AddMvc();
}

Para obter mais informações sobre como configurar a identidade, consulte configurar identidade.
Você também pode configurar o tipo de dados da chave primária, consulte tipo de dados de chaves primárias
de configurar identidade.
7. Exiba o banco de dados.
Se o seu aplicativo estiver usando um banco de dados do SQL Server (o padrão no Windows e para
usuários do Visual Studio), você poderá exibir o banco de dados criado pelo aplicativo. Você pode usar
SQL Server Management Studio. Como alternativa, no Visual Studio, selecione exibição >
Pesquisador de objetos do SQL Server. Conecte-se ao (localdb) \MSSQLLocalDB. O banco de
dados com um nome correspondente aspnet - <nome do projeto>-<cadeia de caracteres de data > é
exibido.

Expanda o banco de dados e sua tabelas, clique com o dbo. AspNetUsers de tabela e selecione exibir
dados.
8. Verifique se a identidade está funcionando
O modelo de projeto padrão aplicativo Web do ASP.NET Core permite que os usuários acessem
qualquer ação no aplicativo sem precisar fazer logon. Para verificar se a identidade do ASP.NET
funciona, adicione um [Authorize] como atributo para a ação About do controlador Home .

[Authorize]
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
return View();
}

Visual Studio
CLI do .NET Core
Execute o projeto usando Ctrl + F5 e navegue até o sobre página. Somente usuários autenticados
podem acessar o sobre página agora, para que o ASP.NET redireciona para a página de logon para
efetuar login ou registrar.

Componentes de identidade
O assembly de referência principal para o sistema de identidade é Microsoft.AspNetCore.Identity . Este pacote
contém o conjunto principal de interfaces para a identidade do ASP.NET Core e está incluído por
Microsoft.AspNetCore.Identity.EntityFrameworkCore .

Essas dependências são necessárias para usar o sistema de identidade em aplicativos do ASP.NET Core:
Microsoft.AspNetCore.Identity.EntityFrameworkCore -Contém os tipos necessários para usar a identidade
com o Entity Framework Core.
Microsoft.EntityFrameworkCore.SqlServer -Entity Framework Core é uma tecnologia de acesso a dados
recomendada da Microsoft para bancos de dados relacionais, como o SQL Server. Para testar, você pode
usar Microsoft.EntityFrameworkCore.InMemory .
Microsoft.AspNetCore.Authentication.Cookies -Middleware que permite que um aplicativo usar a
autenticação baseada em cookie.
Migrar a identidade do ASP.NET Core
Para obter informações adicionais e diretrizes sobre como migrar sua identidade existente store consulte
migrar autenticação e identidade.

Definindo o nível de senha


Consulte configuração para obter um exemplo que defina os requisitos mínimos de senha.

Próximas etapas
Migrar de autenticação e identidade
Confirmação de conta e recuperação de senha
Autenticação de dois fatores com SMS
Facebook, Google e autenticação do provedor externo
Configurar a identidade do ASP.NET Core
10/04/2018 • 12 min to read • Edit Online

Identidade do ASP.NET Core usa a configuração padrão para configurações como a política de senha, tempo de
bloqueio e configurações de cookie. Essas configurações podem ser substituídas no aplicativo do Startup classe.

Opções de identidade
O IdentityOptions classe representa as opções que podem ser usadas para configurar o sistema de identidade.
Identidade baseada em declarações
IdentityOptions.ClaimsIdentity Especifica o ClaimsIdentityOptions com as propriedades mostradas na tabela.

PROPRIEDADE DESCRIÇÃO PADRÃO

RoleClaimType Obtém ou define o tipo de declaração ClaimTypes.Role


usado em uma declaração de função.

SecurityStampClaimType Obtém ou define o tipo de declaração AspNet.Identity.SecurityStamp


usado para a declaração de carimbo de
segurança.

UserIdClaimType Obtém ou define o tipo de declaração ClaimTypes.NameIdentifier


usado para a declaração do
identificador de usuário.

UserNameClaimType Obtém ou define o tipo de declaração ClaimTypes.Name


usado para a declaração de nome de
usuário.

Bloqueio
Impeça o usuário para um período de tempo após um determinado número de tentativas de acesso com falha
(padrão: bloqueio de 5 minutos após 5 tentativas de acesso). Uma autenticação bem-sucedida redefine a
contagem de tentativas de acesso com falha e redefine o relógio.
O exemplo a seguir mostra os valores padrão:

services.AddIdentity<ApplicationUser, IdentityRole>(options =>


{
// Lockout settings
options.Lockout.AllowedForNewUsers = true;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

Confirme se PasswordSignInAsync define lockoutOnFailure para true :

var result = await _signInManager.PasswordSignInAsync(


Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);
IdentityOptions.Lockout Especifica o LockoutOptions com as propriedades mostradas na tabela.

PROPRIEDADE DESCRIÇÃO PADRÃO

AllowedForNewUsers Determina se um novo usuário poderá true


ser bloqueado.

DefaultLockoutTimeSpan A quantidade de tempo de uma usuário 5 minutos


é bloqueada quando ocorre um
bloqueio.

MaxFailedAccessAttempts O número de tentativas de acesso com 5


falha até que um usuário seja
bloqueado, se o bloqueio é ativado.

Senha
Por padrão, a identidade requer que as senhas conter um caractere maiusculo, caractere minúsculo, um dígito e
um caractere não alfanumérico. Senhas devem ter pelo menos seis caracteres. PasswordOptions podem ser
alterados no Startup.ConfigureServices .
ASP.NET Core 2.x
ASP.NET Core 1.x
ASP.NET Core 2.0 adicionado o RequiredUniqueChars propriedade. Caso contrário, as opções são o mesmo que o
ASP.NET Core 1. x.

services.AddIdentity<ApplicationUser, IdentityRole>(options =>


{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequiredUniqueChars = 2;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

IdentityOptions.Password Especifica o PasswordOptions com as propriedades mostradas na tabela.

PROPRIEDADE DESCRIÇÃO PADRÃO

RequireDigit Requer um número entre 0-9, a senha. true

RequiredLength O comprimento mínimo da senha. 6

RequiredUniqueChars Só se aplica ao ASP.NET Core 2.0 ou 1


posterior.

Requer o número de caracteres


distintos na senha.

RequireLowercase Requer um caractere minúsculo na true


senha.
PROPRIEDADE DESCRIÇÃO PADRÃO

RequireNonAlphanumeric Exige um caractere não alfanumérico na true


senha.

RequireUppercase Requer um caractere maiusculo na true


senha.

entrar

services.AddIdentity<ApplicationUser, IdentityRole>(options =>


{
// Signin settings
options.SignIn.RequireConfirmedEmail = true;
options.SignIn.RequireConfirmedPhoneNumber = false;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

IdentityOptions.SignIn Especifica o SignInOptions com as propriedades mostradas na tabela.

PROPRIEDADE DESCRIÇÃO PADRÃO

RequireConfirmedEmail Requer um email confirmado para false


entrar.

RequireConfirmedPhoneNumber Requer um número de telefone false


confirmado entrar.

Tokens
IdentityOptions.Tokens Especifica o TokenOptions com as propriedades mostradas na tabela.

PROPRIEDADE DESCRIÇÃO

AuthenticatorTokenProvider Obtém ou define o AuthenticatorTokenProvider usado


para validar entradas de dois fatores com um autenticador.

ChangeEmailTokenProvider Obtém ou define o ChangeEmailTokenProvider usado para


gerar tokens usados nos emails de confirmação de alteração
de email.

ChangePhoneNumberTokenProvider Obtém ou define o ChangePhoneNumberTokenProvider


usado para gerar tokens usados ao alterar os números de
telefone.

EmailConfirmationTokenProvider Obtém ou define o provedor de token usado para gerar


tokens usados nos emails de confirmação de conta.

PasswordResetTokenProvider Obtém ou define o IUserTwoFactorTokenProvider usado para


gerar tokens usados nos emails de redefinição de senha.

ProviderMap Usado para construir um provedor de Token de usuário com a


chave usada como o nome do provedor.

User
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
// User settings
options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

IdentityOptions.User Especifica o UserOptions com as propriedades mostradas na tabela.

PROPRIEDADE DESCRIÇÃO PADRÃO

AllowedUserNameCharacters Caracteres permitidos no nome do abcdefghijklmnopqrstuvwxyz


usuário. ABCDEFGHIJKLMNOPQRSTUVWXYZ
0123456789
-.@+

RequireUniqueEmail Requer que cada usuário tenha um false


email exclusivo.

Configurações de cookie
Configurar o cookie do aplicativo no Startup.ConfigureServices :
ASP.NET Core 2.x
ASP.NET Core 1.x

services.ConfigureApplicationCookie(options =>
{
options.AccessDeniedPath = "/Account/AccessDenied";
options.Cookie.Name = "YourAppCookieName";
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
options.LoginPath = "/Account/Login";
// ReturnUrlParameter requires `using Microsoft.AspNetCore.Authentication.Cookies;`
options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
options.SlidingExpiration = true;
});

CookieAuthenticationOptions tem as seguintes propriedades:

PROPRIEDADE DESCRIÇÃO

AccessDeniedPath Informa o manipulador que deve alterar uma saída 403


Proibido código de status em um redirecionamento 302 no
caminho especificado.

O valor padrão é /Account/AccessDenied .

AuthenticationScheme Aplica-se somente a ASP.NET Core 1. x.

O nome lógico para um esquema de autenticação específico.


PROPRIEDADE DESCRIÇÃO

AutomaticAuthenticate Aplica-se somente a ASP.NET Core 1. x.

Quando for verdadeiro, autenticação de cookie deve executar


em cada solicitação e tente validar e reconstrua qualquer
entidade de segurança serializada criado por ele.

AutomaticChallenge Aplica-se somente a ASP.NET Core 1. x.

Se for true, o middleware de autenticação manipula desafios


automática. Se false, o middleware de autenticação altera
somente respostas quando explicitamente indicado pelo
AuthenticationScheme .

ClaimsIssuer Obtém ou define o emissor deve ser usado para quaisquer


declarações que são criadas (herdado de
AuthenticationSchemeOptions).

Cookie.Domain O domínio para associar o cookie.

Cookie.Expiration Obtém ou define o tempo de vida do cookie HTTP (não o


cookie de autenticação). Esta propriedade é substituída por
ExpireTimeSpan. Ele não deve ser usado no contexto de
CookieAuthentication.

Cookie.HttpOnly Indica se um cookie pode ser acessado pelo script do lado do


cliente.

O valor padrão é true .

Cookie.Name O nome do cookie.

O valor padrão é .AspNetCore.Cookies .

Cookie.Path O caminho do cookie.

Cookie.SameSite O SameSite atributo do cookie.

O valor padrão é SameSiteMode.Lax.

Cookie.SecurePolicy O CookieSecurePolicy configuração.

O valor padrão é CookieSecurePolicy.SameAsRequest.

CookieDomain Aplica-se somente a ASP.NET Core 1. x.

O nome de domínio em que o cookie é atendido.

CookieHttpOnly Aplica-se somente a ASP.NET Core 1. x.

Um sinalizador que indica se o cookie deve ser acessível


somente para servidores.

O valor padrão é true .


PROPRIEDADE DESCRIÇÃO

CookiePath Aplica-se somente a ASP.NET Core 1. x.

Usado para isolar os aplicativos em execução no mesmo nome


de host.

CookieSecure Aplica-se somente a ASP.NET Core 1. x.

Um sinalizador que indica se o cookie criado deve ser limitado


para HTTPS ( CookieSecurePolicy.Always ), HTTP ou HTTPS (
CookieSecurePolicy.None ), ou o mesmo protocolo que a
solicitação ( CookieSecurePolicy.SameAsRequest ).

O valor padrão é CookieSecurePolicy.SameAsRequest .

CookieManager O componente usado para obter cookies de solicitação ou


defini-las na resposta.

DataProtectionProvider Se definido, o provedor usado pelo


CookieAuthenticationHandler para proteção de dados.

Descrição Aplica-se somente a ASP.NET Core 1. x.

Informações adicionais sobre o tipo de autenticação que é


disponibilizado para o aplicativo.

Eventos O manipulador chama métodos no provedor que dê o


controle de aplicativo em determinados pontos onde ocorre o
processamento.

EventsType Se definido, o tipo de serviço para obter o Events instância


em vez da propriedade (herdado de
AuthenticationSchemeOptions).

ExpireTimeSpan Controles de quanto tempo o tíquete de autenticação


armazenado no cookie permanece válido desde o ponto em
que ele é criado.

O valor padrão é 14 dias.

LoginPath Quando um usuário está autorizado, eles são redirecionados


para esse caminho para fazer logon.

O valor padrão é /Account/Login .

LogoutPath Quando um usuário é desconectado, eles serão


redirecionados a esse caminho.

O valor padrão é /Account/Logout .

ReturnUrlParameter Determina o nome do parâmetro de cadeia de caracteres de


consulta que é acrescentado pelo middleware quando um 401
não autorizado código de status é alterado para um
redirecionamento 302 até o caminho de logon.

O valor padrão é ReturnUrl .


PROPRIEDADE DESCRIÇÃO

SessionStore Um contêiner opcional no qual armazenar a identidade entre


solicitações.

slidingExpiration Quando for verdadeiro, um novo cookie é emitido com uma


nova hora de expiração quando o cookie atual é mais de meio
a janela de expiração.

O valor padrão é true .

TicketDataFormat O TicketDataFormat é usado para proteger e desproteger a


identidade e outras propriedades que são armazenadas no
valor do cookie.
Configurar a autenticação do Windows no ASP.NET
Core
04/05/2018 • 8 min to read • Edit Online

Por Steve Smith e Scott Addie


Autenticação do Windows pode ser configurada para aplicativos ASP.NET Core hospedados no IIS, HTTP.sys,
ou WebListener.

O que é autenticação do Windows?


Autenticação do Windows se baseia no sistema operacional para autenticar os usuários de aplicativos do
ASP.NET Core. Você pode usar a autenticação do Windows quando o servidor é executado em uma rede
corporativa usando identidades do domínio do Active Directory ou outras contas do Windows para identificar
os usuários. Autenticação do Windows é mais adequada para ambientes de intranet em que os usuários, os
aplicativos cliente e servidores web pertencem ao mesmo domínio do Windows.
Saiba mais sobre a autenticação do Windows e instalá-lo para o IIS.

Habilitar a autenticação do Windows em um aplicativo do ASP.NET


Core
O modelo de aplicativo de Web do Visual Studio pode ser configurado para dar suporte à autenticação do
Windows.
Use o modelo de aplicativo de autenticação do Windows
No Visual Studio:
1. Crie um novo Aplicativo Web ASP.NET Core.
2. Selecione o aplicativo Web da lista de modelos.
3. Selecione o alterar autenticação botão e selecione autenticação do Windows.
Execute o aplicativo. O nome de usuário aparece no canto superior direito do aplicativo.
Para o trabalho de desenvolvimento usando o IIS Express, o modelo fornece todas as configurações
necessárias para usar a autenticação do Windows. A seção a seguir mostra como configurar manualmente um
aplicativo ASP.NET Core para autenticação do Windows.
Configurações do Visual Studio para Windows e autenticação anônima
O projeto do Visual Studio propriedades da página depurar guia fornece caixas de seleção para autenticação
anônima e autenticação do Windows.
Como alternativa, essas duas propriedades podem ser definidas no launchSettings.json arquivo:

{
"iisSettings": {
"windowsAuthentication": true,
"anonymousAuthentication": false,
"iisExpress": {
"applicationUrl": "http://localhost:52171/",
"sslPort": 0
}
} // additional options trimmed
}

Habilitar a autenticação do Windows com o IIS


O IIS usa o ASP.NET Core módulo para hospedar aplicativos do ASP.NET Core. A módulo fluxos autenticação
do Windows para o IIS por padrão. Autenticação do Windows é configurada no IIS, não o aplicativo. As seções
a seguir mostram como usar o Gerenciador do IIS para configurar um aplicativo ASP.NET Core para usar a
autenticação do Windows.
Criar um novo site do IIS
Especifique um nome e uma pasta e permitir que ele criar um novo pool de aplicativos.
Personalizar a autenticação
Abra o menu de autenticação para o site.
Desabilitar a autenticação anônima e habilitar a autenticação do Windows.

Publicar seu projeto para a pasta de site do IIS


Usando o Visual Studio ou o .NET Core CLI, publica o aplicativo para a pasta de destino.
Saiba mais sobre publicar no IIS.
Inicie o aplicativo para verificar a autenticação do Windows está funcionando.

Habilitar a autenticação do Windows com o HTTP.sys ou WebListener


ASP.NET Core 2.x
ASP.NET Core 1.x
Embora Kestrel não dá suporte a autenticação do Windows, você pode usar HTTP.sys para dar suporte a
cenários de hospedagem interna no Windows. O exemplo a seguir configura o host de web do aplicativo para
usar o HTTP. sys com autenticação do Windows:

public class Program


{
public static void Main(string[] args) =>
BuildWebHost(args).Run();

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


WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseHttpSys(options =>
{
options.Authentication.Schemes =
AuthenticationSchemes.NTLM | AuthenticationSchemes.Negotiate;
options.Authentication.AllowAnonymous = false;
})
.Build();
}

Trabalhar com a autenticação do Windows


O estado de configuração do acesso anônimo determina o modo no qual o [Authorize] e [AllowAnonymous]
os atributos são usados no aplicativo. As seções a seguir explicam como lidar com os estados de configuração
permitidas e não permitidas do acesso anônimo.
Não permitir acesso anônimo
Quando a autenticação do Windows está habilitada e o acesso anônimo é desabilitado, o [Authorize] e
[AllowAnonymous] atributos não têm nenhum efeito. Se o site do IIS (ou servidor HTTP. sys ou WebListener ) é
configurado para não permitir acesso anônimo, a solicitação nunca atinge seu aplicativo. Por esse motivo, o
[AllowAnonymous] atributo não é aplicável.

Permitir o acesso anônimo


Quando a autenticação do Windows e o acesso anônimo estão habilitados, use o [Authorize] e
[AllowAnonymous] atributos. O [Authorize] atributo permite proteger partes do aplicativo que realmente
exigem a autenticação do Windows. O [AllowAnonymous] atributo substituições [Authorize] atributo uso em
aplicativos que permitem o acesso anônimo. Consulte autorização simples para obter detalhes de uso do
atributo.
No núcleo do ASP.NET 2. x, o [Authorize] atributo requer configuração adicional no Startup.cs desafiar
solicitações anônimas para a autenticação do Windows. A configuração recomendada varia ligeiramente com
base no servidor web que está sendo usado.

OBSERVAÇÃO
Por padrão, os usuários que não têm autorização para acessar uma página são apresentados com uma resposta HTTP
403 vazia. O StatusCodePages middleware podem ser configurados para fornecer aos usuários uma experiência melhor
de "Acesso negado".

IIS
Se usar o IIS, adicione o seguinte para o ConfigureServices método:

// IISDefaults requires the following import:


// using Microsoft.AspNetCore.Server.IISIntegration;
services.AddAuthentication(IISDefaults.AuthenticationScheme);

HTTP.sys
Se usar o HTTP. sys, adicione o seguinte para o ConfigureServices método:

// HttpSysDefaults requires the following import:


// using Microsoft.AspNetCore.Server.HttpSys;
services.AddAuthentication(HttpSysDefaults.AuthenticationScheme);

Representação
ASP.NET Core não implementa a representação. Os aplicativos executados com a identidade de aplicativo para
todas as solicitações, usando a identidade de processo ou pool de aplicativo. Se você precisar executar
explicitamente uma ação em nome do usuário, use WindowsIdentity.RunImpersonated . Executar uma única ação
neste contexto e, em seguida, feche o contexto.
app.Run(async (context) =>
{
try
{
var user = (WindowsIdentity)context.User.Identity;

await context.Response
.WriteAsync($"User: {user.Name}\tState: {user.ImpersonationLevel}\n");

WindowsIdentity.RunImpersonated(user.AccessToken, () =>
{
var impersonatedUser = WindowsIdentity.GetCurrent();
var message =
$"User: {impersonatedUser.Name}\tState: {impersonatedUser.ImpersonationLevel}";

var bytes = Encoding.UTF8.GetBytes(message);


context.Response.Body.Write(bytes, 0, bytes.Length);
});
}
catch (Exception e)
{
await context.Response.WriteAsync(e.ToString());
}
});

Observe que RunImpersonated não dá suporte a operações assíncronas e não deve ser usado para cenários
complexos. Por exemplo, solicitações inteiras ou cadeias de middleware de encapsulamento não é suportado
ou recomendado.
Configurar o tipo de dados de chave primária de
identidade no núcleo do ASP.NET
04/05/2018 • 3 min to read • Edit Online

Identidade do ASP.NET Core permite que você configure o tipo de dados usado para representar uma chave
primária. Identidade usa o string tipo de dados por padrão. Você pode substituir esse comportamento.

Personalizar o tipo de dados de chave primária


1. Criar uma implementação personalizada do IdentityUser classe. Representa o tipo a ser usado para criar
objetos de usuário. No exemplo a seguir, o padrão string tipo é substituído pelo Guid .

namespace webapptemplate.Models
{
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser<Guid>
{
}
}

2. Criar uma implementação personalizada do IdentityRole classe. Representa o tipo a ser usado para criar
objetos de função. No exemplo a seguir, o padrão string tipo é substituído pelo Guid .

namespace webapptemplate.Models
{
public class ApplicationRole : IdentityRole<Guid>
{
}
}

3. Crie uma classe de contexto de banco de dados personalizado. Ela herda da classe de contexto de banco de
dados do Entity Framework usada para identidade. O TUser e TRole argumentos referenciar as classes de
usuário e a função personalizadas criadas na etapa anterior, respectivamente. O Guid tipo de dados é
definido para a chave primária.

namespace webapptemplate.Data
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}

protected override void OnModelCreating(ModelBuilder builder)


{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}
}
4. Registre a classe de contexto de banco de dados personalizados ao adicionar o serviço de identidade na
classe de inicialização do aplicativo.
ASP.NET Core 2.x
ASP.NET Core 1.x
O AddEntityFrameworkStores método não aceita um TKey argumento como fazia no ASP.NET Core 1. x.
Tipo de dados da chave primária é deduzido analisando a DbContext objeto.

public void ConfigureServices(IServiceCollection services)


{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

// Add application services.


services.AddTransient<IEmailSender, EmailSender>();

services.AddMvc();

Teste as alterações
Após a conclusão das alterações de configuração, a propriedade que representa a chave primária reflete o novo
tipo de dados. O exemplo a seguir demonstra como acessar a propriedade em um controlador MVC.

[HttpGet]
[AllowAnonymous]
public async Task<Guid> GetCurrentUserId()
{
ApplicationUser user = await _userManager.GetUserAsync(HttpContext.User);
return user.Id; // No need to cast here because user.Id is already a Guid, and not a string
}
Provedores de armazenamento personalizado para a
identidade do ASP.NET Core
04/05/2018 • 20 min to read • Edit Online

Por Steve Smith


Identidade do ASP.NET Core é um sistema extensível que permite que você criar um provedor de armazenamento
personalizado e conectá-lo ao seu aplicativo. Este tópico descreve como criar um provedor de armazenamento
personalizado para a identidade do ASP.NET Core. Ele aborda os conceitos importantes para criar seu próprio
provedor de armazenamento, mas não o passo a passo.
Exibir ou baixar amostra do GitHub.

Introdução
Por padrão, o sistema de identidade do ASP.NET Core armazena informações de usuário em um banco de dados
do SQL Server usando o Entity Framework Core. Para muitos aplicativos, essa abordagem funciona bem. No
entanto, é preferível usar um mecanismo de persistência diferente ou esquema de dados. Por exemplo:
Você usa armazenamento de tabela do Azure ou outro repositório de dados.
As tabelas de banco de dados têm uma estrutura diferente.
Talvez você queira usar uma abordagem de acesso a dados diferentes, como Dapper.
Em cada um desses casos, você pode escrever um provedor personalizado para o mecanismo de armazenamento e
conecte esse provedor de seu aplicativo.
Identidade do ASP.NET Core está incluída nos modelos de projeto no Visual Studio com a opção de "Contas de
usuário individuais".
Ao usar o .NET Core CLI, adicionar -au Individual :

dotnet new mvc -au Individual


dotnet new webapi -au Individual

A arquitetura de identidade do ASP.NET Core


A identidade do ASP.NET Core consiste em classes chamadas gerentes e repositórios. Gerenciadores de classes de
alto nível que um desenvolvedor de aplicativo usa para executar operações, como a criação de um usuário de
identidade. Repositórios classes de nível inferior que especificam como entidades, como usuários e funções, são
persistentes. Execute as lojas de padrão de repositório e estão estreitamente juntamente com o mecanismo de
persistência. Gerenciadores de são separados do repositórios, o que significa que você pode substituir o
mecanismo de persistência sem alterar o código do aplicativo (com exceção da configuração).
O diagrama a seguir mostra como um aplicativo web interage com os gerentes, enquanto armazena interage com
a camada de acesso a dados.
Para criar um provedor de armazenamento personalizado, crie a fonte de dados, a camada de acesso a dados e as
classes de armazenamento que interagem com essa camada de acesso a dados (as caixas verdes e cinza no
diagrama acima). Não é necessário personalizar os gerentes ou o código do aplicativo que interage com eles (as
caixas azul acima).
Ao criar uma nova instância da UserManager ou RoleManager fornecer o tipo da classe de usuário e passar uma
instância da classe de armazenamento como um argumento. Essa abordagem permite que você conecte suas
classes personalizadas ASP.NET Core.
Reconfigurar o aplicativo para usar o novo provedor de armazenamento mostra como instanciar UserManager e
RoleManager com um repositório personalizado.

Tipos de dados de armazenamentos de identidade do ASP.NET Core


A identidade do ASP.NET Core tipos de dados são detalhados nas seções a seguir:
Usuários
Usuários registrados do seu site. O IdentityUser tipo pode ser estendido ou usado como um exemplo para seu
próprio tipo personalizado. Você não precisa herdar de um tipo específico para implementar sua própria solução
de armazenamento personalizado de identidade.
Declarações de usuário
Um conjunto de instruções (ou declarações) sobre o usuário que representam a identidade do usuário. Pode
habilitar a expressão maior do que a identidade do usuário que pode ser obtido por meio de funções.
Logons de usuário
Informações sobre o provedor de autenticação externa (como o Facebook ou uma conta da Microsoft) para usar ao
fazer logon em um usuário. Exemplo
Funções
Grupos de autorização para seu site. Inclui o nome da função Id e a função (como "Admin" ou "Employee").
Exemplo

Camada de acesso a dados


Este tópico pressupõe que você esteja familiarizado com o mecanismo de persistência que você pretende usar e
como criar entidades para esse mecanismo. Este tópico não fornece detalhes sobre como criar os repositórios ou
classes de acesso de dados; Ele fornece algumas sugestões sobre decisões de design ao trabalhar com a identidade
do ASP.NET Core.
Você tem uma grande liberdade durante a criação de camada de acesso a dados para um provedor de
armazenamento personalizado. Você só precisa criar mecanismos de persistência para os recursos que você
pretende usar em seu aplicativo. Por exemplo, se você não estiver usando funções em seu aplicativo, você não
precisa criar armazenamento para funções ou associações de função de usuário. A tecnologia e a infraestrutura
existente podem exigir uma estrutura que é muito diferente da implementação do padrão de identidade do
ASP.NET Core. Na camada de acesso a dados, você deve fornecer a lógica para trabalhar com a estrutura de sua
implementação de armazenamento.
A camada de acesso de dados fornece a lógica para salvar os dados de identidade do ASP.NET Core para uma
fonte de dados. Camada de acesso a dados para o seu provedor de armazenamento personalizado pode incluir as
seguintes classes para armazenar informações de usuário e a função.
Classe de contexto
Encapsula as informações para se conectar ao seu mecanismo de persistência e executar consultas. Várias classes
de dados requerem uma instância dessa classe, normalmente é fornecido por meio de injeção de dependência.
Exemplo.
Armazenamento de usuário
Armazena e recupera informações do usuário (como o hash de nome e a senha do usuário). Exemplo
Armazenamento de função
Armazena e recupera informações de função (como o nome da função). Exemplo
Armazenamento de UserClaims
Armazena e recupera informações de declaração de usuário (como o tipo de declaração e valor). Exemplo
Armazenamento de UserLogins
Armazena e recupera informações de logon do usuário (como um provedor de autenticação externa). Exemplo
Armazenamento de UserRole
Armazena e recupera a quais funções são atribuídas a quais usuários. Exemplo
Dica: implementar apenas as classes que você pretende usar em seu aplicativo.
Em classes de acesso a dados, forneça o código para executar operações de dados para o mecanismo de
persistência. Por exemplo, dentro de um provedor personalizado, você pode ter o código a seguir para criar um
novo usuário o armazenar classe:
public async Task<IdentityResult> CreateAsync(ApplicationUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
if (user == null) throw new ArgumentNullException(nameof(user));

return await _usersTable.CreateAsync(user);


}

A lógica de implementação para criar o usuário está no _usersTable.CreateAsync método, como mostrado abaixo.

Personalizar a classe de usuário


Ao implementar um provedor de armazenamento, criar uma classe de usuário que é equivalente a IdentityUser
classe.
No mínimo, a classe de usuário deve incluir um Id e um UserName propriedade.
O IdentityUser classe define as propriedades que o UserManager solicitado de chamadas ao executar operações.
O tipo de padrão de Id propriedade é uma cadeia de caracteres, mas você pode herdar de
IdentityUser<TKey, TUserClaim, TUserRole, TUserLogin, TUserToken> e especifique um tipo diferente. A estrutura de
espera a implementação de armazenamento para lidar com conversões de tipo de dados.

Personalizar o repositório do usuário


Criar um UserStore classe que fornece os métodos para todas as operações de dados de usuário. Essa classe é
equivalente a UserStore classe. No seu UserStore classe, implemente IUserStore<TUser> e as interfaces opcionais
necessários. Você selecionar quais interfaces opcionais para implementar com base na funcionalidade fornecida no
seu aplicativo.
Interfaces opcionais
/Dotnet/api/microsoft.aspnetcore.identity.iuserrolestore-1 IUserRoleStore
/Dotnet/api/microsoft.aspnetcore.identity.iuserclaimstore-1 IUserClaimStore
/Dotnet/api/microsoft.aspnetcore.identity.iuserpasswordstore-1 IUserPasswordStore
IUserSecurityStampStore
IUserEmailStore
IPhoneNumberStore
IQueryableUserStore
IUserLoginStore
IUserTwoFactorStore
IUserLockoutStore
As interfaces opcionais herdam de IUserStore . Você pode ver um usuário de exemplo parcialmente implementada
armazenar aqui.
Dentro de UserStore classe, que você usar as classes de acesso de dados que você criou para executar operações.
Eles são passados usando a injeção de dependência. Por exemplo, no SQL Server com a implementação do
Dapper, o UserStore classe tem o CreateAsync método que usa uma instância de DapperUsersTable para inserir
um novo registro:
public async Task<IdentityResult> CreateAsync(ApplicationUser user)
{
string sql = "INSERT INTO dbo.CustomUser " +
"VALUES (@id, @Email, @EmailConfirmed, @PasswordHash, @UserName)";

int rows = await _connection.ExecuteAsync(sql, new { user.Id, user.Email, user.EmailConfirmed,


user.PasswordHash, user.UserName });

if(rows > 0)
{
return IdentityResult.Success;
}
return IdentityResult.Failed(new IdentityError { Description = $"Could not insert user {user.Email}." });
}

Interfaces a serem implementadas ao personalizar o repositório do usuário


IUserStore
O IUserStore<TUser> interface é a única interface, você deve implementar no repositório do usuário. Define
métodos para criar, atualizar, excluir e recuperar os usuários.
IUserClaimStore
O IUserClaimStore<TUser> interface define os métodos a implementar para habilitar as declarações de
usuário. Contém métodos para adicionar, remover e recuperando declarações de usuário.
IUserLoginStore
O IUserLoginStore<TUser> define os métodos a implementar para habilitar provedores de autenticação
externa. Contém métodos para adicionar, remover e recuperar os logons de usuário e um método para
recuperar um usuário com base nas informações de logon.
IUserRoleStore
O IUserRoleStore<TUser> interface define os métodos a implementar para mapear um usuário a uma função.
Contém métodos para adicionar, remover e recuperar funções de usuário e um método para verificar se um
usuário é atribuído a uma função.
IUserPasswordStore
O IUserPasswordStore<TUser> interface define os métodos que você pode implementar para manter as
senhas hash. Contém métodos para obter e definir a senha de hash e um método que indica se o usuário tiver
definido uma senha.
IUserSecurityStampStore
O IUserSecurityStampStore<TUser> interface define os métodos a implementar para usar um carimbo de
segurança para que indica se as informações da conta do usuário foi alterado. Esse carimbo é atualizado
quando um usuário altera a senha ou adiciona ou remove logons. Contém métodos para obter e definir o
carimbo de segurança.
IUserTwoFactorStore
O IUserTwoFactorStore<TUser> interface define os métodos a implementar para dar suporte à autenticação de
dois fatores. Contém métodos para obter e definir se a autenticação de dois fatores é ativada para um usuário.
IUserPhoneNumberStore
O IUserPhoneNumberStore<TUser> interface define os métodos a implementar para armazenar números de
telefone do usuário. Contém métodos para obter e definir o número de telefone e se o número de telefone foi
confirmado.
IUserEmailStore
O IUserEmailStore<TUser> interface define os métodos a implementar para armazenar os endereços de email
do usuário. Contém métodos para obter e definir o endereço de email e se o email for confirmado.
IUserLockoutStore
O IUserLockoutStore<TUser> interface define os métodos a implementar para armazenar informações sobre
como bloquear uma conta. Contém métodos para controlar as tentativas de acesso com falha e bloqueios.
IQueryableUserStore
O IQueryableUserStore<TUser> interface define a implementar membros para fornecer um repositório do
usuário que podem ser consultados.
Você pode implementar apenas as interfaces que são necessários em seu aplicativo. Por exemplo:

public class UserStore : IUserStore<IdentityUser>,


IUserClaimStore<IdentityUser>,
IUserLoginStore<IdentityUser>,
IUserRoleStore<IdentityUser>,
IUserPasswordStore<IdentityUser>,
IUserSecurityStampStore<IdentityUser>
{
// interface implementations not shown
}

IdentityUserClaim, IdentityUserLogin e IdentityUserRole


O Microsoft.AspNet.Identity.EntityFramework namespace contém implementações do IdentityUserClaim,
IdentityUserLogin, e IdentityUserRole classes. Se você estiver usando esses recursos, convém criar suas próprias
versões dessas classes e definir as propriedades de seu aplicativo. No entanto, às vezes é mais eficiente para não
carregar essas entidades na memória ao executar operações básicas (como adicionar ou remover a declaração do
usuário). Em vez disso, as classes de armazenamento de back-end podem executar essas operações diretamente na
fonte de dados. Por exemplo, o UserStore.GetClaimsAsync método pode chamar o
userClaimTable.FindByUserId(user.Id) método para executar uma consulta na tabela diretamente e retornar uma
lista de declarações.

Personalizar a classe de função


Ao implementar um provedor de armazenamento de função, você pode criar um tipo de função personalizada. Ele
não precisa implementar uma interface específica, mas ele deve ter um Id e geralmente terá um Name
propriedade.
A seguir está um exemplo de classe de função:

using System;

namespace CustomIdentityProviderSample.CustomProvider
{
public class ApplicationRole
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; }
}
}

Personalizar o repositório de função


Você pode criar um RoleStore classe que fornece os métodos para todas as operações de dados em funções. Essa
classe é equivalente a RoleStore classe. No RoleStore classe, você implementa o IRoleStore<TRole> e,
opcionalmente, o IQueryableRoleStore<TRole> interface.
IRoleStore<TRole>
O IRoleStore interface define os métodos para implementar a classe de armazenamento de função. Contém
métodos para criar, atualizar, excluir e recuperar funções.
RoleStore<TRole>
Para personalizar RoleStore , crie uma classe que implementa o IRoleStore interface.
Reconfigurar o aplicativo para usar o novo provedor de armazenamento
Depois de implementar um provedor de armazenamento, você pode configurar seu aplicativo para usá-lo. Se seu
aplicativo usado o provedor padrão, substitua-o com o provedor personalizado.
1. Remover o Microsoft.AspNetCore.EntityFramework.Identity pacote NuGet.
2. Se o provedor de armazenamento reside em um projeto separado ou um pacote, adicione uma referência a ele.
3. Substitua todas as referências a Microsoft.AspNetCore.EntityFramework.Identity com o uso de uma instrução
para o namespace de seu provedor de armazenamento.
4. No ConfigureServices método, altere o AddIdentity método para usar seus tipos personalizados. Você pode
criar seus próprios métodos de extensão para essa finalidade. Consulte IdentityServiceCollectionExtensions
para obter um exemplo.
5. Se você estiver usando funções, atualize o RoleManager para usar o RoleStore classe.
6. Atualize a cadeia de caracteres de conexão e as credenciais para a configuração do aplicativo.
Exemplo:

public void ConfigureServices(IServiceCollection services)


{
// Add identity types
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddDefaultTokenProviders();

// Identity Services
services.AddTransient<IUserStore<ApplicationUser>, CustomUserStore>();
services.AddTransient<IRoleStore<ApplicationRole>, CustomRoleStore>();
string connectionString = Configuration.GetConnectionString("DefaultConnection");
services.AddTransient<SqlConnection>(e => new SqlConnection(connectionString));
services.AddTransient<DapperUsersTable>();

// additional configuration
}

Referências
Provedores de armazenamento personalizado para a identidade do ASP.NET
A identidade do ASP.NET Core -este repositório inclui links para a comunidade mantida provedores de
armazenamento.
Autenticação de Facebook, Google e de provedor
externo no ASP.NET Core
10/04/2018 • 6 min to read • Edit Online

Por Valeriy Novytskyy e Rick Anderson


Este tutorial demonstra como criar um aplicativo ASP.NET Core 2.x que permite aos usuários fazer logon
usando o OAuth 2.0 com as credenciais de provedores de autenticação externa.
Os provedores Facebook, Twitter, Google e Microsoft são abordados nas seções a seguir. Outros provedores
estão disponíveis em pacotes de terceiros, como AspNet.Security.OAuth.Providers e
AspNet.Security.OpenId.Providers.

Permitir que os usuários entrem com suas credenciais existentes é conveniente para os usuários e transfere
muitas das complexidades de gerenciar o processo de entrada para um terceiro. Para obter exemplos de como
os logons sociais podem impulsionar o tráfego e as conversões de clientes, consulte os estudos de caso do
Facebook e do Twitter.
Observação: os pacotes apresentados aqui eliminam uma grande parte da complexidade do fluxo de
autenticação OAuth, mas o entendimento dos detalhes pode ser necessário durante a solução de problemas.
Muitos recursos estão disponíveis; por exemplo, consulte Introdução ao OAuth 2 ou Noções básicas do OAuth
2. Alguns problemas podem ser resolvidos examinando o código-fonte do ASP.NET Core para os pacotes de
provedores.

Criar um novo projeto ASP.NET Core


No Visual Studio 2017, crie um novo projeto na Página Inicial ou por meio de Arquivo > Novo >
Projeto.
Selecione o modelo Aplicativo Web ASP.NET Core disponível na categoria Visual C# > .NET Core:
Toque em Aplicativo Web e verifique se a opção Autenticação está definida como Contas de Usuário
Individuais:

Observação: este tutorial aplica-se à versão do SDK do ASP.NET Core 2.0 que pode ser selecionada na parte
superior do assistente.

Aplicar migrações
Execute o aplicativo e selecione o link login.
Selecione o link Registrar como um novo usuário.
Insira o email e a senha para a nova conta e, em seguida, selecione Registrar.
Siga as instruções para aplicar as migrações.
Exigir SSL
O OAuth 2.0 exige o uso de SSL para autenticação por meio do protocolo HTTPS.
Observação: os projetos criados com o uso de modelos de projeto Aplicativo Web ou API Web para o
ASP.NET Core 2.x são automaticamente configurados para habilitar o SSL e iniciados com a URL HTTPS se a
opção Contas de Usuário Individuais foi selecionada na caixa de diálogo Alterar Autenticação no
assistente do projeto, conforme mostrado acima.
Exija o SSL em seu site seguindo as etapas do tópico Impor o SSL em um aplicativo ASP.NET Core.

Usar o SecretManager para armazenar os tokens atribuídos por


provedores de logon
Os provedores de logon social atribuem tokens de ID do Aplicativo e Segredo do Aplicativo durante o
processo de registro (a nomenclatura exata varia de acordo com o provedor).
Esses valores são efetivamente o nome de usuário e a senha que o aplicativo usa para acessar sua API e
constituem os “segredos” que podem ser vinculados à configuração do aplicativo com a ajuda do Secret
Manager, em vez de armazená-los em arquivos de configuração diretamente ou embuti-los em código.
Siga as etapas do tópico Armazenamento seguro de segredos do aplicativo durante o desenvolvimento no
ASP.NET Core para que você possa armazenar os tokens atribuídos por cada provedor de logon abaixo.

Configurar os provedores de logon necessários para o aplicativo


Use os seguintes tópicos para configurar seu aplicativo para usar os respectivos provedores:
Instruções do Facebook
Instruções do Twitter
Instruções do Google
Instruções da Microsoft
Outras instruções do provedor

Definir a senha opcionalmente


Ao registrar um provedor de logon externo, você não precisa ter uma senha registrada no aplicativo. Isso o
alivia da tarefa de criar e lembrar de uma senha para o site, mas também o torna dependente do provedor de
logon externo. Se o provedor de logon externo não estiver disponível, você não poderá fazer logon no site.
Para criar uma senha e entrar usando seu email definido durante o processo de entrada com provedores
externos:
Toque no link Olá na parte superior direita para navegar para a exibição Gerenciar.

Toque em Criar
Defina uma senha válida e use-a para entrar com seu email.

Próximas etapas
Este artigo apresentou a autenticação externa e explicou os pré-requisitos necessários para adicionar
logons externos ao aplicativo ASP.NET Core.
Páginas de referência específicas ao provedor para configurar logons para os provedores necessários
para o aplicativo.
Configuração de logon externo do Facebook no
núcleo do ASP.NET
04/05/2018 • 7 min to read • Edit Online

Por Valeriy Novytskyy e Rick Anderson


Este tutorial mostra como permitir que os usuários entrem com a conta do Facebook deles usando um projeto de
amostra do ASP.NET Core 2.0 criado na página anterior. Vamos começar criando um Facebook App ID seguindo
as etapas oficiais.

Criar o aplicativo no Facebook


Navegue até a página aplicativos no site para desenvolvedores do Facebook. Se você ainda não tiver uma
conta do Facebook, use o link Sign up foinscrever-se para o Facebook na página de logon para criar
uma.
Clique no botão adicionar um novo aplicativo no canto superior direito para criar uma nova ID de
aplicativo.

Preencha o formulário e clique no botão criar ID do aplicativo.

Na página selecionar um produto , clique em Set Up no painel Facebook Login.


O assistente Quickstart iniciará com escolher uma plataforma como a primeira página. Ignore o
assistente clicando no link configurações no menu à esquerda:

Você verá o configurações do cliente OAuth página:


Insira o URI de desenvolvimento com /signin-facebook acrescentados no válido URIs de
redirecionamento OAuth campo (por exemplo: https://localhost:44320/signin-facebook ). A autenticação
do Facebook configurada mais tarde neste tutorial automaticamente manipulará as solicitações no /signin-
facebook rota para implementar o fluxo do OAuth.
Clique em salvar alterações.
Clique o painel link no painel de navegação esquerdo.
Nessa página, anote o App ID e App Secret . Você adicionará ambos em seu aplicativo ASP.NET Core na
próxima seção:

Ao implantar o site, você precisará voltar para a página de configurações Logon do Facebook e registrar
um novo URI público.
Armazenar a ID do aplicativo Facebook e o segredo do aplicativo
Vincular as configurações confidenciais como Facebook App ID e App Secret para sua configuração de aplicativo
usando o Manager segredo. Para os fins deste tutorial, nomeie os tokens Authentication:Facebook:AppId e
Authentication:Facebook:AppSecret .

Execute os seguintes comandos para armazenar com segurança App ID e App Secret usando o Gerenciador de
segredo:

dotnet user-secrets set Authentication:Facebook:AppId <app-id>


dotnet user-secrets set Authentication:Facebook:AppSecret <app-secret>

Configurar a autenticação do Facebook


ASP.NET Core 2.x
ASP.NET Core 1.x
Adicione o serviço do Facebook no método ConfigureServices do arquivo Startup.cs :

services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

services.AddAuthentication().AddFacebook(facebookOptions =>
{
facebookOptions.AppId = Configuration["Authentication:Facebook:AppId"];
facebookOptions.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
});

Observação: a chamada para AddIdentity define as configurações de esquema padrão. O


AddAuthentication(string defaultScheme) conjuntos de sobrecarga de DefaultScheme propriedade; e, o
AddAuthentication(Action<AuthenticationOptions> configureOptions) sobrecarga define apenas as propriedades
que você definir explicitamente. Qualquer um dessas sobrecargas só deve ser chamado uma vez ao adicionar
vários provedores de autenticação. Chamadas subsequentes para que ele tem o potencial de substituição qualquer
configurado anteriormente AuthenticationOptions propriedades.
Consulte as referências de API do FacebookOptions para obter mais informações sobre as opções de
configuração compatíveis com a autenticação do Facebook. As opções de configuração podem ser usadas para:
Solicitar informações diferentes sobre o usuário.
Adicionar argumentos de cadeia de caracteres de consulta para personalizar a experiência de logon.

Entrar com o Facebook


Execute o aplicativo e clique em login. Você verá uma opção para entrar com o Facebook.
Quando você clica na Facebook, você será redirecionado para o Facebook para autenticação:

Endereço de email e o perfil público de solicitações de autenticação do Facebook por padrão:


Depois que você insira suas credenciais de Facebook, que você será redirecionado para o site onde você pode
definir seu email.
Agora você está conectado usando suas credenciais do Facebook:

Solução de problemas
Apenas ASP.NET Core 2.x: se a identidade não for configurada chamando services.AddIdentity no
ConfigureServices , a autenticação resultará em ArgumentException: a opção 'SignInScheme' deve ser
fornecida. O modelo de projeto usado neste tutorial garante que isso é feito.
Se o banco de dados do site não tiver sido criado, aplicando a migração inicial, você obtém uma operação de
banco de dados falhou ao processar a solicitação erro. Toque em aplicar migrações para criar o banco de
dados e a atualização para continuar após o erro.

Próximas etapas
Este artigo mostrou como você pode autenticar com o Facebook. Você pode seguir uma abordagem
semelhante para autenticar com outros provedores listados no página anterior.
Depois de publicar seu site da web para o aplicativo web do Azure, você deve redefinir o AppSecret no
portal do desenvolvedor do Facebook.
Definir o Authentication:Facebook:AppId e Authentication:Facebook:AppSecret como configurações de
aplicativo no portal do Azure. O sistema de configuração é configurado para ler as chaves de variáveis de
ambiente.
Configuração de logon externo com o ASP.NET Core
do Twitter
04/05/2018 • 6 min to read • Edit Online

Por Valeriy Novytskyy e Rick Anderson


Este tutorial mostra como habilitar usuários para entrar com sua conta do Twitter usando um projeto do ASP.NET
Core 2.0 de exemplo criado na página anterior.

Criar o aplicativo no Twitter


Navegue até https://apps.twitter.com/ e entrar. Se você ainda não tiver uma conta do Twitter, use o inscrever-
se agora link para criar uma. Depois de entrar, o gerenciamento de aplicativos página é mostrada:

Toque em criar novo aplicativo e preencha o aplicativo nome, descrição pública e site URI (que pode ser
temporária até que você Registre o nome de domínio):
Insira o URI de desenvolvimento com /signin-twitter acrescentados no válido URIs de
redirecionamento OAuth campo (por exemplo: https://localhost:44320/signin-twitter ). O esquema de
autenticação do Twitter configurado posteriormente neste tutorial automaticamente manipulará as
solicitações no /signin-twitter rota para implementar o fluxo do OAuth.
Preencha o restante do formulário e toque em criar seu aplicativo Twitter. Novos detalhes do aplicativo
são exibidos:
Ao implantar o site será necessário rever o gerenciamento de aplicativos página e registrar um novo URI
público.

Armazenar Twitter ConsumerKey e ConsumerSecret


Vincular as configurações confidenciais, como o Twitter Consumer Key e Consumer Secret para sua configuração
de aplicativo usando o Manager segredo. Para os fins deste tutorial, nomeie os tokens
Authentication:Twitter:ConsumerKey e Authentication:Twitter:ConsumerSecret .

Esses tokens podem ser encontradas no chaves e Tokens de acesso guia depois de criar seu novo aplicativo
Twitter:
Configurar a autenticação do Twitter
O modelo de projeto usado neste tutorial garante que Microsoft.AspNetCore.Authentication.Twitter pacote já está
instalado.
Para instalar este pacote com o Visual Studio 2017, clique com botão direito no projeto e selecione
gerenciar pacotes NuGet.
Para instalar o .NET Core CLI, execute o seguinte comando no diretório do projeto:
dotnet add package Microsoft.AspNetCore.Authentication.Twitter

ASP.NET Core 2.x


ASP.NET Core 1.x
Adicione o serviço do Twitter no ConfigureServices método Startup.cs arquivo:

services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

services.AddAuthentication().AddTwitter(twitterOptions =>
{
twitterOptions.ConsumerKey = Configuration["Authentication:Twitter:ConsumerKey"];
twitterOptions.ConsumerSecret = Configuration["Authentication:Twitter:ConsumerSecret"];
});

Observação: a chamada para AddIdentity define as configurações de esquema padrão. O


AddAuthentication(string defaultScheme) conjuntos de sobrecarga de DefaultScheme propriedade; e, o
AddAuthentication(Action<AuthenticationOptions> configureOptions) sobrecarga define apenas as propriedades
que você definir explicitamente. Qualquer um dessas sobrecargas só deve ser chamado uma vez ao adicionar
vários provedores de autenticação. Chamadas subsequentes para que ele tem o potencial de substituição qualquer
configurado anteriormente AuthenticationOptions propriedades.
Consulte o TwitterOptions referência de API para obter mais informações sobre opções de configuração com
suporte pela autenticação do Twitter. Isso pode ser usado para solicitar informações diferentes sobre o usuário.

Entrar com o Twitter


Execute o aplicativo e clique em login. Será exibida uma opção para entrar com o Twitter:

Clicando em Twitter redireciona para o Twitter para autenticação:


Depois de inserir suas credenciais do Twitter, você será redirecionado para o site onde você pode definir seu email.
Agora você está conectado usando suas credenciais do Twitter:

Solução de problemas
Apenas ASP.NET Core 2.x: se a identidade não for configurada chamando services.AddIdentity no
ConfigureServices , a autenticação resultará em ArgumentException: a opção 'SignInScheme' deve ser
fornecida. O modelo de projeto usado neste tutorial garante que isso é feito.
Se o banco de dados do site não tiver sido criado, aplicando a migração inicial, você obterá uma operação de
banco de dados falhou ao processar a solicitação erro. Toque em aplicar migrações para criar o banco de
dados e a atualização para continuar após o erro.
Próximas etapas
Este artigo mostrou como você pode autenticar com o Twitter. Você pode seguir uma abordagem
semelhante para autenticar com outros provedores listados no página anterior.
Depois de publicar seu site da web para o aplicativo web do Azure, você deve redefinir o ConsumerSecret
no portal do desenvolvedor do Twitter.
Definir o Authentication:Twitter:ConsumerKey e Authentication:Twitter:ConsumerSecret como
configurações de aplicativo no portal do Azure. O sistema de configuração é configurado para ler as chaves
de variáveis de ambiente.
Configuração de logon externo do Google no núcleo
do ASP.NET
04/05/2018 • 9 min to read • Edit Online

Por Valeriy Novytskyy e Rick Anderson


Este tutorial mostra como habilitar os usuários entrar com sua conta Google + usando um projeto do ASP.NET
Core 2.0 de exemplo criado no página anterior. Vamos começar seguindo o oficiais etapas para criar um novo
aplicativo no Console de API do Google.

Criar o aplicativo no Console de API do Google


Navegue até https://console.developers.google.com/projectselector/apis/library e entrar. Se você ainda não
tiver uma conta do Google, use mais opções > criar conta link para criar um:

Você será redirecionado para biblioteca API Manager página:


Toque em criar e insira seu nome do projeto:

Após aceitar a caixa de diálogo, você será redirecionado para a página de biblioteca, permitindo que você
escolha os recursos para seu novo aplicativo. Localizar API do Google + na lista e clique em seu link para
adicionar o recurso de API:
A página para a API recém-adicionado é exibida. Toque em habilitar para adicionar o Google + sinal de
recurso ao seu aplicativo:

Depois de habilitar a API, toque em criar credenciais para configurar os segredos:

Escolha:
API do Google +
Servidor Web (por exemplo, node.js, Tomcat), e
Dados de usuário:
Toque em as credenciais que eu preciso? que leva você para a segunda etapa de configuração de aplicativo,
criar uma ID de cliente OAuth 2.0:
Porque estamos criando um projeto do Google + com apenas um recurso (entrada), podemos inserir a
mesma nome para a ID do cliente OAuth 2.0 é usado para o projeto.
Insira o URI de desenvolvimento com /signin-google acrescentados no autorizados redirecionar URIs
campo (por exemplo: https://localhost:44320/signin-google ). A autenticação do Google configurada mais
tarde neste tutorial automaticamente manipulará as solicitações no /signin-google rota para implementar o
fluxo do OAuth.
Pressione TAB para adicionar o autorizados redirecionar URIs entrada.
Toque em criar ID do cliente, que leva você para a terceira etapa, configurar a tela de consentimento
de OAuth 2.0:
Insira seu voltado ao público endereço de Email e nome do produto mostrado para seu aplicativo
quando Google + solicita ao usuário para entrar. Opções adicionais estão disponíveis em mais opções de
personalização.
Toque em continuar para prosseguir para a última etapa, baixar credenciais:
Toque em baixar para salvar um arquivo JSON com segredos do aplicativo, e feito para concluir a criação
do novo aplicativo.
Ao implantar o site será necessário rever o Google Console e registre uma nova url pública.

Loja Google ClientID e ClientSecret


Vincular as configurações confidenciais com o Google Client ID e Client Secret para sua configuração de
aplicativo usando o Manager segredo. Para os fins deste tutorial, nomeie os tokens
Authentication:Google:ClientId e Authentication:Google:ClientSecret .

Os valores para esses tokens podem ser encontrados no arquivo JSON baixado na etapa anterior em
web.client_id e web.client_secret .

Configurar a autenticação do Google


ASP.NET Core 2.x
ASP.NET Core 1.x
Adicione o serviço do Google no ConfigureServices método Startup.cs arquivo:

services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

services.AddAuthentication().AddGoogle(googleOptions =>
{
googleOptions.ClientId = Configuration["Authentication:Google:ClientId"];
googleOptions.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
});

Observação: a chamada para AddIdentity define as configurações de esquema padrão. O


AddAuthentication(string defaultScheme) conjuntos de sobrecarga de DefaultScheme propriedade; e, o
AddAuthentication(Action<AuthenticationOptions> configureOptions) sobrecarga define apenas as propriedades
que você definir explicitamente. Qualquer um dessas sobrecargas só deve ser chamado uma vez ao adicionar
vários provedores de autenticação. Chamadas subsequentes para que ele tem o potencial de substituição qualquer
configurado anteriormente AuthenticationOptions propriedades.
Consulte o GoogleOptions referência de API para obter mais informações sobre opções de configuração com
suporte pela autenticação do Google. Isso pode ser usado para solicitar informações diferentes sobre o usuário.

Entrar com o Google


Execute o aplicativo e clique em login. Uma opção para entrar com o Google aparece:

Quando você clica no Google, você é redirecionado para o Google para autenticação:
Depois de inserir suas credenciais do Google, em seguida, você será redirecionado para o site onde você pode
definir seu email.
Agora você está conectado usando suas credenciais do Google:

Solução de problemas
Se você receber um 403 (Forbidden) página de erro do seu próprio aplicativo quando em execução no modo
de desenvolvimento (ou interrupção no depurador com o mesmo erro), certifique-se de que API do Google +
foi habilitada no API do Gerenciador de biblioteca seguindo as etapas listadas anteriores nesta página. Se a
entrada não funciona e não estiver obtendo erros, alterne para o modo de desenvolvimento para facilitar o
problema depurar.
Apenas ASP.NET Core 2.x: se a identidade não for configurada chamando services.AddIdentity no
ConfigureServices , a autenticação resultará em ArgumentException: a opção 'SignInScheme' deve ser
fornecida. O modelo de projeto usado neste tutorial garante que isso é feito.
Se o banco de dados do site não tiver sido criado, aplicando a migração inicial, você obterá uma operação de
banco de dados falhou ao processar a solicitação erro. Toque em aplicar migrações para criar o banco de
dados e a atualização para continuar após o erro.

Próximas etapas
Este artigo mostrou como você pode autenticar com o Google. Você pode seguir uma abordagem
semelhante para autenticar com outros provedores listados no página anterior.
Depois de publicar seu site da web para o aplicativo web do Azure, você deve redefinir o ClientSecret no
Console de API do Google.
Definir o Authentication:Google:ClientId e Authentication:Google:ClientSecret como configurações de
aplicativo no portal do Azure. O sistema de configuração é configurado para ler as chaves de variáveis de
ambiente.
Configuração de logon externo Account da Microsoft
com o ASP.NET Core
04/05/2018 • 8 min to read • Edit Online

Por Valeriy Novytskyy e Rick Anderson


Este tutorial mostra como habilitar os usuários entrar com sua conta da Microsoft usando um projeto do ASP.NET
Core 2.0 de exemplo criado no página anterior.

Criar o aplicativo no Portal do desenvolvedor da Microsoft


Navegue até https://apps.dev.microsoft.com e criar ou entrar em uma conta da Microsoft:

Se você ainda não tiver uma conta da Microsoft, toque em criar um! Depois de entrar, você será redirecionado
para meus aplicativos página:

Toque em adicionar um aplicativo no canto superior direito de canto e insira seu nome do aplicativo e
Contact Email:

Para os fins deste tutorial, desmarque o instalação interativa caixa de seleção.


Toque em criar para continuar a registro página. Forneça um nome e observe o valor da Id do
aplicativo, que você usar como ClientId posteriormente no tutorial:
Toque em Adicionar plataforma no plataformas seção e selecione o Web plataforma:
No novo Web plataforma seção, digite a URL de desenvolvimento com /signin-microsoft acrescentados no
URLs de redirecionamento campo (por exemplo: https://localhost:44320/signin-microsoft ). O esquema de
autenticação da Microsoft configurado mais tarde neste tutorial automaticamente manipulará as solicitações
no /signin-microsoft rota para implementar o fluxo de OAuth:

Toque em Adicionar URL para garantir que a URL foi adicionada.


Preencha quaisquer outras configurações de aplicativo, se necessário e toque em salvar na parte inferior
da página para salvar as alterações de configuração do aplicativo.
Ao implantar o site será necessário rever o registro página e defina uma nova URL pública.

Armazenar a Id do aplicativo da Microsoft e a senha


Observe o Application Id exibido no registro página.
Toque em gerar nova senha no segredos do aplicativo seção. Isso exibe uma caixa em que você pode
copiar a senha de aplicativo:

Vincular as configurações confidenciais como Microsoft Application ID e Password para sua configuração de
aplicativo usando o Manager segredo. Para os fins deste tutorial, nomeie os tokens
Authentication:Microsoft:ApplicationId e Authentication:Microsoft:Password .
Configurar a autenticação de conta da Microsoft
O modelo de projeto usado neste tutorial garante que Microsoft.AspNetCore.Authentication.MicrosoftAccount
pacote já está instalado.
Para instalar este pacote com o Visual Studio 2017, clique com botão direito no projeto e selecione
gerenciar pacotes NuGet.
Para instalar o .NET Core CLI, execute o seguinte comando no diretório do projeto:
dotnet add package Microsoft.AspNetCore.Authentication.MicrosoftAccount

ASP.NET Core 2.x


ASP.NET Core 1.x
Adicione o serviço Microsoft Account no ConfigureServices método Startup.cs arquivo:

services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

services.AddAuthentication().AddMicrosoftAccount(microsoftOptions =>
{
microsoftOptions.ClientId = Configuration["Authentication:Microsoft:ApplicationId"];
microsoftOptions.ClientSecret = Configuration["Authentication:Microsoft:Password"];
});

Observação: a chamada para AddIdentity define as configurações de esquema padrão. O


AddAuthentication(string defaultScheme) conjuntos de sobrecarga de DefaultScheme propriedade; e, o
AddAuthentication(Action<AuthenticationOptions> configureOptions) sobrecarga define apenas as propriedades
que você definir explicitamente. Qualquer um dessas sobrecargas só deve ser chamado uma vez ao adicionar
vários provedores de autenticação. Chamadas subsequentes para que ele tem o potencial de substituição qualquer
configurado anteriormente AuthenticationOptions propriedades.
Embora a terminologia usada no Portal do desenvolvedor do Microsoft nomes esses tokens ApplicationId e
Password , eles são expostos como ClientId e ClientSecret para a API de configuração.

Consulte o MicrosoftAccountOptions referência de API para obter mais informações sobre opções de
configuração com suporte pela autenticação Account da Microsoft. Isso pode ser usado para solicitar informações
diferentes sobre o usuário.

Entrar com a conta da Microsoft


Execute o aplicativo e clique em login. Uma opção para entrar com Microsoft será exibida:
Quando você clica na Microsoft, você é redirecionado para a Microsoft para autenticação. Depois de entrar com
sua Account da Microsoft (se ainda não estiver conectado), você será solicitado para permitir que o aplicativo
acessar suas informações:

Toque em Sim e você será redirecionado para o site da web onde você pode definir seu email.
Agora você está conectado usando suas credenciais da Microsoft:
Solução de problemas
Se o provedor do Microsoft Account redireciona para uma página de erro de entrada, observe os erro título
e descrição de cadeia de caracteres parâmetros de consulta diretamente após o # (hashtag) no Uri.
Embora a mensagem de erro parece ser um problema com a autenticação do Microsoft, a causa mais
comum é seu aplicativo Uri não corresponde a nenhuma do URIs de redirecionamento especificado para
o Web plataforma .
Apenas ASP.NET Core 2.x: se a identidade não for configurada chamando services.AddIdentity no
ConfigureServices , a autenticação resultará em ArgumentException: a opção 'SignInScheme' deve ser
fornecida. O modelo de projeto usado neste tutorial garante que isso é feito.
Se o banco de dados do site não tiver sido criado, aplicando a migração inicial, você obterá uma operação
de banco de dados falhou ao processar a solicitação erro. Toque em aplicar migrações para criar o banco
de dados e a atualização para continuar após o erro.

Próximas etapas
Este artigo mostrou como você pode autenticar com a Microsoft. Você pode seguir uma abordagem
semelhante para autenticar com outros provedores listados no página anterior.
Depois de publicar seu site da web para o aplicativo web do Azure, você deve criar um novo Password no
Portal do desenvolvedor do Microsoft.
Definir o Authentication:Microsoft:ApplicationId e Authentication:Microsoft:Password como
configurações de aplicativo no portal do Azure. O sistema de configuração é configurado para ler as chaves
de variáveis de ambiente.
Pesquisa rápida de outros provedores de
autenticação
10/04/2018 • 1 min to read • Edit Online

Por Rick Anderson, Pranav Rastogi, e Valeriy Novytskyy


Aqui estão configurados instruções para alguns outros provedores OAuth comuns. Pacotes do NuGet de terceiros,
como aqueles mantidos pelo aspnet Contribuidor pode ser usada para complementar os provedores de
autenticação implementados pela equipe do ASP.NET Core.
Configurar LinkedIn entrar: https://www.linkedin.com/developer/apps . Consulte etapas oficiais.
Configurar Instagram entrar: https://www.instagram.com/developer/register/ . Consulte etapas oficiais.
Configurar Reddit entrar: https://www.reddit.com/login?
dest=https%3A%2F%2Fwww.reddit.com%2Fprefs%2Fapps . Consulte etapas oficiais.
Configurar Github entrar: https://github.com/login?
return_to=https%3A%2F%2Fgithub.com%2Fsettings%2Fapplications%2Fnew . Consulte etapas oficiais.
Configurar Yahoo entrar: https://login.yahoo.com/config/login?
src=devnet&.done=http%3A%2F%2Fdeveloper.yahoo.com%2Fapps%2Fcreate%2F . Consulte etapas
oficiais.
Configurar Tumblr entrar: https://www.tumblr.com/oauth/apps . Consulte etapas oficiais.
Configurar Pinterest entrar: https://www.pinterest.com/login/?next=http%3A%2F%2Fdevsite%2Fapps%2F
. Consulte etapas oficiais.
Configurar Pocket entrar: https://getpocket.com/developer/apps/new . Consulte etapas oficiais.
Configurar Flickr entrar: https://www.flickr.com/services/apps/create . Consulte etapas oficiais.
Configurar Dribble entrar: https://dribbble.com/signup . Consulte etapas oficiais.
Configurar Vimeo entrar: https://vimeo.com/join . Consulte etapas oficiais.
Configurar SoundCloud entrar: https://soundcloud.com/you/apps/new . Consulte etapas oficiais.
Configurar VK entrar: https://vk.com/apps?act=manage . Consulte etapas oficiais.
Autenticar usuários com o WS-Federation no núcleo
do ASP.NET
10/04/2018 • 6 min to read • Edit Online

Este tutorial demonstra como habilitar usuários entrar com um provedor de autenticação do WS -Federation como
o Active Directory Federation Services (ADFS ) ou Active Directory do Azure (AAD ). Ele usa o aplicativo de
exemplo do ASP.NET Core 2.0 descrito em Facebook, Google e a autenticação do provedor externo.
Para aplicativos do ASP.NET Core 2.0, o suporte do WS -Federation é fornecido por
Microsoft.AspNetCore.Authentication.WsFederation. Esse componente é movido de
Microsoft.Owin.Security.WsFederation e compartilha muitas das mecânica do componente. No entanto, os
componentes são diferentes de duas maneiras importantes.
Por padrão, o middleware novo:
Não permitir logons não solicitados. Esse recurso do protocolo WS -Federation é vulnerável a ataques XSRF.
No entanto, ela pode ser habilitada com o AllowUnsolicitedLogins opção.
Não verifica cada postagem de formulário para mensagens de entrada. Somente as solicitações para o
CallbackPath são verificados quanto à entrada-ins CallbackPath padrão é /signin-wsfed , mas pode ser
alterado. Esse caminho pode ser compartilhado com outros provedores de autenticação, permitindo que o
SkipUnrecognizedRequests opção.

Registrar o aplicativo com o Active Directory


Serviços de Federação do Active Directory
Abra o servidor terceira parte confiável Assistente para adicionar confiança no console de
gerenciamento do AD FS:
Escolha esta opção para inserir os dados manualmente:

Insira um nome para exibição para a terceira parte confiável. O nome não é importante para o aplicativo
ASP.NET Core.
Microsoft.AspNetCore.Authentication.WsFederation não tem suporte para criptografia de token, portanto,
não configurar um certificado de criptografia do token:
Habilite o suporte para o protocolo WS -Federation Passive, usando a URL do aplicativo. Verifique se que a
porta está correta para o aplicativo:
OBSERVAÇÃO
Isso deve ser uma URL HTTPS. O IIS Express pode fornecer um certificado autoassinado ao hospedar o aplicativo durante o
desenvolvimento. Kestrel requer configuração manual do certificado. Consulte o documentação Kestrel para obter mais
detalhes.

Clique em próximo através do restante do assistente e fechar no final.


A identidade do ASP.NET Core requer um ID de nome de declaração. Adicione um do editar regras de
declaração caixa de diálogo:
No Adicionar Assistente de regra de declaração de transformação, deixe o padrão enviar atributos
LDAP como declarações modelo selecionado e clique em próximo. Adicionar um mapeamento de regra a
nome de conta SAM atributo LDAP para o ID de nome declaração de saída:
Clique em concluir > Okey no editar regras de declaração janela.
Azure Active Directory
Navegue até a folha de registros de aplicativo do locatário do AAD. Clique em novo registro de aplicativo:

Insira um nome para o registro do aplicativo. Isso não é importante para o aplicativo ASP.NET Core.
Insira a URL do aplicativo escuta em que o URL de logon:
Clique em pontos de extremidade e observe o documento de metadados de Federação URL. Este é o
middleware de WS -Federation MetadataAddress :

Navegue até o novo registro de aplicativo. Clique em configurações > propriedades e anote o URI da ID do
aplicativo. Este é o middleware de WS -Federation Wtrealm :
Adicionar o WS-Federation como um provedor de logon externo para
a identidade do ASP.NET Core
Adicione uma dependência no Microsoft.AspNetCore.Authentication.WsFederation ao projeto.
Adicionar o WS -Federation para o Configure método Startup.cs:

services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

services.AddAuthentication()
.AddWsFederation(options =>
{
// MetadataAddress represents the Active Directory instance used to authenticate users.
options.MetadataAddress = "https://<ADFS FQDN or AAD tenant>/FederationMetadata/2007-
06/FederationMetadata.xml";

// Wtrealm is the app's identifier in the Active Directory instance.


// For ADFS, use the relying party's identifier, its WS-Federation Passive protocol URL:
options.Wtrealm = "https://localhost:44307/";

// For AAD, use the App ID URI from the app registration's Properties blade:
options.Wtrealm = "https://wsfedsample.onmicrosoft.com/bf0e7e6d-056e-4e37-b9a6-2c36797b9f01";
});

services.AddMvc()
// ...

Observação: a chamada para AddIdentitydefine as configurações de esquema padrão. O


AddAuthentication(string defaultScheme) conjuntos de sobrecarga de DefaultScheme propriedade; e, o
AddAuthentication(Action<AuthenticationOptions> configureOptions) sobrecarga define apenas as propriedades que
você definir explicitamente. Qualquer um dessas sobrecargas só deve ser chamado uma vez ao adicionar vários
provedores de autenticação. Chamadas subsequentes para que ele tem o potencial de substituição qualquer
configurado anteriormente AuthenticationOptions propriedades.
Faça logon com o WS -Federation
Navegue até o aplicativo e clique no login link no cabeçalho de navegação. Há uma opção para fazer logon com
WsFederation:
Com o ADFS como o provedor, o botão redireciona para uma página de entrada do AD FS:

Com o Azure Active Directory como o provedor, o botão redireciona para uma página de entrada do AAD:
Um bem-sucedida entrar para um novo usuário redireciona para a página de registro de usuário do aplicativo:

Use o WS-Federation sem identidade do ASP.NET Core


O middleware de WS -Federation pode ser usado sem identidade. Por exemplo:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddWsFederation(options =>
{
options.Wtrealm = Configuration["wsfed:realm"];
options.MetadataAddress = Configuration["wsfed:metadata"];
})
.AddCookie();
}

public void Configure(IApplicationBuilder app)


{
app.UseAuthentication();
// …
}
Confirmação de conta e de recuperação de senha
no ASP.NET Core
10/04/2018 • 18 min to read • Edit Online

Por Rick Anderson e Joe Audette


Este tutorial mostra como criar um aplicativo do ASP.NET Core com redefinição de senha e de confirmação de
email. Este tutorial é não um tópico de início. Você deve estar familiarizado com:
ASP.NET Core
Autenticação
Confirmação de conta e recuperação de senha
Entity Framework Core
Consulte este arquivo PDF para as versões do ASP.NET Core MVC 1.1 e 2. x.

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

Criar um novo projeto do ASP.NET Core com o .NET Core CLI


ASP.NET Core 2.x
ASP.NET Core 1.x

dotnet new razor --auth Individual -o WebPWrecover


cd WebPWrecover

--auth IndividualEspecifica o modelo de projeto de contas de usuário individuais.


No Windows, adicione o -uld opção. Especifica que o LocalDB deve ser usado em vez do SQLite.
Execute new mvc --help para obter ajuda sobre este comando.
Como alternativa, você pode criar um novo projeto ASP.NET Core com o Visual Studio:
No Visual Studio, crie um novo aplicativo Web projeto.
Selecione Core ASP.NET 2.0. .NET core está selecionado na imagem a seguir, mas você pode selecionar do
.NET Framework.
Selecione alterar autenticação e definido como contas de usuário individuais.
Mantenha o padrão no aplicativo de contas de usuário do repositório.
Testar o novo registro de usuário
Executar o aplicativo, selecione o registrar vincular e registrar um usuário. Siga as instruções para executar
migrações de Entity Framework Core. Neste ponto, a validação somente do email é com o [EmailAddress]
atributo. Depois de enviar o registro, você está conectado no aplicativo. Posteriormente no tutorial, o código foi
atualizado para que novos usuários não podem fazer logon até que o email foi validado.

Exibir o banco de dados de identidade


Consulte trabalhar com SQLite em um projeto MVC do ASP.NET Core para obter instruções sobre como exibir o
banco de dados SQLite.
Para o Visual Studio:
Do exibição menu, selecione Pesquisador de objetos do SQL Server (SSOX).
Navegue até (localdb) MSSQLLocalDB (SQL Server 13). Clique duas vezes em dbo. AspNetUsers >
exibir dados:
Observe a tabela EmailConfirmed campo é False .
Você talvez queira usar o email novamente na próxima etapa, quando o aplicativo envia um email de
confirmação. Clique na linha e selecione excluir. Excluir o alias de email torna mais fácil nas etapas a seguir.

Exigir HTTPS
Consulte exigir HTTPS.

Solicitar confirmação de email


É uma prática recomendada para confirmar o email de um novo registro de usuário. Ajuda de confirmação para
verificar se eles não estiver representando alguém de email (ou seja, eles ainda não registrados com outra pessoa
email). Suponha que você tivesse um fórum de discussão, e quiser impedir "yli@example.com"do registro
como"nolivetto@contoso.com." Sem confirmação por email, "nolivetto@contoso.com" pode receber email
indesejado de seu aplicativo. Suponha que o usuário registrado acidentalmente como "ylo@example.com" e
ainda não tenha percebido a digitação incorreta da "yli". Elas não serão capazes de usar a recuperação de senha
porque o aplicativo não tiver seu email correto. Email de confirmação oferece apenas proteção limitada de robôs.
Email de confirmação não fornece proteção contra usuários mal-intencionados com várias contas de email.
Geralmente você deseja impedir que novos usuários incluam dados em seu site até que eles tenham um email
confirmado.
Atualização ConfigureServices para exigir um email confirmado:
public void ConfigureServices(IServiceCollection services)
{
// Requires using Microsoft.AspNetCore.Mvc;
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new RequireHttpsAttribute());
});

services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddIdentity<ApplicationUser, IdentityRole>(config =>


{
config.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/Account/Manage");
options.Conventions.AuthorizePage("/Account/Logout");
});

config.SignIn.RequireConfirmedEmail = true; impede que usuários registrados fazer logon até que o email foi
confirmado.
Configurar o provedor de email
Neste tutorial, SendGrid é usada para enviar email. Você precisa de uma conta do SendGrid e a chave para
enviar email. Você pode usar outros provedores de email. ASP.NET Core 2. x inclui System.Net.Mail , que permite
enviar email de seu aplicativo. É recomendável que usar o SendGrid ou outro serviço de email para enviar email.
O SMTP é difícil proteger e configurado corretamente.
O padrão de opções é usado para acessar as configurações de conta e a chave de usuário. Para obter mais
informações, consulte configuração.
Crie uma classe para obter a chave de email seguro. Para este exemplo, o AuthMessageSenderOptions classe é
criada no Services/AuthMessageSenderOptions.cs arquivo:

public class AuthMessageSenderOptions


{
public string SendGridUser { get; set; }
public string SendGridKey { get; set; }
}

Definir o SendGridUser e SendGridKey com o ferramenta Gerenciador de segredo. Por exemplo:

C:\WebAppl\src\WebApp1>dotnet user-secrets set SendGridUser RickAndMSFT


info: Successfully saved SendGridUser = RickAndMSFT to the secret store.

No Windows, o segredo Manager armazena pares de chaves/valor em uma secrets.json arquivo o


%APPDATA%/Microsoft/UserSecrets/<WebAppName-userSecretsId> directory.

O conteúdo do secrets.json arquivo não são criptografados. O secrets.json arquivo é mostrado a seguir (o
SendGridKey valor foi removido.)
{
"SendGridUser": "RickAndMSFT",
"SendGridKey": "<key removed>"
}

Configurar a inicialização para usar AuthMessageSenderOptions


Adicionar AuthMessageSenderOptions ao contêiner de serviço no final o ConfigureServices método o Startup.cs
arquivo:
ASP.NET Core 2.x
ASP.NET Core 1.x

public void ConfigureServices(IServiceCollection services)


{
// Requires using Microsoft.AspNetCore.Mvc;
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new RequireHttpsAttribute());
});

services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddIdentity<ApplicationUser, IdentityRole>(config =>


{
config.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/Account/Manage");
options.Conventions.AuthorizePage("/Account/Logout");
});

services.AddSingleton<IEmailSender, EmailSender>();

services.Configure<AuthMessageSenderOptions>(Configuration);
}

Configurar a classe AuthMessageSender


Este tutorial mostra como adicionar notificações de email por meio de SendGrid, mas você pode enviar emails
usando SMTP e outros mecanismos.
Instalar o SendGrid pacote do NuGet:
Na linha de comando:
dotnet add package SendGrid

No Console do Gerenciador de pacotes, digite o seguinte comando:


Install-Package SendGrid

Consulte comece com SendGrid gratuitamente para registrar-se para uma conta gratuita do SendGrid.
Configurar o SendGrid
ASP.NET Core 2.x
ASP.NET Core 1.x
Para configurar o SendGrid, adicione o código semelhante ao seguinte no Services/EmailSender.cs:

using Microsoft.Extensions.Options;
using SendGrid;
using SendGrid.Helpers.Mail;
using System.Threading.Tasks;

namespace WebPWrecover.Services
{
public class EmailSender : IEmailSender
{
public EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor)
{
Options = optionsAccessor.Value;
}

public AuthMessageSenderOptions Options { get; } //set only via Secret Manager

public Task SendEmailAsync(string email, string subject, string message)


{
return Execute(Options.SendGridKey, subject, message, email);
}

public Task Execute(string apiKey, string subject, string message, string email)
{
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage()
{
From = new EmailAddress("Joe@contoso.com", "Joe Smith"),
Subject = subject,
PlainTextContent = message,
HtmlContent = message
};
msg.AddTo(new EmailAddress(email));
return client.SendEmailAsync(msg);
}
}
}

Habilitar a recuperação de confirmação e a senha da conta


O modelo tem o código de recuperação de confirmação e a senha da conta. Localizar o OnPostAsync método
Pages/Account/Register.cshtml.cs.
ASP.NET Core 2.x
ASP.NET Core 1.x
Impedi que usuários recém-registrados sendo registrados automaticamente pelo comentar a seguinte linha:

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

O método complete é mostrado com a linha alterada realçada:


public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
ReturnUrl = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");

var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);


var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
await _emailSender.SendEmailConfirmationAsync(Input.Email, callbackUrl);

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


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

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


return Page();
}

Registrar, confirme o email e redefinição de senha


Executar o aplicativo web e testar o fluxo de recuperação de senha e confirmação de conta.
Execute o aplicativo e registrar um novo usuário
Verifique seu email para o link de confirmação de conta. Consulte depurar email se você não receber o
email.
Clique no link para confirmar seu email.
Faça logon com seu email e senha.
Fazer logoff.
Exibir a página de gerenciamento
Selecione o nome de usuário no navegador:

Talvez seja necessário expandir a barra de navegação para ver o nome de usuário.

ASP.NET Core 2.x


ASP.NET Core 1.x
A página de gerenciamento é exibida com o perfil guia selecionada. O Email mostra uma caixa de seleção que
indica o email foi confirmada.
Redefinição de senha do teste
Se você estiver conectado, selecione Logout.
Selecione o login link e selecione o esqueceu sua senha? link.
Insira o email que é usado para registrar a conta.
É enviado um email com um link para redefinir sua senha. Verifique seu email e clique no link para redefinir
sua senha. Depois que sua senha foi redefinida com êxito, você pode fazer logon com seu email e a nova
senha.
Depurar email
Se você não pode receber email de trabalho:
Criar um aplicativo de console para enviar email.
Examine o atividade Email página.
Verifique a pasta de spam.
Tente outro alias de email em um provedor de email diferente (Microsoft, Yahoo, Gmail, etc.)
Tente enviar para contas de email diferente.
Uma prática recomendada de segurança é não use segredos de produção no desenvolvimento e teste. Se
você publicar o aplicativo no Azure, você pode definir os segredos do SendGrid como configurações de aplicativo
no portal do aplicativo Web do Azure. O sistema de configuração é configurado para ler as chaves de variáveis
de ambiente.

Combinar as contas de logon local e social


Para concluir esta seção, você deve primeiro habilitar um provedor de autenticação externa. Consulte Facebook,
Google e a autenticação do provedor externo.
Você pode combinar as contas locais e sociais clicando no link seu email. Na sequência a seguir,
"RickAndMSFT@gmail.com" é criado como um logon local; no entanto, você pode criar a conta como um logon
social primeiro, depois de adicionar um logon local.

Clique no gerenciar link. Observe externo 0 (logons sociais) associada à conta.

Clique no link para outro serviço de logon e aceitar as solicitações do aplicativo. Na imagem a seguir, o Facebook
é o provedor de autenticação externa:
As duas contas foram combinadas. É possível fazer logon com a conta. Convém que os usuários adicionem
contas locais, caso seu serviço de autenticação de logon social está inoperante ou mais provável perdeu o acesso
à sua conta social.

Habilitar confirmação de conta após um site tem usuários


Habilitar confirmação de conta em um site com usuários bloqueia todos os usuários existentes. Os usuários estão
bloqueados porque suas contas não são confirmadas. Solução alternativa para sair do bloqueio do usuário, use
uma das seguintes abordagens:
Atualizar o banco de dados para marcar todos os usuários existentes como sendo confirmada
Confirme se os usuários existentes. Por exemplo, envio em lote-emails com links de confirmação.
Habilitar a geração de código QR para aplicativos de
autenticador no núcleo do ASP.NET
10/04/2018 • 4 min to read • Edit Online

Observação: Este tópico se aplica ao ASP.NET Core 2. x


ASP.NET Core é fornecido com suporte para aplicativos de autenticador para autenticação individual. Dois
aplicativos factor authentication (2FA) autenticador, usando um baseada em tempo de uso único senha algoritmo
(TOTP ), são o setor abordagem para 2FA recomendado. 2FA usar TOTP é preferível à 2FA do SMS. Um aplicativo
autenticador fornece um código de 6 a 8 dígitos que os usuários devem digitar depois de confirmar seu nome de
usuário e senha. Normalmente, um aplicativo autenticador é instalado em um Smartphone.
Os modelos de aplicativo web ASP.NET Core autenticadores de suporte, mas não fornecem suporte para geração
de QRCode. Geradores de QRCode facilitam a configuração de 2FA. Este documento o orientará durante a adição
de código QR geração para a página de configuração 2FA.

Adicionar códigos QR para a página de configuração 2FA


Essas instruções usam qrcode.js do https://davidshimjs.github.io/qrcodejs/ repositório.
Baixe o biblioteca de javascript qrcode.js para o wwwroot\lib pasta em seu projeto.
Em Pages\Account\Manage\EnableAuthenticator.cshtml (páginas Razor) ou
Views\Manage\EnableAuthenticator.cshtml (MVC ), localize o Scripts seção no final do arquivo:

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

Atualização de Scripts seção para adicionar uma referência para o qrcodejs biblioteca que você adicionou e
uma chamada para gerar o código QR. Ele deve ser da seguinte maneira:

@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")

<script type="text/javascript" src="~/lib/qrcode.js"></script>


<script type="text/javascript">
new QRCode(document.getElementById("qrCode"),
{
text: "@Html.Raw(Model.AuthenticatorUri)",
width: 150,
height: 150
});
</script>
}

Exclua o parágrafo que vincula a essas instruções.


Executar seu aplicativo e certifique-se de que você pode escanear o código QR e validar o código que é o
autenticador.

Alterar o nome do site no código QR


O nome do site no código QR é obtido do nome do projeto que você escolher ao criar seu projeto. Você pode
alterá-lo, procurando o GenerateQrCodeUri(string email, string unformattedKey) método o
Pages\Account\Manage\EnableAuthenticator.cshtml.cs arquivo (páginas Razor) ou o
Controllers\ManageController.cs arquivo (MVC ).
O código padrão do modelo terá a seguinte aparência:

private string GenerateQrCodeUri(string email, string unformattedKey)


{
return string.Format(
AuthenicatorUriFormat,
_urlEncoder.Encode("Razor Pages"),
_urlEncoder.Encode(email),
unformattedKey);
}

O segundo parâmetro na chamada para string.Format é o nome do site, obtido com o nome da solução. Ele pode
ser alterado para qualquer valor, mas deve ser sempre codificados de URL.

Usando uma biblioteca de código QR diferente


Você pode substituir a biblioteca de código QR por sua biblioteca preferencial. O HTML não contém um qrCode
fornece do elemento no qual você pode colocar um código QR por qualquer mecanismo sua biblioteca.
A URL formatada corretamente para o código QR está disponível na:
AuthenticatorUri propriedade do modelo.
data-url propriedade no qrCodeData elemento.

TOTP cliente e servidor diferença de horário


Autenticação TOTP depende do dispositivo do servidor e o autenticador tendo um horário com precisão. Tokens
apenas últimos 30 segundos. Se os logons de 2FA TOTP estiverem falhando, verifique se a hora do servidor,
sincronização e precisão preferencialmente para um serviço NTP preciso.
Autenticação de dois fatores com SMS no núcleo do
ASP.NET
10/04/2018 • 9 min to read • Edit Online

Por Rick Anderson e desenvolvedores Suíça


Este tutorial aplica-se ao ASP.NET Core apenas 1. x. Consulte geração de código de QR habilitar para aplicativos
de autenticador no ASP.NET Core para ASP.NET Core 2.0 e posterior.
Este tutorial mostra como configurar a autenticação de dois fatores (2FA) usando o SMS. As instruções são
fornecidas para twilio e ASPSMS, mas você pode usar qualquer outro provedor SMS. Recomendamos que você
realize confirmação de conta e senha de recuperação antes de iniciar este tutorial.
Exibição de exemplo concluído. Como baixar.

Criar um novo projeto ASP.NET Core


Criar um novo aplicativo web de ASP.NET Core denominado Web2FA com contas de usuário individuais. Siga as
instruções em impor SSL em um aplicativo do ASP.NET Core para configurar e exigir SSL.
Criar uma conta do SMS
Criar uma conta SMS, por exemplo, de twilio ou ASPSMS. Registre as credenciais de autenticação (para o twilio:
accountSid e authToken para ASPSMS: chave de usuário confidenciais e a senha).
Descobrir as credenciais do provedor de SMS
Twilio:
Na guia Painel de sua conta do Twilio, copie o SID de conta e token de autenticação.
ASPSMS:
De suas configurações de conta, navegue até chave de usuário confidenciais e copie-a junto com seu senha.
Mais tarde armazenaremos esses valores com a ferramenta de Gerenciador de segredo nas chaves
SMSAccountIdentification e SMSAccountPassword .

Especificando SenderID / originador


Twilio:
Na guia números, copie o Twilio número de telefone.
ASPSMS:
No Menu originadores desbloquear desbloquear originadores de uma ou mais ou escolha um originador
alfanumérico (não é suportado por todas as redes).
Posteriormente, armazenará esse valor com a ferramenta Gerenciador de segredo na chave do SMSAccountFrom .
Forneça credenciais para o serviço SMS
Usaremos o padrão de opções para acessar as configurações de conta e a chave de usuário.
Crie uma classe para obter a chave segura do SMS. Para este exemplo, o SMSoptions classe é criada no
Services/SMSoptions.cs arquivo.
namespace Web2FA.Services
{
public class SMSoptions
{
public string SMSAccountIdentification { get; set; }
public string SMSAccountPassword { get; set; }
public string SMSAccountFrom { get; set; }
}
}

Definir o SMSAccountIdentification , SMSAccountPassword e SMSAccountFrom com o ferramenta Gerenciador de


segredo. Por exemplo:

C:/Web2FA/src/WebApp1>dotnet user-secrets set SMSAccountIdentification 12345


info: Successfully saved SMSAccountIdentification = 12345 to the secret store.

Adicione o pacote NuGet do provedor de SMS. Do pacote Manager Console (PMC ) executar:
Twilio:
Install-Package Twilio

ASPSMS:
Install-Package ASPSMS

Adicione código de Services/MessageServices.cs arquivo para habilitar o SMS. Use o Twilio ou seção ASPSMS:
Twilio:
using Microsoft.Extensions.Options;
using System.Threading.Tasks;
using Twilio;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Types;

namespace Web2FA.Services
{
// This class is used by the application to send Email and SMS
// when you turn on two-factor authentication in ASP.NET Identity.
// For more details see this link https://go.microsoft.com/fwlink/?LinkID=532713
public class AuthMessageSender : IEmailSender, ISmsSender
{
public AuthMessageSender(IOptions<SMSoptions> optionsAccessor)
{
Options = optionsAccessor.Value;
}

public SMSoptions Options { get; } // set only via Secret Manager

public Task SendEmailAsync(string email, string subject, string message)


{
// Plug in your email service here to send an email.
return Task.FromResult(0);
}

public Task SendSmsAsync(string number, string message)


{
// Plug in your SMS service here to send a text message.
// Your Account SID from twilio.com/console
var accountSid = Options.SMSAccountIdentification;
// Your Auth Token from twilio.com/console
var authToken = Options.SMSAccountPassword;

TwilioClient.Init(accountSid, authToken);

return MessageResource.CreateAsync(
to: new PhoneNumber(number),
from: new PhoneNumber(Options.SMSAccountFrom),
body: message);
}
}
}

ASPSMS:
using Microsoft.Extensions.Options;
using System.Threading.Tasks;

namespace Web2FA.Services
{
// This class is used by the application to send Email and SMS
// when you turn on two-factor authentication in ASP.NET Identity.
// For more details see this link https://go.microsoft.com/fwlink/?LinkID=532713
public class AuthMessageSender : IEmailSender, ISmsSender
{
public AuthMessageSender(IOptions<SMSoptions> optionsAccessor)
{
Options = optionsAccessor.Value;
}

public SMSoptions Options { get; } // set only via Secret Manager

public Task SendEmailAsync(string email, string subject, string message)


{
// Plug in your email service here to send an email.
return Task.FromResult(0);
}

public Task SendSmsAsync(string number, string message)


{
ASPSMS.SMS SMSSender = new ASPSMS.SMS();

SMSSender.Userkey = Options.SMSAccountIdentification;
SMSSender.Password = Options.SMSAccountPassword;
SMSSender.Originator = Options.SMSAccountFrom;

SMSSender.AddRecipient(number);
SMSSender.MessageData = message;

SMSSender.SendTextSMS();

return Task.FromResult(0);
}
}
}

Configurar a inicialização para usar SMSoptions

Adicionar SMSoptions no contêiner de serviço no ConfigureServices método o Startup.cs:

// Add application services.


services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
services.Configure<SMSoptions>(Configuration);
}

Habilitar a autenticação de dois fatores


Abra o Views/Manage/Index.cshtml o arquivo de exibição Razor e remover caracteres de comentário (portanto,
nenhuma marcação é comentário removido).

Faça logon com a autenticação de dois fatores


Execute o aplicativo e registrar um novo usuário
Toque em seu nome de usuário que ativa o Index método de ação no controlador de gerenciamento. Em
seguida, toque o número de telefone adicionar link.

Adicionar um número de telefone que receberá o código de verificação e toque em enviar o código de
verificação.
Você receberá uma mensagem de texto com o código de verificação. Insira-o e toque em enviar

Se você não receber uma mensagem de texto, consulte a página de registro do twilio.
O modo de gerenciar mostra que o número de telefone foi adicionado com êxito.
Toque em habilitar para habilitar a autenticação de dois fatores.

Autenticação de dois fatores do teste


Fazer logoff.
Iniciar sessão.
A conta de usuário habilitou a autenticação de dois fatores, você precisará fornecer o segundo fator de
autenticação. Neste tutorial você ativou a verificação do telefone. Os modelos internos de também
permitem que você configurar o email como o segundo fator. Você pode configurar os fatores adicionais de
segundo para autenticação, como códigos QR. Toque em enviar.

Insira o código que você pode obter na mensagem SMS.


Clicar no lembrar este navegador caixa de seleção isentará a necessidade de usar 2FA para fazer logon
ao usar o mesmo dispositivo e o navegador. Habilitando 2FA e clicando em lembrar este navegador
fornecerá 2FA forte proteção contra usuários mal-intencionados que tentar acessar sua conta, desde que
eles não têm acesso ao dispositivo. Você pode fazer isso em qualquer dispositivo privado que você usa
regularmente. Definindo lembrar este navegador, obter a segurança adicional de 2FA de dispositivos que
você não usa regularmente e obter a conveniência de não ter percorrer 2FA no seus próprios dispositivos.
Bloqueio de conta para proteger contra ataques de força bruta
Bloqueio de conta é recomendado com 2FA. Depois que um usuário faz logon por meio de uma conta local ou
sociais, cada tentativa falha de 2FA é armazenada. Se as tentativas de acesso com falha máximo for atingido, o
usuário está bloqueado (padrão: bloqueio de 5 minutos após 5 tentativas de acesso). Uma autenticação bem-
sucedida redefine a contagem de tentativas de acesso com falha e redefine o relógio. O máximo de tentativas de
acesso com falha e tempo de bloqueio pode ser definido com MaxFailedAccessAttempts e
DefaultLockoutTimeSpan. A seguir configura o bloqueio de conta para 10 minutos após 10 tentativas de acesso:

public void ConfigureServices(IServiceCollection services)


{
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

services.AddMvc();

services.Configure<IdentityOptions>(options =>
{
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
});

// Add application services.


services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
services.Configure<SMSoptions>(Configuration);
}

Confirme se PasswordSignInAsync define lockoutOnFailure para true :

var result = await _signInManager.PasswordSignInAsync(


Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);
Usar autenticação de cookie sem a identidade do
ASP.NET Core
04/05/2018 • 28 min to read • Edit Online

Por Rick Anderson e Luke Latham


Como você viu nos tópicos anteriores de autenticação, a identidade do ASP.NET Core é um provedor de
autenticação completa e completo para criar e manter os logons. No entanto, você talvez queira usar sua própria
lógica de autenticação personalizada com autenticação baseada em cookie às vezes. Você pode usar a
autenticação baseada em cookie como um provedor de autenticação autônomo sem a identidade do ASP.NET
Core.
Exibir ou baixar código de exemplo (como baixar)
Para obter informações sobre a autenticação baseada em cookie Migrando do ASP.NET Core 1. x para 2.0,
consulte migrar autenticação e identidade de tópico do ASP.NET Core 2.0 (autenticação baseada em Cookie).

Configuração
ASP.NET Core 2.x
ASP.NET Core 1.x
Se você não estiver usando o Microsoft.AspNetCore.All metapackage, instale a versão 2.0 + o
Microsoft.AspNetCore.Authentication.Cookies pacote NuGet.
No ConfigureServices método, criar o serviço de Middleware de autenticação com o AddAuthentication e
AddCookie métodos:

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie();

AuthenticationScheme passado para AddAuthentication define o esquema de autenticação padrão para o


aplicativo. AuthenticationScheme é útil quando há várias instâncias de autenticação de cookie e você deseja
autorizar com um esquema específico. Definindo o AuthenticationScheme para
CookieAuthenticationDefaults.AuthenticationScheme fornece um valor de "Cookies" para o esquema. Você pode
fornecer qualquer valor de cadeia de caracteres que diferencia o esquema.
No Configuremétodo, use o UseAuthentication método a invocar o Middleware de autenticação que define o
HttpContext.User propriedade. Chamar o UseAuthentication método antes de chamar UseMvcWithDefaultRoute
ou UseMvc :

app.UseAuthentication();

Opções de AddCookie
O CookieAuthenticationOptions classe é usada para configurar as opções de provedor de autenticação.
OPÇÃO DESCRIÇÃO

AccessDeniedPath Fornece o caminho para fornecer um 302 não encontrado


(redirecionamento de URL) quando disparadas por
HttpContext.ForbidAsync . O valor padrão é
/Account/AccessDenied .

ClaimsIssuer O emissor a ser usado para o emissor propriedade em


quaisquer declarações criado pelo serviço de autenticação de
cookie.

Cookie.Domain O nome de domínio em que o cookie é atendido. Por padrão,


esse é o nome do host da solicitação. O navegador envia
somente o cookie em solicitações para um nome de host
correspondente. Talvez você queira ajustar isso para que os
cookies disponíveis para qualquer host no seu domínio. Por
exemplo, definir o domínio do cookie .contoso.com
disponibiliza para contoso.com , www.contoso.com , e
staging.www.contoso.com .

Cookie.Expiration Obtém ou define o tempo de vida de um cookie. No


momento, essa opção não ops e se torna obsoleta no ASP.NET
Core 2.1 +. Use o ExpireTimeSpan opção para definir a
expiração do cookie. Para obter mais informações, consulte
esclarecer o comportamento de
CookieAuthenticationOptions.Cookie.Expiration.

Cookie.HttpOnly Um sinalizador que indica se o cookie deve ser acessível


somente para servidores. A alteração desse valor para false
permite que scripts do lado do cliente para acessar o cookie e
pode abrir o aplicativo para roubo de cookie deve ter seu
aplicativo um script entre sites (XSS) vulnerabilidade. O valor
padrão é true .

Cookie.Name Define o nome do cookie.

Cookie.Path Usado para isolar os aplicativos em execução no mesmo


nome de host. Se você tiver um aplicativo em execução no
/app1 e deseja restringir os cookies para esse aplicativo,
defina o CookiePath propriedade /app1 . Por isso, o cookie
só está disponível em solicitações para /app1 e qualquer
aplicativo abaixo dele.

Cookie.SameSite Indica se o navegador deve permitir que o cookie a ser


anexado ao mesmo site solicitações somente (
SameSiteMode.Strict ) ou solicitações de sites usando
métodos HTTP seguros e solicitações do mesmo site (
SameSiteMode.Lax ). Quando definido como
SameSiteMode.None , o valor do cabeçalho de cookie não
está definido. Observe que Middleware do Cookie política
pode substituir o valor que você fornecer. Para dar suporte à
autenticação OAuth, o valor padrão é SameSiteMode.Lax .
Para obter mais informações, consulte autenticação OAuth
interrompida devido à política de cookie SameSite.
OPÇÃO DESCRIÇÃO

Cookie.SecurePolicy Um sinalizador que indica se o cookie criado deve ser limitado


para HTTPS ( CookieSecurePolicy.Always ), HTTP ou HTTPS (
CookieSecurePolicy.None ), ou o mesmo protocolo que a
solicitação ( CookieSecurePolicy.SameAsRequest ). O valor
padrão é CookieSecurePolicy.SameAsRequest .

DataProtectionProvider Conjuntos de DataProtectionProvider que é usado para


criar o padrão TicketDataFormat . Se o TicketDataFormat
propriedade for definida, o DataProtectionProvider opção
não é usada. Se não for fornecido, o provedor de proteção de
dados do aplicativo padrão é usado.

Eventos O manipulador chama métodos no provedor que dê o


controle de aplicativo em determinados pontos de
processamento. Se Events não fornecido, uma instância
padrão é fornecida que não faz nada quando os métodos são
chamados.

EventsType Usado como o tipo de serviço para obter o Events instância


em vez da propriedade.

ExpireTimeSpan O TimeSpan depois que o tíquete de autenticação


armazenado dentro o cookie expira. ExpireTimeSpan é
adicionado à hora atual para criar o tempo de expiração do
tíquete de. O ExpiredTimeSpan valor sempre vai para o
AuthTicket criptografado verificado pelo servidor. Ele também
pode ir para o Set-Cookie cabeçalho, mas somente se
IsPersistent está definido. Para definir IsPersistent
para true , configure o AuthenticationProperties passado
para SignInAsync . O valor padrão de ExpireTimeSpan é
de 14 dias.

LoginPath Fornece o caminho para fornecer um 302 não encontrado


(redirecionamento de URL) quando disparadas por
HttpContext.ChallengeAsync . A URL atual que gerou o 401
é adicionada para o LoginPath como um parâmetro de
cadeia de caracteres de consulta nomeado pelo
ReturnUrlParameter . Uma vez uma solicitação para o
LoginPath concede uma nova identidade, o
ReturnUrlParameter valor é usado para redirecionar o
navegador para a URL que fez com que o código de status
não autorizado original. O valor padrão é /Account/Login .

LogoutPath Se o LogoutPath é fornecido para o manipulador, em


seguida, redireciona uma solicitação para o caminho com base
no valor da ReturnUrlParameter . O valor padrão é
/Account/Logout .

ReturnUrlParameter Determina o nome do parâmetro de cadeia de caracteres de


consulta será anexado o manipulador de resposta 302 não
encontrado (redirecionamento de URL).
ReturnUrlParameter é usado quando uma solicitação chega
no LoginPath ou LogoutPath para retornar o navegador a
URL original depois que a ação de logon ou logout é
executada. O valor padrão é ReturnUrl .
OPÇÃO DESCRIÇÃO

SessionStore Um contêiner opcional usado para armazenar a identidade


entre solicitações. Quando usado, um identificador de sessão
é enviado ao cliente. SessionStore pode ser usado para
atenuar problemas potenciais com identidades grandes.

slidingExpiration Um sinalizador que indica se um novo cookie com um tempo


de expiração atualizados deve ser emitido dinamicamente.
Isso pode acontecer em qualquer solicitação em que o
período de expiração do cookie atual mais de 50% expirou. A
nova data de expiração é movida para frente para ser a data
atual mais o ExpireTimespan . Um tempo de expiração do
cookie absoluto pode ser definida usando o
AuthenticationProperties classe ao chamar SignInAsync
. Um tempo de expiração absoluta pode melhorar a segurança
do seu aplicativo, limitando a quantidade de tempo que o
cookie de autenticação é válido. O valor padrão é true .

TicketDataFormat O TicketDataFormat é usado para proteger e desproteger a


identidade e outras propriedades que são armazenadas no
valor do cookie. Se não for fornecido, um TicketDataFormat
é criado usando o DataProtectionProvider.

Validar Método que verifica que as opções são válidas.

Definir CookieAuthenticationOptions na configuração de serviço para autenticação no ConfigureServices método:

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
...
});

Middleware de política de cookie


Middleware do cookie política habilita recursos de política de cookie em um aplicativo. Adicionar o middleware
para o pipeline de processamento de aplicativos é a ordem e minúsculas; ela afeta apenas os componentes
registrados depois no pipeline.

app.UseCookiePolicy(cookiePolicyOptions);

O CookiePolicyOptions fornecido para o Middleware do Cookie política permitem que você controle
características globais do processamento de cookie e gancho em manipuladores de processamento do cookie
quando os cookies são anexados ou excluídos.

PROPRIEDADE DESCRIÇÃO

HttpOnly Afeta se os cookies devem ser HttpOnly, que é um sinalizador


que indica se o cookie deve ser acessível somente para
servidores. O valor padrão é HttpOnlyPolicy.None .

MinimumSameSitePolicy Afeta o atributo de mesmo site do cookie (veja abaixo). O


valor padrão é SameSiteMode.Lax . Essa opção está
disponível para o ASP.NET Core 2.0 +.
PROPRIEDADE DESCRIÇÃO

OnAppendCookie Chamado quando um cookie é anexado.

OnDeleteCookie Chamado quando um cookie é excluído.

Proteger Afeta se os cookies devem ser seguro. O valor padrão é


CookieSecurePolicy.None .

MinimumSameSitePolicy (ASP.NET Core 2.0 + apenas)


O padrão MinimumSameSitePolicy valor é SameSiteMode.Lax para permitir a autenticação OAuth2. Estritamente
impor uma política do mesmo site do SameSiteMode.Strict , defina o MinimumSameSitePolicy . Embora essa
configuração quebras OAuth2 e outros esquemas de autenticação entre origens, ele eleva o nível de segurança do
cookie para outros tipos de aplicativos que não dependem de processamento de solicitação entre origens.

var cookiePolicyOptions = new CookiePolicyOptions


{
MinimumSameSitePolicy = SameSiteMode.Strict,
};

A configuração de Middleware de política de Cookie para MinimumSameSitePolicy pode afetar sua configuração de
Cookie.SameSite na CookieAuthenticationOptions configurações de acordo com a matriz abaixo.

CONFIGURAÇÃO DE COOKIE.SAMESITE
MINIMUMSAMESITEPOLICY COOKIE.SAMESITE RESULTANTE

SameSiteMode.None SameSiteMode.None SameSiteMode.None


SameSiteMode.Lax SameSiteMode.Lax
SameSiteMode.Strict SameSiteMode.Strict

SameSiteMode.Lax SameSiteMode.None SameSiteMode.Lax


SameSiteMode.Lax SameSiteMode.Lax
SameSiteMode.Strict SameSiteMode.Strict

SameSiteMode.Strict SameSiteMode.None SameSiteMode.Strict


SameSiteMode.Lax SameSiteMode.Strict
SameSiteMode.Strict SameSiteMode.Strict

Criando um cookie de autenticação


Para criar um cookie contendo informações de usuário, você precisa construir uma ClaimsPrincipal. As
informações do usuário são serializadas e armazenadas no cookie.
ASP.NET Core 2.x
ASP.NET Core 1.x
Criar um ClaimsIdentity com quaisquer declaraçãos e chame SignInAsync para assinar o usuário:
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.Email),
new Claim("FullName", user.FullName),
new Claim(ClaimTypes.Role, "Administrator"),
};

var claimsIdentity = new ClaimsIdentity(


claims, CookieAuthenticationDefaults.AuthenticationScheme);

var authProperties = new AuthenticationProperties


{
//AllowRefresh = <bool>,
// Refreshing the authentication session should be allowed.

//ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(10),
// The time at which the authentication ticket expires. A
// value set here overrides the ExpireTimeSpan option of
// CookieAuthenticationOptions set with AddCookie.

//IsPersistent = true,
// Whether the authentication session is persisted across
// multiple requests. Required when setting the
// ExpireTimeSpan option of CookieAuthenticationOptions
// set with AddCookie. Also required when setting
// ExpiresUtc.

//IssuedUtc = <DateTimeOffset>,
// The time at which the authentication ticket was issued.

//RedirectUri = <string>
// The full path or absolute URI to be used as an http
// redirect response value.
};

await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);

SignInAsync cria um cookie criptografado e o adiciona à resposta atual. Se você não especificar um
AuthenticationScheme , o esquema padrão é usado.

Nos bastidores, a criptografia é ASP.NET Core proteção de dados sistema. Se você estiver hospedando o
aplicativo em vários computadores, balanceamento de carga em aplicativos ou usando uma web farm, você deve
configurar a proteção de dados para usar o mesmo anel de chave e o identificador do aplicativo.

Sair
ASP.NET Core 2.x
ASP.NET Core 1.x
Para desconectar o usuário atual e exclua seus cookies, chame SignOutAsync:

await HttpContext.SignOutAsync(
CookieAuthenticationDefaults.AuthenticationScheme);

Se você não estiver usando CookieAuthenticationDefaults.AuthenticationScheme (ou "Cookies") como o esquema


(por exemplo, "ContosoCookie"), forneça o esquema usado ao configurar o provedor de autenticação. Caso
contrário, o esquema padrão será usado.
Reagir a alterações de back-end
Quando um cookie é criado, ele se torna a única fonte de identidade. Mesmo se você desabilitar um usuário nos
seus sistemas de back-end, o sistema de autenticação de cookie não tem conhecimento sobre isso e um usuário
permanece conectado como o cookie é válido.
O ValidatePrincipal evento no ASP.NET Core 2. x ou ValidateAsync método no núcleo do ASP.NET 1. x pode ser
usado para interceptar e ignorar a validação da identidade do cookie. Isso reduz o risco dos usuários revogados
acessando o aplicativo.
Uma abordagem para validação de cookie baseia-se em manter o controle de quando o banco de dados do
usuário foi alterado. Se o banco de dados não foi alterado desde que o cookie do usuário foi emitido, não é
necessário para autenticar o usuário novamente se o cookie ainda é válido. Para implementar este cenário, o
banco de dados, que é implementado no IUserRepository para este exemplo armazena um LastChanged valor.
Quando nenhum usuário é atualizado no banco de dados, o LastChanged valor é definido para a hora atual.
Para invalidar um cookie quando as alterações do banco de dados com base no LastChanged valor, criar o cookie
com um LastChanged declaração contendo atual LastChanged valor do banco de dados:

var claims = new List<Claim>


{
new Claim(ClaimTypes.Name, user.Email),
new Claim("LastChanged", {Database Value})
};

var claimsIdentity = new ClaimsIdentity(


claims,
CookieAuthenticationDefaults.AuthenticationScheme);

await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity));

ASP.NET Core 2.x


ASP.NET Core 1.x
Para implementar uma substituição para o ValidatePrincipal evento, a gravação de um método com a seguinte
assinatura em uma classe que deriva de CookieAuthenticationEvents:

ValidatePrincipal(CookieValidatePrincipalContext)

Um exemplo é semelhante ao seguinte:


using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;

public class CustomCookieAuthenticationEvents : CookieAuthenticationEvents


{
private readonly IUserRepository _userRepository;

public CustomCookieAuthenticationEvents(IUserRepository userRepository)


{
// Get the database from registered DI services.
_userRepository = userRepository;
}

public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)


{
var userPrincipal = context.Principal;

// Look for the LastChanged claim.


var lastChanged = (from c in userPrincipal.Claims
where c.Type == "LastChanged"
select c.Value).FirstOrDefault();

if (string.IsNullOrEmpty(lastChanged) ||
!_userRepository.ValidateLastChanged(lastChanged))
{
context.RejectPrincipal();

await context.HttpContext.SignOutAsync(
CookieAuthenticationDefaults.AuthenticationScheme);
}
}
}

Registrar a instância de eventos durante o registro do serviço de cookie no ConfigureServices método. Forneça
um registro de serviço com escopo definido para seu CustomCookieAuthenticationEvents classe:

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.EventsType = typeof(CustomCookieAuthenticationEvents);
});

services.AddScoped<CustomCookieAuthenticationEvents>();

Considere uma situação em que o nome do usuário é atualizado — uma decisão que não afeta a segurança de
qualquer forma. Se você quiser atualizar não destrutivo a entidade de usuário, chame context.ReplacePrincipal e
defina o context.ShouldRenew propriedade true .

AVISO
A abordagem descrita aqui é acionada em cada solicitação. Isso pode resultar em uma penalidade de desempenho para o
aplicativo.

Cookies persistentes
Talvez você queira que o cookie para persistir nas sessões do navegador. Essa persistência deve ser habilitada
apenas com a permissão explícita do usuário com uma "Lembrar-Me" caixa de seleção no logon ou um
mecanismo semelhante.
O trecho de código a seguir cria uma identidade e o cookie correspondente que sobrevive a fechamentos de
navegador. As configurações de expiração deslizante configuradas anteriormente são respeitadas. Se o cookie
expirar enquanto o navegador for fechado, o navegador limpa o cookie depois que ele seja reiniciado.
ASP.NET Core 2.x
ASP.NET Core 1.x

await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
new AuthenticationProperties
{
IsPersistent = true
});

O AuthenticationProperties classe reside no Microsoft.AspNetCore.Authentication namespace.

Expiração do cookie absoluto


Você pode definir um tempo de expiração absoluta com ExpiresUtc . Você também deve definir IsPersistent ;
caso contrário, ExpiresUtc é ignorada e um cookie de sessão único é criado. Quando ExpiresUtc é definido em
SignInAsync , ela substitui o valor da ExpireTimeSpan opção de CookieAuthenticationOptions , se definido.

O trecho de código a seguir cria uma identidade e o cookie correspondente que tem duração de 20 minutos. Isso
ignora as configurações de expiração deslizante configuradas anteriormente.
ASP.NET Core 2.x
ASP.NET Core 1.x

await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMinutes(20)
});

Consulte também
Alterações de autenticação 2.0 / Comunicado de migração
Limitar a identidade por esquema
Autorização baseada em declarações
Verificações de função e baseada em políticas
Azure Active Directory com o ASP.NET Core
10/04/2018 • 1 min to read • Edit Online

Integrando o Azure AD em um aplicativo Web ASP.NET Core


Chamando uma API Web ASP.NET Core em um aplicativo do WPF usando o Azure AD
Chamando uma API Web em um aplicativo Web ASP.NET Core usando o Azure AD
Um aplicativo API Web ASP.NET Core com o Azure AD B2C
Autenticação de nuvem com o Azure Active
Directory B2C no núcleo do ASP.NET
15/03/2018 • 10 min to read • Edit Online

Por Cam Soper


B2C de diretório ativo do Azure (Azure AD B2C ) é uma solução de gerenciamento de identidade de nuvem para
aplicativos web e móveis. O serviço fornece autenticação para aplicativos hospedados na nuvem e local. Tipos de
autenticação incluem contas individuais, contas de rede social e federados contas corporativas. Além disso, o Azure
AD B2C pode fornecer autenticação multifator com configuração mínima.

DICA
Azure Active Directory (AD do Azure) do Azure AD B2C são ofertas de produtos separados. Um locatário do AD do Azure
representa uma organização, enquanto um locatário Azure AD B2C representa uma coleção de identidades a serem usadas
com aplicativos de terceira parte confiável. Para obter mais informações, consulte do Azure AD B2C: perguntas frequentes
(FAQ).

Neste tutorial, saiba como:


Crie um locatário do Azure Active Directory B2C
Registrar um aplicativo no Azure B2C do AD
Usar o Visual Studio para criar um aplicativo web do ASP.NET Core configurado para usar o locatário do Azure
AD B2C para autenticação
Configurar políticas que controlam o comportamento do locatário do Azure AD B2C

Pré-requisitos
A seguir é necessários para este passo a passo:
Assinatura do Microsoft Azure
Visual Studio de 2017 (qualquer edição)

Criar o locatário do Azure Active Directory B2C


Crie um locatário do Azure Active Directory B2C conforme descrito na documentação do. Quando solicitado,
associar o locatário com uma assinatura do Azure é opcional para este tutorial.

Registrar o aplicativo em B2C do Azure AD


O locatário do Azure AD B2C recém-criado, registre seu aplicativo usando as etapas na documentação do sob o
registrar um aplicativo web seção. Parar no criar um segredo de cliente do aplicativo web seção. Um
segredo do cliente não é necessário para este tutorial.
Use os seguintes valores:

CONFIGURAÇÃO VALOR OBSERVAÇÕES


CONFIGURAÇÃO VALOR OBSERVAÇÕES

Nome <Nome do aplicativo> Insira um nome para o aplicativo que


descrevem seu aplicativo para os
consumidores.

Incluir o aplicativo web / da web Sim


API

Permitir que o fluxo implícito Sim

URL de resposta https://localhost:44300 URLs de resposta são pontos de


extremidade em que o Azure AD B2C
retorna todos os tokens que solicita a
seu aplicativo. O Visual Studio fornece a
URL de resposta para usar. Por
enquanto, digite
https://localhost:44300 para
preencher o formulário.

URI da ID do aplicativo Deixe em branco Não é necessário para este tutorial.

Incluir o cliente nativo Não

AVISO
Se a configuração de uma URL de resposta não localhost, esteja ciente do restrições sobre o que é permitido na lista de URL
de resposta.

Depois que o aplicativo for registrado, é exibida a lista de aplicativos no locatário. Selecione o aplicativo que
acabou de ser registrado. Selecione o cópia ícone à direita do ID do aplicativo campo para copiá-lo para a área
de transferência.
Nada mais podem ser configurado no locatário do Azure AD B2C neste momento, mas deixam a janela do
navegador aberta. Há mais de configuração depois que o aplicativo ASP.NET Core é criado.

Criar um aplicativo do ASP.NET Core no Visual Studio de 2017


O modelo de aplicativo de Web do Visual Studio pode ser configurado para usar o locatário do Azure AD B2C
para autenticação.
No Visual Studio:
1. Crie um novo Aplicativo Web ASP.NET Core.
2. Selecione aplicativo Web da lista de modelos.
3. Selecione o alterar autenticação botão.
4. No alterar autenticação caixa de diálogo, selecione contas de usuário individuaise, em seguida,
selecione conectar a um repositório de usuário existente na nuvem na lista suspensa.

5. Preencha o formulário com os seguintes valores:

CONFIGURAÇÃO VALOR

Nome de domínio <o nome de domínio do seu locatário B2C>

ID do aplicativo <Cole a ID do aplicativo da área de transferência>


CONFIGURAÇÃO VALOR

Caminho de retorno de chamada <Use o valor padrão>

Política de inscrever-se ou entrar B2C_1_SiUpIn

Política de redefinição de senha B2C_1_SSPR

Editar política de perfil <Deixe em branco>

Selecione o cópia próximo ao link URI Reply para copiar o URI de resposta para a área de transferência.
Selecione Okey para fechar o alterar autenticação caixa de diálogo. Selecione Okey para criar o
aplicativo web.

Concluir o registro do aplicativo B2C


Retornar à janela do navegador com as propriedades do aplicativo B2C ainda abertas. Alterar temporárias URL de
resposta especificado anteriormente para o valor copiado do Visual Studio. Selecione salvar na parte superior da
janela.

DICA
Se você não copiar a URL de resposta, use o endereço SSL da guia depuração nas propriedades do projeto web e acrescente
o CallbackPath valor appSettings. JSON.

Configurar políticas
Use as etapas da documentação do Azure AD B2C criar uma política de inscrever-se ou entrare, em seguida, criar
uma política de redefinição de senha. Use os valores de exemplo fornecidos na documentação do provedores de
identidade, inscrição atributos, e declarações de aplicativo. Usando o executar agora botão para testar as
políticas, conforme descrito na documentação é opcional.

AVISO
Verifique os nomes de política são exatamente conforme descrito na documentação, como as políticas foram usadas no
alterar autenticação caixa de diálogo no Visual Studio. Os nomes de política podem ser verificados no appSettings. JSON.

Executar o aplicativo
No Visual Studio, pressione F5 para compilar e executar o aplicativo. Depois que o aplicativo web é iniciado,
selecione entrar.
O navegador é redirecionado para o locatário do Azure AD B2C. Entrar com uma conta existente (se foi criado as
políticas de teste) ou selecione inscrever-se agora para criar uma nova conta. O esqueceu sua senha? link é
usado para redefinir uma senha esquecida.
Depois de entrar com êxito no, o navegador é redirecionado ao aplicativo web.

Próximas etapas
Neste tutorial, você aprendeu como:
Crie um locatário do Azure Active Directory B2C
Registrar um aplicativo no Azure B2C do AD
Usar o Visual Studio para criar um aplicativo de Web do ASP.NET Core configurado para usar o locatário do
Azure AD B2C para autenticação
Configurar políticas que controlam o comportamento do locatário do Azure AD B2C
O aplicativo do ASP.NET Core já está configurado para usar o Azure AD B2C para autenticação, o autorizar
atributo pode ser usado para proteger seu aplicativo. Continue a desenvolver seu aplicativo pelo aprendizado para:
Personalizar a interface do usuário do Azure AD B2C.
Configurar requisitos de complexidade de senha.
Habilitar a autenticação multifator.
Configurar provedores de identidade adicional, como Microsoft, Facebook, Google, Amazon, do Twitter e
outros.
Use a API do Azure AD Graph para recuperar informações de usuário adicionais, como membros do grupo, do
locatário do Azure AD B2C.
Proteger um ASP.NET Core API da web usando o Azure AD B2C.
Chamar uma API da web do .NET a partir de um aplicativo web do .NET usando o Azure AD B2C.
Autenticação de nuvem em APIs com o Azure Active
Directory B2C ASP.NET do núcleo da web
10/04/2018 • 15 min to read • Edit Online

Por Cam Soper


B2C de diretório ativo do Azure (Azure AD B2C ) é uma solução de gerenciamento de identidade de nuvem para
aplicativos web e móveis. O serviço fornece autenticação para aplicativos hospedados na nuvem e local. Tipos de
autenticação incluem contas individuais, contas de rede social e federados contas corporativas. Além disso, o
Azure AD B2C pode fornecer autenticação multifator com configuração mínima.

DICA
Azure Active Directory (AD do Azure) e o Azure AD B2C são ofertas de produtos separados. Um locatário do AD do Azure
representa uma organização, enquanto um locatário Azure AD B2C representa uma coleção de identidades a serem usadas
com aplicativos de terceira parte confiável. Para obter mais informações, consulte do Azure AD B2C: perguntas frequentes
(FAQ).

Como as APIs da web não tem nenhuma interface de usuário, eles são não é possível redirecionar o usuário para
um serviço de token seguro como o Azure AD B2C. Em vez disso, a API é passada um token de portador do
aplicativo de chamada, que já foi autenticada do usuário com o Azure AD B2C. A API, em seguida, valida o token
sem interação direta do usuário.
Neste tutorial, saiba como:
Crie um locatário do Azure Active Directory B2C.
Registre uma API da Web no B2C do Azure AD.
Use o Visual Studio para criar uma API da Web configurado para usar o locatário do Azure AD B2C para
autenticação.
Configure políticas que controlam o comportamento do locatário do Azure AD B2C.
Use carteiro para simular um aplicativo web que apresenta uma caixa de diálogo de logon, recupera um token
e usa para fazer uma solicitação em relação a API da web.

Pré-requisitos
A seguir é necessários para este passo a passo:
Assinatura do Microsoft Azure
Visual Studio de 2017 (qualquer edição)
Postman

Criar o locatário do Azure Active Directory B2C


Crie um locatário Azure AD B2C conforme descrito na documentação do. Quando solicitado, associar o locatário
com uma assinatura do Azure é opcional para este tutorial.

Configurar uma política de inscrever-se ou entrar


Use as etapas da documentação do Azure AD B2C criar uma política de inscrever-se ou entrar. Nomear a política
SiUpIn. Use os valores de exemplo fornecidos na documentação do provedores de identidade, inscrição
atributos, e declarações de aplicativo. Usando o executar agora botão para testar a política conforme descrito
na documentação é opcional.

Registrar a API no B2C do Azure AD


Registrar no locatário do Azure AD B2C recém-criado, usando a API as etapas na documentação do sob o
registrar uma API da web seção.
Use os seguintes valores:

CONFIGURAÇÃO VALOR OBSERVAÇÕES

Nome <Nome da API> Insira um nome para o aplicativo que


descrevem seu aplicativo para os
consumidores.

Incluir o aplicativo web / da web Sim


API

Permitir que o fluxo implícito Sim

URL de resposta https://localhost URLs de resposta são pontos de


extremidade em que o Azure AD B2C
retorna todos os tokens que solicita a
seu aplicativo.

URI da ID do aplicativo api O URI não precisa ser resolvido para


um endereço físico. Ele só precisa ser
exclusivo.

Incluir o cliente nativo Não

Depois que a API é registrada, é exibida a lista de aplicativos e APIs no locatário. Selecione a API que acabou de
ser registrada. Selecione o cópia ícone à direita do ID do aplicativo campo para copiá-lo para a área de
transferência. Selecione publicado escopos e verifique se o padrão user_impersonation escopo está presente.

Criar um aplicativo do ASP.NET Core no Visual Studio de 2017


O modelo de aplicativo de Web do Visual Studio pode ser configurado para usar o locatário do Azure AD B2C
para autenticação.
No Visual Studio:
1. Crie um novo Aplicativo Web ASP.NET Core.
2. Selecione API da Web da lista de modelos.
3. Selecione o alterar autenticação botão.
4. No alterar autenticação caixa de diálogo, selecione contas de usuário individuaise, em seguida,
selecione conectar a um repositório de usuário existente na nuvem na lista suspensa.

5. Preencha o formulário com os seguintes valores:

CONFIGURAÇÃO VALOR

Nome de domínio <o nome de domínio do seu locatário B2C>

ID do aplicativo <Cole a ID do aplicativo da área de transferência>

Política de inscrever-se ou entrar B2C_1_SiUpIn

Selecione Okey para fechar o alterar autenticação caixa de diálogo. Selecione Okey para criar o
aplicativo web.
O Visual Studio cria a API da web com um controlador chamado ValuesController.cs que retorna valores
embutidos para solicitações GET. A classe está decorada com o autorizar atributo, portanto, todas as solicitações
requerem autenticação.
Executar a API da web
No Visual Studio, execute a API. Visual Studio inicia um navegador apontado URL da raiz da API. Anote a URL na
barra de endereços e deixe a API em execução em segundo plano.

OBSERVAÇÃO
Como não há nenhum controlador definido para a URL raiz, o navegador exibe um erro 404 de (página não encontrada).
Esse comportamento é esperado.

Use carteiro para obter um token e a API de teste


Carteiro é uma ferramenta para testar APIs da web. Para este tutorial, carteiro simula um aplicativo web que
acessa a API da web em nome do usuário.
Registrar carteiro como um aplicativo web
Como carteiro simula um aplicativo web que pode obter tokens do locatário do Azure AD B2C, ele deve ser
registrado no locatário como um aplicativo web. Registrar carteiro usando as etapas na documentação do sob o
registrar um aplicativo web seção. Parar no criar um segredo de cliente do aplicativo web seção. Um
segredo do cliente não é necessário para este tutorial.
Use os seguintes valores:

CONFIGURAÇÃO VALOR OBSERVAÇÕES

Nome Carteiro

Incluir o aplicativo web / da web Sim


API

Permitir que o fluxo implícito Sim

URL de resposta https://getpostman.com/postman

URI da ID do aplicativo <Deixe em branco> Não é necessário para este tutorial.

Incluir o cliente nativo Não

O aplicativo web recém-registrados precisa de permissão para acessar a API da web em nome do usuário.
1. Selecione carteiro na lista de aplicativos e, em seguida, selecione acesso à API no menu à esquerda.
2. Selecione + adicionar.
3. No selecione API lista suspensa, selecione o nome da API web.
4. No selecione escopos suspenso, certifique-se de que todos os escopos são selecionados.
5. Selecione Okey.
Observe o ID do aplicativo do aplicativo carteiro, pois, é necessário para obter um token de portador.
Criar uma solicitação de carteiro
Inicie o carteiro. Por padrão, exibe carteiro a criar novo caixa de diálogo no início. Se a caixa de diálogo não for
exibida, selecione o + novo botão no canto superior esquerdo.
Do criar novo caixa de diálogo:
1. Selecione solicitação.
2. Digite obter valores no nome da solicitação caixa.
3. Selecione + Criar coleção para criar uma nova coleção para armazenar a solicitação. Nome da coleção
tutoriais do ASP.NET Core e, em seguida, selecione a marca de seleção.
4. Selecione o Salvar para tutoriais do ASP.NET Core botão.
Testar a API da web sem autenticação
Para verificar que a API da web requer autenticação, primeiro verifique uma solicitação sem autenticação.
1. No insira a URL de solicitação , digite a URL para ValuesController . A URL é a mesma exibida no
navegador com api/valores anexado. Um exemplo seria https://localhost:44375/api/values .
2. Selecione o enviar botão.
3. Observe o status da resposta é 401 não autorizado.
Obter um token de portador
Para fazer uma solicitação autenticada para a API da web, é necessário um token de portador. Carteiro facilita a
entrar no locatário do Azure AD B2C e obter um token.
1. No autorização guia o tipo lista suspensa, selecione OAuth 2.0. No adicionar dados de autorização
para lista suspensa, selecione cabeçalhos de solicitação. Selecione obter Token de acesso novo.

2. Concluir o obter novo TOKEN de acesso caixa de diálogo da seguinte maneira:

CONFIGURAÇÃO VALOR OBSERVAÇÕES

Nome do token <nome do token> Insira um nome descritivo para o


token.

Tipo de concessão Implícita

URL de retorno de chamada https://getpostman.com/postman

URL de autenticação Substituir <nome de domínio de


https://login.microsoftonline.com/<tenant
domain name>/oauth2/v2.0/authorize? locatário> com o nome de domínio
p=B2C_1_SiUpIn
do locatário.

ID do cliente <Insira o aplicativo de carteiro ID


do aplicativo>

Segredo do cliente <Deixe em branco>

Escopo https://<tenant domain Substituir <nome de domínio de


name>/<api>/user_impersonation locatário> com o nome de domínio
openid offline_access
do locatário. Substituir <api> com o
nome do projeto de API da Web.
Você também pode usar a ID do
aplicativo. O padrão para a URL é:
https:// {tenant }.onmicrosoft.com/ {ap
p_name_or_id}/ {scope nome}.

Autenticação de cliente Enviar as credenciais do cliente no


corpo

3. Selecione o solicitação de Token botão.


4. Carteiro abre uma nova janela que contém a caixa de diálogo de entrada do locatário do Azure AD B2C.
Entrar com uma conta existente (se foi criado as políticas de teste) ou selecione inscrever-se agora para
criar uma nova conta. O esqueceu sua senha? link é usado para redefinir uma senha esquecida.
5. Depois de entrar com êxito no, a janela será fechada e o gerenciar TOKENS de acesso caixa de diálogo é
exibida. Role para baixo até a parte inferior e selecione o uso Token botão.

A API da web com autenticação de teste


Selecione o enviar botão para enviar a solicitação novamente. Neste momento, o status da resposta é 200 Okey e
a carga JSON é visível na resposta corpo guia.

Próximas etapas
Neste tutorial, você aprendeu como:
Crie um locatário do Azure Active Directory B2C.
Registre uma API da Web no B2C do Azure AD.
Use o Visual Studio para criar uma API da Web configurado para usar o locatário do Azure AD B2C para
autenticação.
Configure políticas que controlam o comportamento do locatário do Azure AD B2C.
Use carteiro para simular um aplicativo web que apresenta uma caixa de diálogo de logon, recupera um token
e usa para fazer uma solicitação em relação a API da web.
Continue a desenvolver sua API aprendendo para:
Proteger um ASP.NET Core aplicativo da web usando o Azure AD B2C.
Chamar uma API da web do .NET a partir de um aplicativo web do .NET usando o Azure AD B2C.
Personalizar a interface do usuário do Azure AD B2C.
Configurar requisitos de complexidade de senha.
Habilitar a autenticação multifator.
Configurar provedores de identidade adicional, como Microsoft, Facebook, Google, Amazon, do Twitter e
outros.
Use a API do Azure AD Graph para recuperar informações de usuário adicionais, como membros do grupo, do
locatário do Azure AD B2C.
Artigos com base em projetos do ASP.NET Core
criados com as contas de usuário individuais
10/04/2018 • 1 min to read • Edit Online

Identidade do ASP.NET Core está incluída nos modelos de projeto no Visual Studio com a opção de "Contas de
usuário individuais".
Os modelos de autenticação estão disponíveis no .NET Core CLI com -au Individual :

dotnet new mvc -au Individual


dotnet new webapi -au Individual
dotnet new razor -au Individual

Os artigos a seguir mostram como usar o código gerado em modelos do ASP.NET Core que usam contas de
usuário individuais:
Autenticação de dois fatores com SMS
Confirmação de conta e de recuperação de senha no ASP.NET Core
Criar um aplicativo do ASP.NET Core com dados de usuário protegidos por autorização
Autorização no ASP.NET Core
23/02/2018 • 1 min to read • Edit Online

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
Autorizar com um esquema específico
Introdução à autorização no núcleo do ASP.NET
27/04/2018 • 1 min to read • Edit Online

Autorização é o processo que determina o que um usuário pode fazer. Por exemplo, um usuário administrativo
tem permissão para criar uma biblioteca de documentos e adicionar, editar e excluir documentos. Um usuário não
administrativo trabalhando com esta biblioteca só está autorizado a ler os documentos.
A autorização é ortogonal e independente da autenticação. No entanto, a autorização requer um mecanismo de
autenticação. Autenticação é o processo de verificação de quem é um usuário. A autenticação pode criar uma ou
mais identidades para o usuário atual.

Tipos de autorização
A autorização do ASP.NET Core fornece um modelo simples de funções declarativas baseado em políticas. Ela é
expressa em requisitos e os manipuladores avaliam as reivindicações de um usuário em relação aos requisitos.
Verificações imperativas podem ser baseadas em políticas simples ou políticas que avaliem a identidade do
usuário e propriedades do recurso que o usuário está tentando acessar.

Namespaces
Componentes de autorização, incluindo os atributo AuthorizeAttribute e AllowAnonymousAttribute , são
encontrados no namespace Microsoft.AspNetCore.Authorization .
Consulte a documentação em autorização simples.
Criar um aplicativo do ASP.NET Core com dados de
usuário protegidos por autorização
04/05/2018 • 28 min to read • Edit Online

Por Rick Anderson e Joe Audette


Este tutorial mostra como criar um aplicativo web do ASP.NET Core com dados de usuário protegidos por
autorização. Ele exibe uma lista de contatos criada por usuários autenticados (registrados). Há três grupos de
segurança:
Usuários registrados podem exibir todos os dados aprovados e podem editar/excluir seus próprios dados.
Gerenciadores de podem aprovar ou rejeitar dados de um contato. Apenas os contatos aprovados são
visíveis aos usuários.
Os administradores podem rejeitar/aprovar e editar/excluir todos os dados.
Na imagem a seguir, o usuário Rick ( rick@example.com ) está conectado. Rick só pode ver os contatos aprovados e
os links editar/excluir/criar novo de seus contatos. Somente o último registro criado por Rick exibe os links
editar e excluir. Outros usuários não verão o último registro até que um gerente ou administrador altere o
status para "Aprovado".

Na imagem a seguir, manager@contoso.com está conectado e na função de gerenciadores:


A imagem a seguir mostra a tela de exibição de detalhes de um contato dos gerentes:

O botões aprovar e rejeitar são exibidos somente para administradores e gerentes.


Na imagem a seguir, admin@contoso.com está conectado e na função de gerenciadores:
O administrador tem todos os privilégios. Ele pode ler/editar/excluir todos os contatos e alterar os status deles.
O aplicativo foi criado fazendo scaffolding do seguinte modelo de Contact :

public class Contact


{
public int ContactId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
}

O exemplo contém os seguintes manipuladores de autorização:


ContactIsOwnerAuthorizationHandler : Garante que um usuário só pode editar seus dados.
ContactManagerAuthorizationHandler : Permite que os gerentes aprovem ou rejeitem contatos.
ContactAdministratorsAuthorizationHandler : Permite aos administradores aprovar, rejeitar e editar/excluir
contatos.

Pré-requisitos
Este tutorial é avançado. Você deve estar familiarizado com:
ASP.NET Core
Autenticação
Confirmação de conta e recuperação de senha
Autorização
Entity Framework Core
Consulte este arquivo PDF para a versão do MVC do ASP.NET Core. A versão 1.1 do ASP.NET Core deste tutorial
está nesta pasta. O exemplo do ASP.NET Core 1.1 está em exemplos.

O aplicativo inicial e o concluído


Baixar o concluída aplicativo. Teste o aplicativo concluído para que você se familiarize com seus recursos de
segurança.
O aplicativo inicial
Baixe o aplicativo inicial.
Execute o aplicativo, clique em ContactManager e verifique se você pode criar, editar e excluir um contato.

Proteger os dados de usuário


As seções a seguir têm todas as principais etapas para criar um aplicativo com dados de usuários seguros. Talvez
seja útil para fazer referência ao projeto concluído.
Vincular os dados de contato ao usuário
Usar o ASP.NET identidade ID de usuário para garantir que os usuários pode editar seus dados, mas não de
outros dados de usuários. Adicionar OwnerID e ContactStatus para o Contact modelo:

public class Contact


{
public int ContactId { get; set; }

// user ID from AspNetUser table.


public string OwnerID { get; set; }

public string Name { get; set; }


public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
[DataType(DataType.EmailAddress)]
public string Email { get; set; }

public ContactStatus Status { get; set; }


}

public enum ContactStatus


{
Submitted,
Approved,
Rejected
}

OwnerID é a ID do usuário da tabela AspNetUser no banco de dados de identidade. O campo Status determina
se um contato pode ser visto por usuários gerais.
Criar uma nova migração e atualizar o banco de dados:

dotnet ef migrations add userID_Status


dotnet ef database update
Exigir HTTPS e usuários autenticados
Adicionar IHostingEnvironment para Startup :

public class Startup


{
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
Configuration = configuration;
Environment = env;
}

public IConfiguration Configuration { get; }


private IHostingEnvironment Environment { get; }

No método ConfigureServices do arquivo Startup.cs , adicione o filtro de autorização RequireHttpsAttribute:

public void ConfigureServices(IServiceCollection services)


{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

var skipHTTPS = Configuration.GetValue<bool>("LocalTest:skipHTTPS");


// requires using Microsoft.AspNetCore.Mvc;
services.Configure<MvcOptions>(options =>
{
// Set LocalTest:skipHTTPS to true to skip SSL requrement in
// debug mode. This is useful when not using Visual Studio.
if (Environment.IsDevelopment() && !skipHTTPS)
{
options.Filters.Add(new RequireHttpsAttribute());
}
});

Se você estiver usando o Visual Studio, habilite o HTTPS.


Para redirecionar solicitações HTTP para HTTPS, consulte Middleware de regravação de URL. Se você estiver
usando o Visual Studio Code ou testando em uma plataforma local que não inclui um certificado de teste para
HTTPS:
Definir "LocalTest:skipSSL": true no appsettings. Developement.JSON arquivo.
Exigir usuários autenticados
Defina a política de autenticação padrão para exigir que os usuários sejam autenticados. Você pode recusar a
autenticação no nível de método página Razor, controlador ou ação com o [AllowAnonymous] atributo. Configurar
a política de autenticação padrão para exigir que os usuários sejam autenticados protege recém-adicionado
páginas Razor e controladores. Com a autenticação exigida por padrão é mais segura do que contar com novos
controladores e páginas Razor para incluir o [Authorize] atributo.
Com o requisito de todos os usuários autenticados, o AuthorizeFolder e AuthorizePage chamadas não são
necessárias.
Atualize ConfigureServices com as seguintes alterações:
Comente AuthorizeFolder e AuthorizePage .
Defina a política de autenticação padrão para exigir que os usuários sejam autenticados.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

var skipHTTPS = Configuration.GetValue<bool>("LocalTest:skipHTTPS");


// requires using Microsoft.AspNetCore.Mvc;
services.Configure<MvcOptions>(options =>
{
// Set LocalTest:skipHTTPS to true to skip SSL requrement in
// debug mode. This is useful when not using Visual Studio.
if (Environment.IsDevelopment() && !skipHTTPS)
{
options.Filters.Add(new RequireHttpsAttribute());
}
});

services.AddMvc();
//.AddRazorPagesOptions(options =>
//{
// options.Conventions.AuthorizeFolder("/Account/Manage");
// options.Conventions.AuthorizePage("/Account/Logout");
//});

services.AddSingleton<IEmailSender, EmailSender>();

// requires: using Microsoft.AspNetCore.Authorization;


// using Microsoft.AspNetCore.Mvc.Authorization;
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});

Adicionar AllowAnonymousàs páginas Índice, Sobre e Contatos para que usuários anônimos possam obter
informações sobre o site antes de eles se registrarem.

// requires using Microsoft.AspNetCore.Mvc.RazorPages;


[AllowAnonymous]
public class IndexModel : PageModel
{
public void OnGet()
{

}
}

Adicionar [AllowAnonymous] para o LoginModel e RegisterModel.


Configurar a conta de teste
A classe SeedData cria duas contas: administrador e gerenciador. Use a ferramenta Gerenciador de segredo para
definir uma senha para essas contas. Defina a senha do diretório do projeto (o diretório que contém Program.cs):

dotnet user-secrets set SeedUserPW <PW>


Atualização Main para usar a senha de teste:

public class Program


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

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


{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<ApplicationDbContext>();
context.Database.Migrate();

// requires using Microsoft.Extensions.Configuration;


var config = host.Services.GetRequiredService<IConfiguration>();
// Set password with the Secret Manager tool.
// dotnet user-secrets set SeedUserPW <pw>

var testUserPw = config["SeedUserPW"];

try
{
SeedData.Initialize(services, testUserPw).Wait();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
throw ex;
}
}

host.Run();
}

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


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

Criar as contas de teste e atualizar os contatos


Atualize o método Initialize na classe SeedData para criar as contas de teste:
public static async Task Initialize(IServiceProvider serviceProvider, string testUserPw)
{
using (var context = new ApplicationDbContext(
serviceProvider.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))
{
// For sample purposes we are seeding 2 users both with the same password.
// The password is set with the following command:
// dotnet user-secrets set SeedUserPW <pw>
// The admin user can do anything

var adminID = await EnsureUser(serviceProvider, testUserPw, "admin@contoso.com");


await EnsureRole(serviceProvider, adminID, Constants.ContactAdministratorsRole);

// allowed user can create and edit contacts that they create
var uid = await EnsureUser(serviceProvider, testUserPw, "manager@contoso.com");
await EnsureRole(serviceProvider, uid, Constants.ContactManagersRole);

SeedDB(context, adminID);
}
}

private static async Task<string> EnsureUser(IServiceProvider serviceProvider,


string testUserPw, string UserName)
{
var userManager = serviceProvider.GetService<UserManager<ApplicationUser>>();

var user = await userManager.FindByNameAsync(UserName);


if (user == null)
{
user = new ApplicationUser { UserName = UserName };
await userManager.CreateAsync(user, testUserPw);
}

return user.Id;
}

private static async Task<IdentityResult> EnsureRole(IServiceProvider serviceProvider,


string uid, string role)
{
IdentityResult IR = null;
var roleManager = serviceProvider.GetService<RoleManager<IdentityRole>>();

if (!await roleManager.RoleExistsAsync(role))
{
IR = await roleManager.CreateAsync(new IdentityRole(role));
}

var userManager = serviceProvider.GetService<UserManager<ApplicationUser>>();

var user = await userManager.FindByIdAsync(uid);

IR = await userManager.AddToRoleAsync(user, role);

return IR;
}

Adicione a ID de usuário de administrador e ContactStatus aos contatos. Torne um dos contatos "Enviado" e um
"Rejeitado". Adicione a ID de usuário e o status para todos os contatos. Somente um contato é exibido:
public static void SeedDB(ApplicationDbContext context, string adminID)
{
if (context.Contact.Any())
{
return; // DB has been seeded
}

context.Contact.AddRange(
new Contact
{
Name = "Debra Garcia",
Address = "1234 Main St",
City = "Redmond",
State = "WA",
Zip = "10999",
Email = "debra@example.com",
Status = ContactStatus.Approved,
OwnerID = adminID
},

Criar proprietário, Gerenciador e manipuladores de autorização do


administrador
Criar uma classe ContactIsOwnerAuthorizationHandler na pasta autorização. O
ContactIsOwnerAuthorizationHandler verifica que o usuário atuando em um recurso possui o recurso.
using System.Threading.Tasks;
using ContactManager.Data;
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.AspNetCore.Identity;

namespace ContactManager.Authorization
{
public class ContactIsOwnerAuthorizationHandler
: AuthorizationHandler<OperationAuthorizationRequirement, Contact>
{
UserManager<ApplicationUser> _userManager;

public ContactIsOwnerAuthorizationHandler(UserManager<ApplicationUser>
userManager)
{
_userManager = userManager;
}

protected override Task


HandleRequirementAsync(AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Contact resource)
{
if (context.User == null || resource == null)
{
// Return Task.FromResult(0) if targeting a version of
// .NET Framework older than 4.6:
return Task.CompletedTask;
}

// If we're not asking for CRUD permission, return.

if (requirement.Name != Constants.CreateOperationName &&


requirement.Name != Constants.ReadOperationName &&
requirement.Name != Constants.UpdateOperationName &&
requirement.Name != Constants.DeleteOperationName )
{
return Task.CompletedTask;
}

if (resource.OwnerID == _userManager.GetUserId(context.User))
{
context.Succeed(requirement);
}

return Task.CompletedTask;
}
}
}

O ContactIsOwnerAuthorizationHandler chamadas contexto. Êxito se o usuário autenticado atual é o proprietário


do contato. Manipuladores de autorização geralmente:
Retornar context.Succeed quando os requisitos são atendidos.
Retornar Task.CompletedTask quando os requisitos não atendidos. Task.CompletedTask não é êxito ou falha—
permite que outros manipuladores de autorização executar.
Se você precisar explicitamente falhar, retornar contexto. Falha.
O aplicativo permite que proprietários de contato possam editar/excluir/criar seus dados.
ContactIsOwnerAuthorizationHandler não precisa verificar a operação passada no parâmetro de requisito.
Criar um manipulador de autorização do Gerenciador
Criar uma classe ContactManagerAuthorizationHandler na pasta autorização. O
ContactManagerAuthorizationHandler verifica se o usuário atuando no recurso é um Gerenciador de. Somente os
gerentes podem aprovar ou rejeitar alterações de conteúdo (nova ou alteradas).

using System.Threading.Tasks;
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.AspNetCore.Identity;

namespace ContactManager.Authorization
{
public class ContactManagerAuthorizationHandler :
AuthorizationHandler<OperationAuthorizationRequirement, Contact>
{
protected override Task
HandleRequirementAsync(AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Contact resource)
{
if (context.User == null || resource == null)
{
return Task.CompletedTask;
}

// If not asking for approval/reject, return.


if (requirement.Name != Constants.ApproveOperationName &&
requirement.Name != Constants.RejectOperationName)
{
return Task.CompletedTask;
}

// Managers can approve or reject.


if (context.User.IsInRole(Constants.ContactManagersRole))
{
context.Succeed(requirement);
}

return Task.CompletedTask;
}
}
}

Criar um manipulador de autorização do administrador


Criar uma classe ContactAdministratorsAuthorizationHandler na pasta autorização. O
ContactAdministratorsAuthorizationHandler verifica se o usuário atuando no recurso é um administrador.
Administrador pode fazer todas as operações.
using System.Threading.Tasks;
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;

namespace ContactManager.Authorization
{
public class ContactAdministratorsAuthorizationHandler
: AuthorizationHandler<OperationAuthorizationRequirement, Contact>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Contact resource)
{
if (context.User == null)
{
return Task.CompletedTask;
}

// Administrators can do anything.


if (context.User.IsInRole(Constants.ContactAdministratorsRole))
{
context.Succeed(requirement);
}

return Task.CompletedTask;
}
}
}

Registrar os manipuladores de autorização


Serviços usando o Entity Framework Core devem ser registrados para injeção de dependência usando
AddScoped. O ContactIsOwnerAuthorizationHandler usa o ASP.NET Core identidade, que está incluído no Entity
Framework Core. Registrar os manipuladores com a coleção de serviço para que eles estejam disponíveis para o
ContactsController por meio de injeção de dependência. Adicione o seguinte código ao final da
ConfigureServices :
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

var skipHTTPS = Configuration.GetValue<bool>("LocalTest:skipHTTPS");


// requires using Microsoft.AspNetCore.Mvc;
services.Configure<MvcOptions>(options =>
{
// Set LocalTest:skipHTTPS to true to skip SSL requrement in
// debug mode. This is useful when not using Visual Studio.
if (Environment.IsDevelopment() && !skipHTTPS)
{
options.Filters.Add(new RequireHttpsAttribute());
}
});

services.AddMvc();
//.AddRazorPagesOptions(options =>
//{
// options.Conventions.AuthorizeFolder("/Account/Manage");
// options.Conventions.AuthorizePage("/Account/Logout");
//});

services.AddSingleton<IEmailSender, EmailSender>();

// requires: using Microsoft.AspNetCore.Authorization;


// using Microsoft.AspNetCore.Mvc.Authorization;
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});

// Authorization handlers.
services.AddScoped<IAuthorizationHandler,
ContactIsOwnerAuthorizationHandler>();

services.AddSingleton<IAuthorizationHandler,
ContactAdministratorsAuthorizationHandler>();

services.AddSingleton<IAuthorizationHandler,
ContactManagerAuthorizationHandler>();
}

ContactAdministratorsAuthorizationHandler e ContactManagerAuthorizationHandler são adicionados como


singletons. Elas são singletons porque eles não usam o EF e todas as informações necessárias no Context
parâmetro do HandleRequirementAsync método.

Suporte à autorização
Nesta seção, você atualize as páginas Razor e adicionar uma classe de requisitos de operações.
Examine a classe de requisitos de operações de contato
Examine o ContactOperations classe. Essa classe contém os requisitos de aplicativo suporta:
using Microsoft.AspNetCore.Authorization.Infrastructure;

namespace ContactManager.Authorization
{
public static class ContactOperations
{
public static OperationAuthorizationRequirement Create =
new OperationAuthorizationRequirement {Name=Constants.CreateOperationName};
public static OperationAuthorizationRequirement Read =
new OperationAuthorizationRequirement {Name=Constants.ReadOperationName};
public static OperationAuthorizationRequirement Update =
new OperationAuthorizationRequirement {Name=Constants.UpdateOperationName};
public static OperationAuthorizationRequirement Delete =
new OperationAuthorizationRequirement {Name=Constants.DeleteOperationName};
public static OperationAuthorizationRequirement Approve =
new OperationAuthorizationRequirement {Name=Constants.ApproveOperationName};
public static OperationAuthorizationRequirement Reject =
new OperationAuthorizationRequirement {Name=Constants.RejectOperationName};
}

public class Constants


{
public static readonly string CreateOperationName = "Create";
public static readonly string ReadOperationName = "Read";
public static readonly string UpdateOperationName = "Update";
public static readonly string DeleteOperationName = "Delete";
public static readonly string ApproveOperationName = "Approve";
public static readonly string RejectOperationName = "Reject";

public static readonly string ContactAdministratorsRole =


"ContactAdministrators";
public static readonly string ContactManagersRole = "ContactManagers";
}
}

Criar uma classe base para as páginas Razor


Crie uma classe base que contém os serviços usados em contatos páginas Razor. A classe base coloca esse código
de inicialização em um local:

using ContactManager.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace ContactManager.Pages.Contacts
{
public class DI_BasePageModel : PageModel
{
protected ApplicationDbContext Context { get; }
protected IAuthorizationService AuthorizationService { get; }
protected UserManager<ApplicationUser> UserManager { get; }

public DI_BasePageModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<ApplicationUser> userManager) : base()
{
Context = context;
UserManager = userManager;
AuthorizationService = authorizationService;
}
}
}
O código anterior:
Adiciona o serviço IAuthorizationService para acessar os manipuladores de autorização.
Adiciona o serviço de identidade UserManager .
Adicione a ApplicationDbContext .
Atualizar o CreateModel
Atualize o construtor de criar modelo de página para usar a classe base DI_BasePageModel :

public class CreateModel : DI_BasePageModel


{
public CreateModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<ApplicationUser> userManager)
: base(context, authorizationService, userManager)
{
}

Atualize o método CreateModel.OnPostAsync para:


Adicione a ID de usuário ao modelo Contact .
Chama o manipulador de autorização para verificar se que o usuário tem permissão para criar contatos.

public async Task<IActionResult> OnPostAsync()


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

Contact.OwnerID = UserManager.GetUserId(User);

// requires using ContactManager.Authorization;


var isAuthorized = await AuthorizationService.AuthorizeAsync(
User, Contact,
ContactOperations.Create);
if (!isAuthorized.Succeeded)
{
return new ChallengeResult();
}

Context.Contact.Add(Contact);
await Context.SaveChangesAsync();

return RedirectToPage("./Index");
}

Atualizar o IndexModel
Atualize o método OnGetAsync para que apenas os contatos aprovados sejam mostrados aos usuários gerais:
public class IndexModel : DI_BasePageModel
{
public IndexModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<ApplicationUser> userManager)
: base(context, authorizationService, userManager)
{
}

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

public async Task OnGetAsync()


{
var contacts = from c in Context.Contact
select c;

var isAuthorized = User.IsInRole(Constants.ContactManagersRole) ||


User.IsInRole(Constants.ContactAdministratorsRole);

var currentUserId = UserManager.GetUserId(User);

// Only approved contacts are shown UNLESS you're authorized to see them
// or you are the owner.
if (!isAuthorized)
{
contacts = contacts.Where(c => c.Status == ContactStatus.Approved
|| c.OwnerID == currentUserId);
}

Contact = await contacts.ToListAsync();


}
}

Atualizar o EditModel
Adicione um manipulador de autorização para verificar se que o usuário possui o contato. Como a autorização de
recursos está sendo validada, o [Authorize] atributo não é suficiente. O aplicativo não tiver acesso ao recurso
quando atributos são avaliados. Autorização baseada em recursos deve ser obrigatória. Verificações de devem ser
executadas depois que o aplicativo tenha acesso ao recurso, carregá-lo no modelo de página ou carregá-lo dentro
do manipulador de si mesmo. Frequentemente, você acessar o recurso, passando a chave de recurso.

public class EditModel : DI_BasePageModel


{
public EditModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<ApplicationUser> userManager)
: base(context, authorizationService, userManager)
{
}

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

public async Task<IActionResult> OnGetAsync(int id)


{
Contact = await Context.Contact.FirstOrDefaultAsync(
m => m.ContactId == id);

if (Contact == null)
{
return NotFound();
}
var isAuthorized = await AuthorizationService.AuthorizeAsync(
User, Contact,
ContactOperations.Update);
if (!isAuthorized.Succeeded)
{
return new ChallengeResult();
}

return Page();
}

public async Task<IActionResult> OnPostAsync(int id)


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

// Fetch Contact from DB to get OwnerID.


var contact = await Context
.Contact.AsNoTracking()
.FirstOrDefaultAsync(m => m.ContactId == id);

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

var isAuthorized = await AuthorizationService.AuthorizeAsync(


User, contact,
ContactOperations.Update);
if (!isAuthorized.Succeeded)
{
return new ChallengeResult();
}

Contact.OwnerID = contact.OwnerID;

Context.Attach(Contact).State = EntityState.Modified;

if (contact.Status == ContactStatus.Approved)
{
// If the contact is updated after approval,
// and the user cannot approve,
// set the status back to submitted so the update can be
// checked and approved.
var canApprove = await AuthorizationService.AuthorizeAsync(User,
contact,
ContactOperations.Approve);

if (!canApprove.Succeeded)
{
contact.Status = ContactStatus.Submitted;
}
}

await Context.SaveChangesAsync();

return RedirectToPage("./Index");
}

private bool ContactExists(int id)


{
return Context.Contact.Any(e => e.ContactId == id);
}
}
Atualizar o DeleteModel
Atualize o modelo de página de exclusão para usar o manipulador de autorização para verificar se que o usuário
tem permissão para excluir no contato.

public class DeleteModel : DI_BasePageModel


{
public DeleteModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<ApplicationUser> userManager)
: base(context, authorizationService, userManager)
{
}

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

public async Task<IActionResult> OnGetAsync(int id)


{
Contact = await Context.Contact.FirstOrDefaultAsync(
m => m.ContactId == id);

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

var isAuthorized = await AuthorizationService.AuthorizeAsync(


User, Contact,
ContactOperations.Delete);
if (!isAuthorized.Succeeded)
{
return new ChallengeResult();
}

return Page();
}

public async Task<IActionResult> OnPostAsync(int id)


{
Contact = await Context.Contact.FindAsync(id);

var contact = await Context


.Contact.AsNoTracking()
.FirstOrDefaultAsync(m => m.ContactId == id);

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

var isAuthorized = await AuthorizationService.AuthorizeAsync(


User, contact,
ContactOperations.Delete);
if (!isAuthorized.Succeeded)
{
return new ChallengeResult();
}

Context.Contact.Remove(Contact);
await Context.SaveChangesAsync();

return RedirectToPage("./Index");
}
}
Injetar o serviço de autorização para os modos de exibição
No momento, a interface do usuário mostra editar e excluir links de dados que o usuário não pode modificar. A
interface do usuário é fixo, aplicando o manipulador de autorização para os modos de exibição.
Injetar o serviço de autorização no arquivo Views/_ViewImports.cshtml para que ele esteja disponível para todos
os modos de exibição:

@using Microsoft.AspNetCore.Identity
@using ContactManager
@using ContactManager.Data
@namespace ContactManager.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using ContactManager.Authorization;
@using Microsoft.AspNetCore.Authorization
@using ContactManager.Models
@inject IAuthorizationService AuthorizationService

A marcação anterior adiciona várias using instruções.


Atualização de editar e excluir links em Pages/Contacts/Index.cshtml para elas estão renderizadas somente para
usuários com as permissões apropriadas:

@page
@model ContactManager.Pages.Contacts.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.Contact[0].Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].Address)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].City)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].State)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].Zip)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].Email)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].Status)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Contact)
{
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Address)
</td>
<td>
@Html.DisplayFor(modelItem => item.City)
</td>
<td>
@Html.DisplayFor(modelItem => item.State)
</td>
<td>
@Html.DisplayFor(modelItem => item.Zip)
</td>

<td>
@Html.DisplayFor(modelItem => item.Email)
</td>

<td>
@Html.DisplayFor(modelItem => item.Status)
</td>
<td>
@if ((await AuthorizationService.AuthorizeAsync(
User, item,
ContactOperations.Update)).Succeeded)
{
<a asp-page="./Edit" asp-route-id="@item.ContactId">Edit</a>
<text> | </text>
}

<a asp-page="./Details" asp-route-id="@item.ContactId">Details</a>

@if ((await AuthorizationService.AuthorizeAsync(


User, item,
ContactOperations.Delete)).Succeeded)
{
<text> | </text>
<a asp-page="./Delete" asp-route-id="@item.ContactId">Delete</a>
}
</td>
</tr>
}
</tbody>
</table>

AVISO
Ocultar links de usuários que não tem permissão para alterar os dados, o aplicativo não seguro. Ocultar links torna o
aplicativo mais amigável exibindo links só é válidas. Os usuários podem hack as URLs geradas para chamar editar e excluir
operações nos dados que não possuem. O Razor de página ou o controlador deve impor verificações de acesso para
proteger os dados.

Detalhes da atualização
Atualize a exibição de detalhes para que os gerentes possam aprovar ou rejeitar contatos:
@*Precedng markup omitted for brevity.*@

<dd>
@Html.DisplayFor(model => model.Contact.Email)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Contact.Status)
</dt>
<dd>
@Html.DisplayFor(model => model.Contact.Status)
</dd>
</dl>
</div>

@if (Model.Contact.Status != ContactStatus.Approved)


{
@if ((await AuthorizationService.AuthorizeAsync(
User, Model.Contact, ContactOperations.Approve)).Succeeded)
{
<form style="display:inline;" method="post">
<input type="hidden" name="id" value="@Model.Contact.ContactId" />
<input type="hidden" name="status" value="@ContactStatus.Approved" />
<button type="submit" class="btn btn-xs btn-success">Approve</button>
</form>
}
}

@if (Model.Contact.Status != ContactStatus.Rejected)


{
@if ((await AuthorizationService.AuthorizeAsync(
User, Model.Contact, ContactOperations.Reject)).Succeeded)
{
<form style="display:inline;" method="post">
<input type="hidden" name="id" value="@Model.Contact.ContactId" />
<input type="hidden" name="status" value="@ContactStatus.Rejected" />
<button type="submit" class="btn btn-xs btn-success">Reject</button>
</form>
}
}

<div>
@if ((await AuthorizationService.AuthorizeAsync(
User, Model.Contact,
ContactOperations.Update)).Succeeded)
{
<a asp-page="./Edit" asp-route-id="@Model.Contact.ContactId">Edit</a>
<text> | </text>
}
<a asp-page="./Index">Back to List</a>
</div>

Atualize o modelo de página de detalhes:


public class DetailsModel : DI_BasePageModel
{
public DetailsModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<ApplicationUser> userManager)
: base(context, authorizationService, userManager)
{
}

public Contact Contact { get; set; }

public async Task<IActionResult> OnGetAsync(int id)


{
Contact = await Context.Contact.FirstOrDefaultAsync(m => m.ContactId == id);

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

public async Task<IActionResult> OnPostAsync(int id, ContactStatus status)


{
var contact = await Context.Contact.FirstOrDefaultAsync(
m => m.ContactId == id);

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

var contactOperation = (status == ContactStatus.Approved)


? ContactOperations.Approve
: ContactOperations.Reject;

var isAuthorized = await AuthorizationService.AuthorizeAsync(User, contact,


contactOperation);
if (!isAuthorized.Succeeded)
{
return new ChallengeResult();
}
contact.Status = status;
Context.Contact.Update(contact);
await Context.SaveChangesAsync();

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

Testar o aplicativo concluído


Se você estiver usando o Visual Studio Code ou testando em uma plataforma local que não inclui um certificado
de teste para HTTPS:
Defina "LocalTest:skipSSL": true no arquivo appsettings. Developement.JSON. Skip HTTPS somente em um
computador de desenvolvimento.
Se o aplicativo tiver contatos:
Excluir todos os registros da tabela Contact .
Reinicie o aplicativo para propagar o banco de dados.
Registre um usuário para procurar os contatos.
Uma maneira fácil de testar o aplicativo concluído é iniciar três navegadores diferentes (ou versões de
incógnita/InPrivate). Em um navegador, registrar um novo usuário (por exemplo, test@example.com ). Entrar para
cada localizador com um usuário diferente. Verifique se as seguintes operações:
Os usuários registrados podem exibir todos os dados de contato aprovados.
Os usuários registrados podem editar/excluir seus próprios dados.
Os gerentes podem aprovar ou rejeitar contatos. A tela Details mostra os botões aprovar e rejeitar.
Os administradores podem Aprovar/rejeitar e editar/excluir todos os dados.

USER OPÇÕES

test@example.com Pode editar/excluir próprios dados

manager@contoso.com Rejeitar/aprovar e editar/excluir podem ter dados

admin@contoso.com Pode editar/excluir e Aprovar/rejeitar todos os dados

Crie um contato no navegador do administrador. Copie a URL para excluir e editar o contato de administrador.
Cole esses links no navegador do usuário de teste para verificar se que o usuário de teste não pode executar essas
operações.

Criar o aplicativo de início


Criar um aplicativo de páginas Razor denominado "ContactManager"
Criar o aplicativo com contas de usuário individuais.
Coloque o nome de "ContactManager" para que o namespace coincida com o namespace usado no
exemplo.

dotnet new razor -o ContactManager -au Individual -uld

Especifica o LocalDB, em vez de SQLite


-uld
Adicionar o seguinte modelo Contact :

public class Contact


{
public int ContactId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
}

Fazer Scaffold do modelo Contact :

dotnet aspnet-codegenerator razorpage -m Contact -udl -dc ApplicationDbContext -outDir Pages\Contacts --


referenceScriptLibraries

Atualizar o link de ContactManager no arquivo Pages/_Layout.cshtml:


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

Fazer Scaffold da migração inicial e atualizar o banco de dados:

dotnet ef migrations add initial


dotnet ef database update

Testar o aplicativo de criação, edição e exclusão de um contato


Propagar o banco de dados
Adicionar o SeedData de classe para o dados pasta. Se você baixou o exemplo, você pode copiar o SeedData.cs o
arquivo para o dados pasta do projeto starter.
Chamar SeedData.Initialize de Main :

public class Program


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

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


{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<ApplicationDbContext>();
context.Database.Migrate();

try
{
SeedData.Initialize(services, "").Wait();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
throw ex;
}
}

host.Run();
}

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


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

Teste o aplicativo propagado o banco de dados. Se houver linhas no banco de dados de contato, o método de
propagação não é executado.
Recursos adicionais
Laboratório de autorização de ASP.NET Core. Este laboratório apresenta mais detalhes sobre os recursos de
segurança introduzidos neste tutorial.
Autorização no ASP.NET Core: Simple, função, baseada em declarações e personalizada
Autorização baseada em política personalizada
Convenções de autorização de páginas Razor do
ASP.NET Core
03/03/2018 • 3 min to read • Edit Online

Por Luke Latham


Uma maneira de controlar o acesso em seu aplicativo de páginas Razor é usar as convenções de autorização na
inicialização. As seguintes convenções permitem que você autorizar os usuários e permitir que usuários anônimos
acessem páginas individuais ou pastas de páginas. Aplicam as convenções descritas neste tópico
automaticamente filtros de autorização para controlar o acesso.
Exibir ou baixar código de exemplo (como baixar)

Exigir autorização para acessar uma página


Use o AuthorizePage convenção via AddRazorPagesOptions para adicionar um AuthorizeFilter para a página no
caminho especificado:

services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/Contact");
options.Conventions.AuthorizeFolder("/Private");
options.Conventions.AllowAnonymousToPage("/Private/PublicPage");
options.Conventions.AllowAnonymousToFolder("/Private/PublicPages");
});

O caminho especificado é o caminho do mecanismo de exibição, que é o caminho relativo do Razor páginas raiz
sem uma extensão e contendo apenas barras invertidas.
Um AuthorizePage sobrecarga estará disponível se você precisa especificar uma política de autorização.

Exigir autorização para acessar uma pasta de páginas


Use o AuthorizeFolder convenção via AddRazorPagesOptions para adicionar um AuthorizeFilter para todas as
páginas em uma pasta no caminho especificado:

services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/Contact");
options.Conventions.AuthorizeFolder("/Private");
options.Conventions.AllowAnonymousToPage("/Private/PublicPage");
options.Conventions.AllowAnonymousToFolder("/Private/PublicPages");
});

O caminho especificado é o caminho do mecanismo de exibição, que é o caminho relativo de raiz de páginas
Razor.
Um AuthorizeFolder sobrecarga estará disponível se você precisa especificar uma política de autorização.

Permitir acesso anônimo a uma página


Use o AllowAnonymousToPage convenção via AddRazorPagesOptions para adicionar um AllowAnonymousFilter
para uma página no caminho especificado:

services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/Contact");
options.Conventions.AuthorizeFolder("/Private");
options.Conventions.AllowAnonymousToPage("/Private/PublicPage");
options.Conventions.AllowAnonymousToFolder("/Private/PublicPages");
});

O caminho especificado é o caminho do mecanismo de exibição, que é o caminho relativo do Razor páginas raiz
sem uma extensão e contendo apenas barras invertidas.

Permitir o acesso anônimo para uma pasta de páginas


Use o AllowAnonymousToFolder convenção via AddRazorPagesOptions para adicionar um
AllowAnonymousFilter para todas as páginas em uma pasta no caminho especificado:

services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/Contact");
options.Conventions.AuthorizeFolder("/Private");
options.Conventions.AllowAnonymousToPage("/Private/PublicPage");
options.Conventions.AllowAnonymousToFolder("/Private/PublicPages");
});

O caminho especificado é o caminho do mecanismo de exibição, que é o caminho relativo de raiz de páginas
Razor.

Observe na combinação de autorização e acesso anônimo


É perfeitamente válido para especificar que uma pasta de páginas exigem autorização e especifique que uma
página dentro da pasta permite acesso anônimo:

// This works.
.AuthorizeFolder("/Private").AllowAnonymousToPage("/Private/Public")

O inverso, no entanto, não é true. Você não pode declarar uma pasta de páginas para acesso anônimo e
especificar uma página dentro de autorização:

// This doesn't work!


.AllowAnonymousToFolder("/Public").AuthorizePage("/Public/Private")

Exigir autorização na página privada não funcionará porque quando tanto o AllowAnonymousFilter e
AuthorizeFilter filtros são aplicados para a página, o AllowAnonymousFilter wins e controla o acesso.

Consulte também
Provedores de modelo personalizado de página e rota de Páginas Razor
PageConventionCollection classe
Simples de autorização no núcleo do ASP.NET
10/04/2018 • 2 min to read • Edit Online

No MVC é controlada por meio de AuthorizeAttribute atributo e seus vários parâmetros. Em sua forma mais
simples, aplicando o AuthorizeAttribute de atributo para um controlador ou ação limites acesse o controlador
ou ação para qualquer usuário autenticado.
Por exemplo, o código a seguir limita o acesso para o AccountController para qualquer usuário autenticado.

[Authorize]
public class AccountController : Controller
{
public ActionResult Login()
{
}

public ActionResult Logout()


{
}
}

Se você deseja aplicar a autorização para uma ação em vez do controlador, aplique a AuthorizeAttribute de
atributo para a ação em si:

public class AccountController : Controller


{
public ActionResult Login()
{
}

[Authorize]
public ActionResult Logout()
{
}
}

Agora, somente usuários autenticados podem acessar o Logout função.


Você também pode usar o AllowAnonymous atributo para permitir o acesso por usuários não autenticados para
ações individuais. Por exemplo:

[Authorize]
public class AccountController : Controller
{
[AllowAnonymous]
public ActionResult Login()
{
}

public ActionResult Logout()


{
}
}

Isso permitiria que somente usuários autenticados para o AccountController , exceto para o Login ação, que
pode ser acessada por todos os usuários, independentemente de seu status de autenticado ou anônimo / não
autenticado.

AVISO
[AllowAnonymous] Ignora todas as declarações de autorização. Se você aplicar combinar [AllowAnonymous] e qualquer
[Authorize] atributo e os atributos de autorizar sempre serão ignorados. Por exemplo, se você aplicar
[AllowAnonymous] no controlador de nível qualquer [Authorize] atributos no mesmo controlador, ou em qualquer
ação dentro dele serão ignorados.
Autorização baseada em função no núcleo do
ASP.NET
04/05/2018 • 3 min to read • Edit Online

Quando uma identidade é criada ele pode pertencer a uma ou mais funções. Por exemplo, Tânia pode pertencer a
funções de administrador e usuário embora Scott só pode pertencer à função de usuário. Como essas funções
são criadas e gerenciadas dependem do armazenamento de backup do processo de autorização. Funções são
expostas para o desenvolvedor de IsInRole método o ClaimsPrincipal classe.

Adicionando verificações de função


Verificações de autorização baseada em função são declarativas—o desenvolvedor incorpora dentro de seu
código, em relação a um controlador ou uma ação dentro de um controlador, especificando funções que o usuário
atual deve ser um membro de acessar o recurso solicitado.
Por exemplo, o código a seguir limita o acesso a todas as ações no AdministrationController para os usuários
que são membros do Administrator função:

[Authorize(Roles = "Administrator")]
public class AdministrationController : Controller
{
}

Você pode especificar várias funções como uma lista separada por vírgulas:

[Authorize(Roles = "HRManager,Finance")]
public class SalaryController : Controller
{
}

Esse controlador seria somente acessível por usuários que são membros do HRManager função ou o Finance
função.
Se você aplicar vários atributos de um usuário ao acessar deve ser um membro de todas as funções especificadas;
o exemplo a seguir exige que um usuário deve ser um membro de ambos os PowerUser e ControlPanelUser
função.

[Authorize(Roles = "PowerUser")]
[Authorize(Roles = "ControlPanelUser")]
public class ControlPanelController : Controller
{
}

Você pode limitar o acesso mais aplicando atributos de autorização de função adicionais no nível da ação:
[Authorize(Roles = "Administrator, PowerUser")]
public class ControlPanelController : Controller
{
public ActionResult SetTime()
{
}

[Authorize(Roles = "Administrator")]
public ActionResult ShutDown()
{
}
}

Em membros de trecho de código anterior do Administrator função ou o PowerUser função pode acessar o
controlador e o SetTime ação, mas somente os membros do Administrator função pode acessar o ShutDown
ação.
Você também pode bloquear um controlador mas permitir o acesso anônimo, não autenticado para ações
individuais.

[Authorize]
public class ControlPanelController : Controller
{
public ActionResult SetTime()
{
}

[AllowAnonymous]
public ActionResult Login()
{
}
}

Verificações de função baseada em política


Requisitos da função também podem ser expressos usando a nova sintaxe de diretiva, onde um desenvolvedor
registra uma política na inicialização como parte da configuração do serviço de autorização. Isso normalmente
ocorre em ConfigureServices() no seu Startup.cs arquivo.

public void ConfigureServices(IServiceCollection services)


{
services.AddMvc();

services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdministratorRole", policy => policy.RequireRole("Administrator"));
});
}

As políticas são aplicadas usando o Policy propriedade o AuthorizeAttribute atributo:

[Authorize(Policy = "RequireAdministratorRole")]
public IActionResult Shutdown()
{
return View();
}

Se desejar especificar várias funções permitidas em um requisito, você pode especificá-los como parâmetros para
o RequireRole método:

options.AddPolicy("ElevatedRights", policy =>


policy.RequireRole("Administrator", "PowerUser", "BackupAdministrator"));

Este exemplo autoriza a usuários que pertencem ao Administrator , PowerUser ou BackupAdministrator funções.
Autorização baseada em declarações no núcleo do
ASP.NET
10/04/2018 • 5 min to read • Edit Online

Quando uma identidade é criada ele pode ser atribuído uma ou mais declarações emitidas por um terceiro
confiável. Uma declaração é um par nome-valor que representa o assunto, não que a entidade pode fazer. Por
exemplo, você pode ter de motorista uma carteira, emitida por uma autoridade de licença de um local. Licença do
driver tem sua data de nascimento. Nesse caso seria o nome da declaração DateOfBirth , o valor da declaração
seria sua data de nascimento, por exemplo 8th June 1970 e o emissor de um autoridade de licença. Autorização
baseada em declarações, em sua forma mais simples, verifica o valor de uma declaração e permite o acesso a um
recurso com base no valor. Por exemplo, se você quiser que o processo de autorização de acesso para uma
sociedade noite poderia ser:
A analista de segurança de porta deve avaliar o valor da sua data de nascimento declaração e se elas têm
confiança do emissor (de um autoridade de licença) antes de conceder a que você acessar.
Uma identidade pode conter várias declarações com vários valores e pode conter várias declarações do mesmo
tipo.

Adicionar declarações verificações


Declaração de verificações de autorização com base são declarativas - o desenvolvedor incorpora dentro de seu
código, em relação a um controlador ou uma ação dentro de um controlador, especificando que o usuário atual
deve ter, e, opcionalmente, o valor de declaração deve conter para acessar o recurso solicitado. Declarações de
requisitos são baseada em política, o desenvolvedor deve criar e registrar uma política de expressar os requisitos
de declarações.
O tipo mais simples de política procura a presença de uma declaração de declaração e não verifica o valor.
Primeiro você precisa criar e registrar a política. Isso ocorre como parte da configuração do serviço de
autorização, que normalmente faz parte do ConfigureServices() no seu Startup.cs arquivo.

public void ConfigureServices(IServiceCollection services)


{
services.AddMvc();

services.AddAuthorization(options =>
{
options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
});
}

Nesse caso o EmployeeOnly política verifica a presença de um EmployeeNumber de declaração de identidade atual.
Em seguida, aplique a política usando o Policy propriedade no AuthorizeAttribute atributo para especificar o
nome da política
[Authorize(Policy = "EmployeeOnly")]
public IActionResult VacationBalance()
{
return View();
}

O AuthorizeAttribute atributo pode ser aplicado a um controlador de inteiro, nesta instância, somente a política
de correspondência de identidades terão acesso permitidas para qualquer ação no controlador.

[Authorize(Policy = "EmployeeOnly")]
public class VacationController : Controller
{
public ActionResult VacationBalance()
{
}
}

Se você tiver um controlador que é protegido pelo AuthorizeAttribute de atributo, mas deseja permitir acesso
anônimo a ações específicas que você aplicar o AllowAnonymousAttribute atributo.

[Authorize(Policy = "EmployeeOnly")]
public class VacationController : Controller
{
public ActionResult VacationBalance()
{
}

[AllowAnonymous]
public ActionResult VacationPolicy()
{
}
}

A maioria das declarações vem com um valor. Você pode especificar uma lista de valores permitido ao criar a
política. O exemplo a seguir seria êxito apenas para os funcionários cujo número de funcionário foi 1, 2, 3, 4 ou 5.

public void ConfigureServices(IServiceCollection services)


{
services.AddMvc();

services.AddAuthorization(options =>
{
options.AddPolicy("Founders", policy =>
policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
});
}

Vários avaliação de política


Se você aplicar várias políticas para um controlador ou ação, todas as políticas devem passar antes que o acesso é
concedido. Por exemplo:
[Authorize(Policy = "EmployeeOnly")]
public class SalaryController : Controller
{
public ActionResult Payslip()
{
}

[Authorize(Policy = "HumanResources")]
public ActionResult UpdateSalary()
{
}
}

No exemplo acima qualquer identidade que atende a EmployeeOnly política pode acessar o Payslip ação como
essa política é aplicada no controlador. No entanto para chamar o UpdateSalary ação de identidade deve ser
atendidos ambos o EmployeeOnly política e o HumanResources política.
Se você quiser políticas mais complicadas, como colocar uma data de nascimento declaração, calcular uma idade
dele e verificando a idade for 21 ou anterior, você precisa gravar manipuladores de política personalizada.
Autorização baseada em política no ASP.NET Core
10/04/2018 • 11 min to read • Edit Online

Nos bastidores, autorização baseada em função e autorização baseada em declarações usam um requisito, um
manipulador de requisito e uma política de pré-configurada. Esses blocos de construção oferecem suporte a
expressão de avaliações de autorização no código. O resultado é uma estrutura de autorização mais sofisticados,
reutilizáveis e testáveis.
Uma política de autorização consiste em um ou mais requisitos. Ele é registrado como parte da configuração do
serviço de autorização no Startup.ConfigureServices método:

public void ConfigureServices(IServiceCollection services)


{
services.AddMvc();

services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
});
}

No exemplo anterior, uma política de "AtLeast21" é criada. Ele tem um requisito—de idade mínima, que é
fornecida como um parâmetro para o requisito.
As políticas são aplicadas usando o [Authorize] atributo com o nome da política. Por exemplo:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseController : Controller
{
public IActionResult Login() => View();

public IActionResult Logout() => View();


}

Requisitos
Um requisito de autorização é uma coleção de parâmetros de dados que uma política pode usar para avaliar a
entidade de segurança do usuário atual. Em nossa política de "AtLeast21", o requisito é um único parâmetro—a
idade mínima. Implementa um requisito IAuthorizationRequirement, que é uma interface de marcador vazio.
Um requisito de idade mínima com parâmetros pode ser implementado da seguinte maneira:
using Microsoft.AspNetCore.Authorization;

public class MinimumAgeRequirement : IAuthorizationRequirement


{
public int MinimumAge { get; private set; }

public MinimumAgeRequirement(int minimumAge)


{
MinimumAge = minimumAge;
}
}

OBSERVAÇÃO
Um requisito não precisa ter dados ou propriedades.

Manipuladores de autorização
Um manipulador de autorização é responsável pela avaliação de propriedades de um requisito. O manipulador
de autorização avalia os requisitos em relação a um fornecido AuthorizationHandlerContext para determinar se
o acesso é permitido.
Pode ter um requisito vários manipuladores. Um manipulador pode herdar
AuthorizationHandler<TRequirement >, onde TRequirement é o requisito para ser tratada. Como alternativa, um
manipulador pode implementar IAuthorizationHandler para lidar com mais de um tipo de requisito.
Usar um manipulador de um requisito
Este é um exemplo de uma relação um para um no qual um manipulador de idade mínima utiliza um requisito:
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;
using System;
using System.Security.Claims;
using System.Threading.Tasks;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>


{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth &&
c.Issuer == "http://contoso.com"))
{
//TODO: Use the following if targeting a version of
//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}

var dateOfBirth = Convert.ToDateTime(


context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth &&
c.Issuer == "http://contoso.com").Value);

int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;


if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
{
calculatedAge--;
}

if (calculatedAge >= requirement.MinimumAge)


{
context.Succeed(requirement);
}

//TODO: Use the following if targeting a version of


//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
}

O código anterior determina se a entidade de usuário atual tem uma data de nascimento de declaração que foi
emitido por um emissor confiável e conhecido. A autorização não pode ocorrer quando a declaração está
ausente, caso em que uma tarefa concluída é retornada. Quando uma declaração estiver presente, a duração do
usuário é calculada. Se o usuário atende a idade mínima definida pelo requisito de autorização é considerada
bem-sucedido. Quando a autorização for bem-sucedido, context.Succeed é invocado com o requisito satisfeito
como seu único parâmetro.
Usar um manipulador para várias necessidades
Este é um exemplo de uma relação um-para-muitos em que um manipulador de permissão utiliza três
requisitos:
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

public class PermissionHandler : IAuthorizationHandler


{
public Task HandleAsync(AuthorizationHandlerContext context)
{
var pendingRequirements = context.PendingRequirements.ToList();

foreach (var requirement in pendingRequirements)


{
if (requirement is ReadPermission)
{
if (IsOwner(context.User, context.Resource) ||
IsSponsor(context.User, context.Resource))
{
context.Succeed(requirement);
}
}
else if (requirement is EditPermission ||
requirement is DeletePermission)
{
if (IsOwner(context.User, context.Resource))
{
context.Succeed(requirement);
}
}
}

//TODO: Use the following if targeting a version of


//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}

private bool IsOwner(ClaimsPrincipal user, object resource)


{
// Code omitted for brevity

return true;
}

private bool IsSponsor(ClaimsPrincipal user, object resource)


{
// Code omitted for brevity

return true;
}
}

O código anterior atravessa PendingRequirements—uma propriedade que contém requisitos não marcada
como bem-sucedido. Se o usuário tem permissão de leitura, ele deve ser um proprietário ou um patrocinador
para acessar o recurso solicitado. Se o usuário editar ou excluir permissão, deve ser um proprietário para acessar
o recurso solicitado. Quando a autorização for bem-sucedido, context.Succeed é invocado com o requisito
satisfeito como seu único parâmetro.
Registro do manipulador
Manipuladores são registrados na coleção de serviços durante a configuração. Por exemplo:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();

services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
});

services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
}

Cada manipulador é adicionado à coleção de serviços invocando


services.AddSingleton<IAuthorizationHandler, YourHandlerClass>(); .

O que deve retornar um manipulador?


Observe que o Handle método o exemplo manipulador não retorna nenhum valor. Como é um status de êxito
ou falha indicado?
Um manipulador indica êxito chamando context.Succeed(IAuthorizationRequirement requirement) ,
passando o requisito de que foi validada com êxito.
Um manipulador não precisa lidar com falhas em geral, como outros manipuladores para o mesmo
requisito podem ser bem-sucedida.
Para garantir a falha, mesmo se outros manipuladores de requisito tenha êxito, chame context.Fail .

Quando definido como false , o InvokeHandlersAfterFailure propriedade (disponível no ASP.NET Core 1.1 e
posterior) reduz a execução de manipuladores quando context.Fail é chamado. InvokeHandlersAfterFailure o
padrão será a true , caso em que todos os manipuladores são chamados. Isso permite que os requisitos
produzir efeitos colaterais, como o registro em log, que sempre ocorrem mesmo se context.Fail foi chamado
no manipulador de outro.

Por que eu quero vários manipuladores para um requisito?


Em casos onde você deseja evaluation para estar em um ou base, implementar vários manipuladores para um
requisito. Por exemplo, a Microsoft tem portas que apenas abrir com cartões de chave. Se você deixar o seu
cartão de chave em casa, o recepcionista imprime uma etiqueta temporária e abre a porta para você. Nesse
cenário, você teria um requisito, BuildingEntry, mas vários manipuladores, cada um deles examinando um
requisito.
BuildingEntryRequirement.cs

using Microsoft.AspNetCore.Authorization;

public class BuildingEntryRequirement : IAuthorizationRequirement


{
}

BadgeEntryHandler.cs
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;
using System.Security.Claims;
using System.Threading.Tasks;

public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>


{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
BuildingEntryRequirement requirement)
{
if (context.User.HasClaim(c => c.Type == ClaimTypes.BadgeId &&
c.Issuer == "http://microsoftsecurity"))
{
context.Succeed(requirement);
}

//TODO: Use the following if targeting a version of


//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
}

TemporaryStickerHandler.cs

using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;
using System.Security.Claims;
using System.Threading.Tasks;

public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>


{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
BuildingEntryRequirement requirement)
{
if (context.User.HasClaim(c => c.Type == ClaimTypes.TemporaryBadgeId &&
c.Issuer == "https://microsoftsecurity"))
{
// We'd also check the expiration date on the sticker.
context.Succeed(requirement);
}

//TODO: Use the following if targeting a version of


//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
}

Certifique-se de que ambos os manipuladores são registrado. Se o manipulador é bem-sucedida quando uma
política avalia o BuildingEntryRequirement , a avaliação de política for bem-sucedida.

Usando uma função para atender a uma política


Pode haver situações nas quais que atendem a uma política é simple de expressar em código. É possível fornecer
uma Func<AuthorizationHandlerContext, bool> ao configurar a política com o RequireAssertion criador de
políticas.
Por exemplo, o anterior BadgeEntryHandler poderia ser reescrito da seguinte maneira:
services.AddAuthorization(options =>
{
options.AddPolicy("BadgeEntry", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(c =>
(c.Type == ClaimTypes.BadgeId ||
c.Type == ClaimTypes.TemporaryBadgeId) &&
c.Issuer == "https://microsoftsecurity")));
});

Acessando o contexto de solicitação do MVC em manipuladores


O HandleRequirementAsync método implementar um manipulador de autorização tem dois parâmetros: um
AuthorizationHandlerContext e o TRequirement manipulada. Estruturas como MVC ou Jabbr serão livres para
adicionar qualquer objeto para o Resource propriedade o AuthorizationHandlerContext para passar informações
adicionais.
Por exemplo, o MVC passa uma instância de AuthorizationFilterContext no Resource propriedade. Esta
propriedade fornece acesso a HttpContext , RouteData e tudo pessoa forneceu MVC e páginas Razor.
O uso do Resource é de propriedade específicos da estrutura. Usando informações de Resource propriedade
limita suas políticas de autorização para estruturas específicas. Você deve converter o Resource propriedade
usando o as palavra-chave e, em seguida, confirme a conversão tenha êxito para garantir que seu código não
falha com um InvalidCastException quando executado em outras estruturas:

// Requires the following import:


// using Microsoft.AspNetCore.Mvc.Filters;
if (context.Resource is AuthorizationFilterContext mvcContext)
{
// Examine MVC-specific things like routing data.
}
Injeção de dependência em manipuladores de
requisito no núcleo do ASP.NET
10/04/2018 • 1 min to read • Edit Online

Manipuladores de autorização devem ser registrados na coleção durante a configuração do serviço (usando
injeção de dependência).
Suponha que você tenha um repositório de regras que você deseja avaliar dentro de um manipulador de
autorização e esse repositório foi registrado na coleção de serviço. A autorização será resolver e injetar que seu
construtor.
Por exemplo, se você quiser usar o ASP. NET do log de infraestrutura que você deseja inserir ILoggerFactory para
o manipulador. Tal um manipulador pode parecer com:

public class LoggingAuthorizationHandler : AuthorizationHandler<MyRequirement>


{
ILogger _logger;

public LoggingAuthorizationHandler(ILoggerFactory loggerFactory)


{
_logger = loggerFactory.CreateLogger(this.GetType().FullName);
}

protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement


requirement)
{
_logger.LogInformation("Inside my handler");
// Check if the requirement is fulfilled.
return Task.CompletedTask;
}
}

Você deve registrar o manipulador com services.AddSingleton() :

services.AddSingleton<IAuthorizationHandler, LoggingAuthorizationHandler>();

Uma instância do manipulador será criado quando o aplicativo é iniciado e será DI injetar registrado
ILoggerFactory para seu construtor.

OBSERVAÇÃO
Manipuladores que usam o Entity Framework não devem ser registrados como singletons.
Autorização baseada em recursos no ASP.NET Core
10/04/2018 • 8 min to read • Edit Online

Estratégia de autorização depende do recurso que está sendo acessado. Considere a possibilidade de um
documento que tem uma propriedade de autor. Somente o autor tem permissão para atualizar o documento.
Consequentemente, o documento deve ser recuperado do armazenamento de dados antes de avaliação de
autorização pode ocorrer.
Avaliação do atributo ocorre antes da associação de dados e antes da execução do manipulador de página ou ação
que carrega o documento. Por esses motivos, autorização declarativa com um [Authorize] atributo não é
suficiente. Em vez disso, você pode chamar um método de autorização personalizada—um estilo conhecido como
autorização obrigatória.
Use o aplicativos de exemplo (como baixar) para explorar os recursos descritos neste tópico.
Criar um aplicativo do ASP.NET Core com dados de usuário protegidos por autorização contém um aplicativo de
exemplo que usa a autorização baseada em recursos.

Usar autorização obrigatória


Autorização é implementada como um IAuthorizationService de serviço e é registrada na coleção de serviço
dentro de Startup classe. O serviço é disponibilizado por meio de injeção de dependência manipuladores de
página ou ações.

public class DocumentController : Controller


{
private readonly IAuthorizationService _authorizationService;
private readonly IDocumentRepository _documentRepository;

public DocumentController(IAuthorizationService authorizationService,


IDocumentRepository documentRepository)
{
_authorizationService = authorizationService;
_documentRepository = documentRepository;
}

IAuthorizationService tem duas AuthorizeAsync sobrecargas do método: aceitando um recurso e o nome da


política e o outro aceitando o recurso e uma lista de requisitos para avaliar.
ASP.NET Core 2.x
ASP.NET Core 1.x

Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,


object resource,
IEnumerable<IAuthorizationRequirement> requirements);
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
object resource,
string policyName);

O exemplo a seguir, o recurso a ser protegida é carregado em um personalizado Document objeto. Um


AuthorizeAsync sobrecarga é chamada para determinar se o usuário atual tem permissão para editar o
documento. Uma diretiva de autorização personalizada "EditPolicy" é acrescentada a uma decisão. Consulte
autorização com base na política de personalizado para obter mais informações sobre a criação de políticas de
autorização.

OBSERVAÇÃO
O código a seguir exemplos pressupõem a autenticação foi executado e o conjunto de User propriedade.

ASP.NET Core 2.x


ASP.NET Core 1.x

public async Task<IActionResult> OnGetAsync(Guid documentId)


{
Document = _documentRepository.Find(documentId);

if (Document == null)
{
return new NotFoundResult();
}

var authorizationResult = await _authorizationService


.AuthorizeAsync(User, Document, "EditPolicy");

if (authorizationResult.Succeeded)
{
return Page();
}
else if (User.Identity.IsAuthenticated)
{
return new ForbidResult();
}
else
{
return new ChallengeResult();
}
}

Escrever um Gerenciador de recursos


Escrevendo um manipulador para a autorização baseada em recursos não é muito diferente de escrevendo um
manipulador de requisitos simples. Criar uma classe personalizada de requisito e implementar uma classe de
manipulador de requisito. A classe do manipulador Especifica o requisito e o tipo de recurso. Por exemplo, um uso
de manipulador um SameAuthorRequirement requisito e um Document recurso tem a seguinte aparência:
ASP.NET Core 2.x
ASP.NET Core 1.x
public class DocumentAuthorizationHandler :
AuthorizationHandler<SameAuthorRequirement, Document>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
SameAuthorRequirement requirement,
Document resource)
{
if (context.User.Identity?.Name == resource.Author)
{
context.Succeed(requirement);
}

return Task.CompletedTask;
}
}

public class SameAuthorRequirement : IAuthorizationRequirement { }

Registrar o manipulador no e o requisito de Startup.ConfigureServices método:

services.AddMvc();

services.AddAuthorization(options =>
{
options.AddPolicy("EditPolicy", policy =>
policy.Requirements.Add(new SameAuthorRequirement()));
});

services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
services.AddScoped<IDocumentRepository, DocumentRepository>();

Requisitos operacionais
Se você estiver fazendo decisões com base nos resultados de CRUD (Criar, RER, Utualizar, Dexcluir) operações,
use o OperationAuthorizationRequirement classe auxiliar. Essa classe permite que você grave um único
manipulador em vez de uma classe individual para cada tipo de operação. Para usá-lo, forneça alguns nomes de
operação:

public static class Operations


{
public static OperationAuthorizationRequirement Create =
new OperationAuthorizationRequirement { Name = nameof(Create) };
public static OperationAuthorizationRequirement Read =
new OperationAuthorizationRequirement { Name = nameof(Read) };
public static OperationAuthorizationRequirement Update =
new OperationAuthorizationRequirement { Name = nameof(Update) };
public static OperationAuthorizationRequirement Delete =
new OperationAuthorizationRequirement { Name = nameof(Delete) };
}

O manipulador é implementado como a seguir, usando um OperationAuthorizationRequirement requisito e um


Document recursos:

ASP.NET Core 2.x


ASP.NET Core 1.x
public class DocumentAuthorizationCrudHandler :
AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Document resource)
{
if (context.User.Identity?.Name == resource.Author &&
requirement.Name == Operations.Read.Name)
{
context.Succeed(requirement);
}

return Task.CompletedTask;
}
}

O manipulador anterior valida a operação usando o recurso, a identidade do usuário e o requisito Name
propriedade.
Para chamar um manipulador de recurso operacionais, especifique a operação ao invocar AuthorizeAsync no
manipulador de página ou ação. O exemplo a seguir determina se o usuário autenticado tem permissão para exibir
o documento.

OBSERVAÇÃO
O código a seguir exemplos pressupõem a autenticação foi executado e o conjunto de User propriedade.

ASP.NET Core 2.x


ASP.NET Core 1.x

public async Task<IActionResult> OnGetAsync(Guid documentId)


{
Document = _documentRepository.Find(documentId);

if (Document == null)
{
return new NotFoundResult();
}

var authorizationResult = await _authorizationService


.AuthorizeAsync(User, Document, Operations.Read);

if (authorizationResult.Succeeded)
{
return Page();
}
else if (User.Identity.IsAuthenticated)
{
return new ForbidResult();
}
else
{
return new ChallengeResult();
}
}

Se a autorização bem-sucedida, a página para exibir o documento será retornada. Se falhar de autorização, mas o
usuário é autenticado, o retorno ForbidResult informa qualquer middleware de autenticação com falha de
autorização. Um ChallengeResult é retornado quando a autenticação deve ser executada. Para clientes de
navegador interativo, pode ser apropriado redirecionar o usuário para uma página de logon.
Autorização baseada em modo de exibição no
ASP.NET MVC de núcleo
10/04/2018 • 2 min to read • Edit Online

Um desenvolvedor quer geralmente Mostrar, ocultar ou modificar uma interface do usuário com base na
identidade do usuário atual. Você pode acessar o serviço de autorização em modos de exibição do MVC por meio
de injeção de dependência. Para injetar o serviço de autorização em um modo de exibição Razor, use o @inject
diretiva:

@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService

Se você deseja que o serviço de autorização em cada exibição, coloque o @inject diretiva para o viewimports.
cshtml arquivo do exibições diretório. Para obter mais informações, consulte Injeção de dependência em exibições.
Usar o serviço de autorização injetado para invocar AuthorizeAsync exatamente da mesma forma que você deve
verificar durante autorização baseada em recursos:
ASP.NET Core 2.x
ASP.NET Core 1.x

@if ((await AuthorizationService.AuthorizeAsync(User, "PolicyName")).Succeeded)


{
<p>This paragraph is displayed because you fulfilled PolicyName.</p>
}

Em alguns casos, o recurso será o modelo de exibição. Invocar AuthorizeAsync exatamente da mesma forma que
você deve verificar durante autorização baseada em recursos:
ASP.NET Core 2.x
ASP.NET Core 1.x

@if ((await AuthorizationService.AuthorizeAsync(User, Model, Operations.Edit)).Succeeded)


{
<p><a class="btn btn-default" role="button"
href="@Url.Action("Edit", "Document", new { id = Model.Id })">Edit</a></p>
}

No código anterior, o modelo é passado como um recurso de que avaliação de política deve levar em
consideração.

AVISO
Não confie na alternância de visibilidade de elementos de interface do usuário do aplicativo como a verificação de autorização
exclusiva. Ocultar um elemento de interface do usuário pode completamente impede o acesso a sua ação de controlador
associado. Por exemplo, considere o botão no trecho de código anterior. Um usuário pode invocar o Edit URL do método
de ação se saiba o recurso relativo é /Document/Edit/1. Por esse motivo, o Edit método de ação deve executar sua
própria verificação de autorização.
Autorizar com um esquema específico no núcleo do
ASP.NET
10/04/2018 • 4 min to read • Edit Online

Em alguns cenários, como aplicativos de página única (SPAs), é comum usar vários métodos de autenticação. Por
exemplo, o aplicativo pode usar autenticação baseada em cookie para fazer logon e autenticação do portador
JWT para solicitações de JavaScript. Em alguns casos, o aplicativo pode ter várias instâncias de um manipulador
de autenticação. Por exemplo, dois manipuladores de cookie onde um contém uma identidade básica e um é
criado quando uma autenticação multifator (MFA) foi acionada. MFA pode ser acionado porque o usuário
solicitou uma operação que requer segurança adicional.
ASP.NET Core 2.x
ASP.NET Core 1.x
Um esquema de autenticação é chamado quando o serviço de autenticação é configurado durante a
autenticação. Por exemplo:

public void ConfigureServices(IServiceCollection services)


{
// Code omitted for brevity

services.AddAuthentication()
.AddCookie(options => {
options.LoginPath = "/Account/Unauthorized/";
options.AccessDeniedPath = "/Account/Forbidden/";
})
.AddJwtBearer(options => {
options.Audience = "http://localhost:5001/";
options.Authority = "http://localhost:5000/";
});

No código anterior, foram adicionados dois manipuladores de autenticação: um para cookies e outro para
transmissão.

OBSERVAÇÃO
Especifica o esquema padrão resulta no HttpContext.User propriedade sendo definida como essa identidade. Se esse
comportamento não for desejado, desabilite-o invocando a forma sem parâmetros de AddAuthentication .

Selecionar o esquema com o atributo de autorização


No ponto de autorização, o aplicativo indica o manipulador a ser usado. Selecione o manipulador com o qual o
aplicativo autorizará passando uma lista delimitada por vírgulas de esquemas de autenticação para [Authorize] .
O [Authorize] atributo especifica o esquema de autenticação ou esquemas de usar, independentemente se um
padrão está configurado. Por exemplo:
ASP.NET Core 2.x
ASP.NET Core 1.x
[Authorize(AuthenticationSchemes = AuthSchemes)]
public class MixedController : Controller
// Requires the following imports:
// using Microsoft.AspNetCore.Authentication.Cookies;
// using Microsoft.AspNetCore.Authentication.JwtBearer;
private const string AuthSchemes =
CookieAuthenticationDefaults.AuthenticationScheme + "," +
JwtBearerDefaults.AuthenticationScheme;

No exemplo anterior, o cookie e o portador manipuladores execute em terá a oportunidade de criar e acrescentar
uma identidade para o usuário atual. Ao especificar um único esquema, o manipulador correspondente é
executado.
ASP.NET Core 2.x
ASP.NET Core 1.x

[Authorize(AuthenticationSchemes =
JwtBearerDefaults.AuthenticationScheme)]
public class MixedController : Controller

No código acima, somente o manipulador com o esquema de "Portador" em execução. Todas as identidades
baseada em cookie são ignoradas.

Selecionar o esquema com as políticas


Se você preferir especificar os esquemas de desejado política, você pode definir o AuthenticationSchemes ao
adicionar sua política de coleta:

services.AddAuthorization(options =>
{
options.AddPolicy("Over18", policy =>
{
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
policy.RequireAuthenticatedUser();
policy.Requirements.Add(new MinimumAgeRequirement());
});
});

No exemplo anterior, a política de "O Over18" só é executada em relação a identidade criada pelo manipulador
de "Portador". Usar a política, definindo o [Authorize] do atributo Policy propriedade:

[Authorize(Policy = "Over18")]
public class RegistrationController : Controller
Proteção de Dados no ASP.NET Core
10/04/2018 • 1 min to read • Edit Online

Introdução à proteção de dados


Introdução às APIs de proteção de dados
APIs de consumidor
Visão geral das APIs de consumidor
Cadeias de caracteres de finalidade
Multilocação e hierarquia de finalidade
Senhas hash
Limitar o tempo de vida de cargas protegidas
Desproteger cargas cujas chaves foram revogadas
Configuração
Configurar a proteção de dados do ASP.NET Core
Configurações padrão
Política ampla de computador
Cenários sem reconhecimento de DI
APIs de extensibilidade
Extensibilidade da criptografia básica
Extensibilidade de gerenciamento de chaves
APIs diversas
Implementação
Detalhes de criptografia autenticada
Derivação de subchaves e criptografia autenticada
Cabeçalhos de contexto
Gerenciamento de chaves
Provedores de armazenamento de chaves
Criptografia de chave em repouso
Imutabilidade de chave e configurações
Formato do armazenamento de chaves
Provedores de proteção de dados efêmeros
Compatibilidade
Substituição do ASP.NET no ASP.NET Core
Proteção de dados do ASP.NET Core
10/04/2018 • 11 min to read • Edit Online

Aplicativos da Web geralmente necessário armazenar dados confidenciais. O Windows fornece DPAPI para
aplicativos de desktop, mas isso é inadequado para aplicativos da web. A pilha de proteção de dados do ASP.NET
Core fornecem uma API de criptografia simple e fácil de usar um desenvolvedor pode usar para proteger dados,
incluindo gerenciamento de chave e rotação.
A pilha de proteção de dados do ASP.NET Core foi projetada para servir como a substituição de longo prazo para
o elemento no ASP.NET 1. x - 4. x. Ele foi projetado para resolver muitos dos problemas da pilha de criptografia
antigo enquanto fornece uma solução para a maioria dos casos de uso, os aplicativos modernos têm
probabilidade de encontrar.

Declaração do problema
A declaração do problema geral pode ser declarada em um único sentença sucintamente: necessário manter
informações confiáveis para recuperação posterior, mas não confio o mecanismo de persistência. Em termos de
web, isso pode ser escrito como "Preciso ir e voltar estado confiável por meio de um cliente não confiável."
O exemplo canônico disso é um cookie de autenticação ou portador token. O servidor gera um "Estou Groot e ter
permissões de xyz" token e encaminha-lo ao cliente. No futuro, o cliente apresentará esse token para o servidor,
mas o servidor precisa de algum tipo de garantia de que o cliente não foi forjada o token. Portanto, o primeiro
requisito: autenticidade (também conhecido como integridade e à prova de adulteração).
Como o estado persistente é confiável pelo servidor, estimamos que esse estado pode conter informações
específicas para o ambiente operacional. Isso pode ser na forma de um caminho de arquivo, uma permissão, um
identificador ou outra referência indireta ou alguma outra parte de dados específico do servidor. Essas
informações, geralmente, não devem ser reveladas para um cliente não confiável. Portanto, o segundo requisito:
confidencialidade.
Finalmente, desde que os aplicativos modernos são componentizados, o que vimos é que componentes
individuais queira tirar proveito do sistema sem considerar outros componentes no sistema. Por exemplo, se um
componente de token de portador está usando esta pilha, ele deve operar sem interferência de um mecanismo de
anti-CSRF que também pode usar a mesma pilha. Portanto, o requisito de final: isolamento.
Podemos fornecer mais restrições para restringir o escopo dos nossos requisitos. Vamos supor que todos os
serviços que operam com o criptográfico são igualmente confiáveis e que os dados não precisam ser gerado ou
consumido fora os serviços em nosso controle direto. Além disso, é necessário que as operações são tão rápidas
quanto possível, desde que cada solicitação ao serviço web pode percorrer o criptográfico uma ou mais vezes.
Isso torna a criptografia simétrica ideal para o nosso cenário, e nós pode desconto criptografia assimétrica até
como uma hora que é necessária.

Filosofia de design
Começamos identificando problemas com a pilha existente. Depois que tivemos que, podemos pesquisadas o
cenário de soluções existentes e concluiu que nenhuma solução existente tinha muito dos recursos que são
procurados. Em seguida, projetamos uma solução baseada em vários princípios.
O sistema deve oferecer simplicidade de configuração. O ideal é o sistema seria nenhuma configuração e
os desenvolvedores foi atingido o início em execução. Em situações em que os desenvolvedores precisam
configurar um aspecto específico (como o repositório de chaves), consideração deve ser fornecida a fazer
essas configurações específicas simples.
Oferece uma API simples do voltado ao consumidor. As APIs deve ser fácil de usar corretamente e difícil
de usar incorretamente.
Os desenvolvedores não devem saber os princípios de gerenciamento de chaves. O sistema deve lidar com
a seleção do algoritmo e a vida útil da chave em nome do desenvolvedor. Idealmente o desenvolvedor
nunca mesmo deve ter acesso ao material de chave bruto.
As chaves devem ser protegidas em repouso quando possível. O sistema deve descobrir um mecanismo
de proteção padrão apropriado e aplicá-lo automaticamente.
Esses princípios em mente, desenvolvemos um simples, fácil de usar pilha de proteção de dados.
A proteção de dados do ASP.NET Core APIs não são principalmente para persistência indefinida de cargas
confidenciais. Outras tecnologias, como Windows CNG DPAPI e Azure Rights Management são mais adequados
para o cenário de armazenamento indefinido, e eles têm recursos de gerenciamento de chave forte de forma
correspondente. Dito isso, não há nada proibindo um desenvolvedor usa as APIs de proteção de dados ASP.NET
Core para proteção de longo prazo de dados confidenciais.

Público-alvo
O sistema de proteção de dados é dividido em cinco pacotes principais. Vários aspectos dessas APIs três público
principal; de destino
1. O visão geral de APIs do consumidor os desenvolvedores de aplicativo e do framework de destino.
"Quero saber mais sobre como funciona a pilha de ou sobre como ele está configurado. Simplesmente
desejo executar alguma operação no simples assim uma maneira possível com alta probabilidade de usar
as APIs com êxito."
2. O APIs de configuração os desenvolvedores de aplicativos e os administradores do sistema de destino.
"Eu preciso informar o sistema de proteção de dados que o meu ambiente requer caminhos não padrão ou
as configurações".
3. Os desenvolvedores de destino APIs de extensibilidade responsável pela implementação de política
personalizada. O uso dessas APIs seria limitado a situações raras e experientes, os desenvolvedores com
reconhecimento de segurança.
"Preciso substituir um componente inteiro dentro do sistema porque tenho requisitos comportamentais
realmente exclusivos. Desejo saber uncommonly usado partes da superfície de API para criar um plug-in
que atende aos meus requisitos."

Layout do pacote
A pilha de proteção de dados consiste em cinco pacotes.
Microsoft.AspNetCore.DataProtection.Abstractions contém as interfaces IDataProtectionProvider e
IDataProtector básicas. Ele também contém métodos de extensão úteis que podem ajudá-lo a trabalhar
com esses tipos (por exemplo, sobrecargas de IDataProtector.Protect). Consulte a seção de interfaces de
consumidor para obter mais informações. Se alguém é responsável por criar uma instância do sistema de
proteção de dados e as APIs simplesmente está consumindo, você desejará
Microsoft.AspNetCore.DataProtection.Abstractions de referência.
Microsoft.AspNetCore.DataProtection contém a implementação de núcleo do sistema proteção de dados,
incluindo as principais operações criptográficas, gerenciamento de chaves, configuração e extensibilidade.
Se você é responsável para instanciar o sistema de proteção de dados (por exemplo, adicioná-lo para um
IServiceCollection) ou modificar ou estender seu comportamento, você desejará
Microsoft.AspNetCore.DataProtection de referência.
Microsoft.AspNetCore.DataProtection.Extensions contém APIs adicionais que os desenvolvedores podem
ser úteis, mas que não pertencem no pacote principal. Por exemplo, este pacote contém uma API simples
"instanciar o sistema apontando para um diretório de armazenamento de chave específico com nenhuma
configuração de injeção de dependência" (mais informações). Ele também contém métodos de extensão
para limitar o tempo de vida das cargas protegidos (mais informações).
Microsoft.AspNetCore.DataProtection.SystemWeb pode ser instalado em um aplicativo de 4. x ASP.NET
existente para redirecionar seu operações em vez disso, usar a nova pilha de proteção de dados. Consulte
compatibilidade para obter mais informações.
Microsoft.AspNetCore.Cryptography.KeyDerivation fornece uma implementação da rotina de hash de
senha do PBKDF2 e podem ser usadas por sistemas que precisam lidar com as senhas de usuário com
segurança. Consulte Hash senhas para obter mais informações.
Começar com as APIs de proteção de dados no
ASP.NET Core
10/04/2018 • 3 min to read • Edit Online

Em seus dados mais simples, protegendo consiste as seguintes etapas:


1. Crie um tipo de dados protetor de um provedor de proteção de dados.
2. Chamar o Protect método com os dados que você deseja proteger.
3. Chamar o Unprotect método com os dados que você deseja transformar novamente em texto sem
formatação.
A maioria das estruturas e modelos de aplicativo, como ASP.NET ou SignalR, já que configurar o sistema de
proteção de dados e adicioná-lo para um contêiner de serviço, que você pode acessar por meio de injeção de
dependência. O exemplo a seguir demonstra como configurar um contêiner de serviço para injeção de
dependência e registrando a pilha de proteção de dados, recebendo o provedor de proteção de dados por meio de
DI, criando um protetor e desproteção e proteção de dados
using System;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection;

public class Program


{
public static void Main(string[] args)
{
// add data protection services
var serviceCollection = new ServiceCollection();
serviceCollection.AddDataProtection();
var services = serviceCollection.BuildServiceProvider();

// create an instance of MyClass using the service provider


var instance = ActivatorUtilities.CreateInstance<MyClass>(services);
instance.RunSample();
}

public class MyClass


{
IDataProtector _protector;

// the 'provider' parameter is provided by DI


public MyClass(IDataProtectionProvider provider)
{
_protector = provider.CreateProtector("Contoso.MyClass.v1");
}

public void RunSample()


{
Console.Write("Enter input: ");
string input = Console.ReadLine();

// protect the payload


string protectedPayload = _protector.Protect(input);
Console.WriteLine($"Protect returned: {protectedPayload}");

// unprotect the payload


string unprotectedPayload = _protector.Unprotect(protectedPayload);
Console.WriteLine($"Unprotect returned: {unprotectedPayload}");
}
}
}

/*
* SAMPLE OUTPUT
*
* Enter input: Hello world!
* Protect returned: CfDJ8ICcgQwZZhlAlTZT...OdfH66i1PnGmpCR5e441xQ
* Unprotect returned: Hello world!
*/

Quando você cria um protetor você deve fornecer um ou mais cadeias de caracteres de finalidade. Uma cadeia de
caracteres de finalidade fornece isolamento entre os consumidores. Por exemplo, um protetor criado com uma
cadeia de caracteres de fim de "green" não será possível desproteger dados fornecidos por um protetor com a
finalidade de "roxo".
DICA
Instâncias do IDataProtectionProvider e IDataProtector são thread-safe para chamadores vários. Ele foi desenvolvido
que depois que um componente obtém uma referência a um IDataProtector por meio de uma chamada para
CreateProtector , ele usará essa referência para várias chamadas para Protect e Unprotect .

Uma chamada para Unprotect lançará CryptographicException se a carga protegida não pode ser verificada ou decifrada.
Alguns componentes poderá ignorar erros durante Desproteger operações; um componente que lê os cookies de
autenticação pode manipular esse erro e tratar a solicitação, como não se tivesse nenhum cookie de em vez de falhar a
solicitação. Componentes que deseja que esse comportamento especificamente devem capturar CryptographicException em
vez de assimilação todas as exceções.
APIs de consumidor para o ASP.NET Core
10/04/2018 • 1 min to read • Edit Online

Visão geral das APIs de consumidor


Cadeias de caracteres de finalidade
Multilocação e hierarquia de finalidade
Senhas hash
Limitar o tempo de vida de cargas protegidas
Desproteger cargas cujas chaves foram revogadas
Visão geral APIs do consumidor do ASP.NET Core
10/04/2018 • 7 min to read • Edit Online

O IDataProtectionProvider e IDataProtector interfaces são as interfaces básicas por meio do qual os


consumidores usam o sistema de proteção de dados. Eles estão localizados no
Microsoft.AspNetCore.DataProtection.Abstractions pacote.

IDataProtectionProvider
A interface de provedor representa a raiz do sistema de proteção de dados. Ele não pode ser usado diretamente
para proteger ou desproteger dados. Em vez disso, o consumidor deve obter uma referência a um
IDataProtector chamando IDataProtectionProvider.CreateProtector(purpose) , onde o objetivo é uma cadeia de
caracteres que descreve o caso de uso pretendido do consumidor. Consulte cadeias de caracteres de finalidade
para obter mais informações sobre a intenção deste parâmetro e como escolher um valor apropriado.

IDataProtector
A interface de protetor é retornada por uma chamada para CreateProtector e ele essa interface que os
consumidores podem usar para executar proteger e Desproteger as operações.
Para proteger uma parte dos dados, transmitir os dados para o Protect método. A interface básica define um
método que byte converte [] -> byte [], mas há também uma sobrecarga (fornecida como um método de
extensão), que converte a cadeia de caracteres -> a cadeia de caracteres. A segurança oferecida pelos dois
métodos é idêntica; o desenvolvedor deve escolher qualquer sobrecarga é mais conveniente para seu caso de
uso. Independentemente da sobrecarga escolhida, o valor retornado pelo Proteja método agora está protegido
(enciphered e à prova de adulteração) e o aplicativo pode enviá-lo para um cliente não confiável.
Para desproteger uma parte anteriormente protegido dos dados, passar os dados protegidos para o Unprotect
método. (Há byte []-sobrecargas com base e baseada em cadeia de caracteres para conveniência do
desenvolvedor.) Se a carga protegida foi gerada por uma chamada anterior para Protect esse mesmo
IDataProtector , o Unprotect método retornará a carga desprotegida original. Se a carga protegida tenha sido
violada ou foi produzida por outro IDataProtector , o Unprotect método lançará CryptographicException.
O conceito de mesmo versus diferentes IDataProtector vínculos de volta para o conceito de finalidade. Se dois
IDataProtector instâncias foram geradas a partir da mesma raiz IDataProtectionProvider mas por meio de
cadeias de caracteres de finalidade diferente na chamada para IDataProtectionProvider.CreateProtector , em
seguida, elas são consideradas protetores diferentes, e um não será possível desproteger cargas geradas por
outros.

Essas interfaces de consumo


Para um componente com reconhecimento de injeção de dependência, o uso pretendido é que o componente
levar uma IDataProtectionProvider parâmetro em seu construtor e que o sistema DI fornece automaticamente
esse serviço quando o componente é instanciado.
OBSERVAÇÃO
Alguns aplicativos (como aplicativos de console ou aplicativos do ASP.NET 4. x) podem não ser DI com reconhecimento de
forma não é possível usar o mecanismo descrito aqui. Para esses cenários consulte o cenários com reconhecimento de DI
não documento para obter mais informações sobre a obtenção de uma instância de um IDataProtection provedor sem
passar por DI.

O exemplo a seguir demonstra três conceitos:


1. Adicione o sistema de proteção de dados ao contêiner de serviço,
2. Usando DI para receber uma instância de um IDataProtectionProvider ,e
3. Criando um IDataProtector de um IDataProtectionProvider e usá-lo para proteger e Desproteger dados.
using System;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection;

public class Program


{
public static void Main(string[] args)
{
// add data protection services
var serviceCollection = new ServiceCollection();
serviceCollection.AddDataProtection();
var services = serviceCollection.BuildServiceProvider();

// create an instance of MyClass using the service provider


var instance = ActivatorUtilities.CreateInstance<MyClass>(services);
instance.RunSample();
}

public class MyClass


{
IDataProtector _protector;

// the 'provider' parameter is provided by DI


public MyClass(IDataProtectionProvider provider)
{
_protector = provider.CreateProtector("Contoso.MyClass.v1");
}

public void RunSample()


{
Console.Write("Enter input: ");
string input = Console.ReadLine();

// protect the payload


string protectedPayload = _protector.Protect(input);
Console.WriteLine($"Protect returned: {protectedPayload}");

// unprotect the payload


string unprotectedPayload = _protector.Unprotect(protectedPayload);
Console.WriteLine($"Unprotect returned: {unprotectedPayload}");
}
}
}

/*
* SAMPLE OUTPUT
*
* Enter input: Hello world!
* Protect returned: CfDJ8ICcgQwZZhlAlTZT...OdfH66i1PnGmpCR5e441xQ
* Unprotect returned: Hello world!
*/

O pacote Microsoft.AspNetCore.DataProtection.Abstractions contém um método de extensão


IServiceProvider.GetDataProtector como uma conveniência de desenvolvedor. Ele encapsula como uma única
operação ambos recuperando uma IDataProtectionProvider do provedor de serviços e chamar
IDataProtectionProvider.CreateProtector . O exemplo a seguir demonstra o uso.
using System;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection;

public class Program


{
public static void Main(string[] args)
{
// add data protection services
var serviceCollection = new ServiceCollection();
serviceCollection.AddDataProtection();
var services = serviceCollection.BuildServiceProvider();

// get an IDataProtector from the IServiceProvider


var protector = services.GetDataProtector("Contoso.Example.v2");
Console.Write("Enter input: ");
string input = Console.ReadLine();

// protect the payload


string protectedPayload = protector.Protect(input);
Console.WriteLine($"Protect returned: {protectedPayload}");

// unprotect the payload


string unprotectedPayload = protector.Unprotect(protectedPayload);
Console.WriteLine($"Unprotect returned: {unprotectedPayload}");
}
}

DICA
Instâncias do IDataProtectionProvider e IDataProtector são thread-safe para chamadores vários. Ele foi
desenvolvido que depois que um componente obtém uma referência a um IDataProtector por meio de uma chamada
para CreateProtector , ele usará essa referência para várias chamadas para Protect e Unprotect . Uma chamada para
Unprotect lançará CryptographicException se a carga protegida não pode ser verificada ou decifrada. Alguns
componentes poderá ignorar erros durante Desproteger operações; um componente que lê os cookies de autenticação
pode manipular esse erro e tratar a solicitação, como não se tivesse nenhum cookie de em vez de falhar a solicitação.
Componentes que deseja que esse comportamento especificamente devem capturar CryptographicException em vez de
assimilação todas as exceções.
Cadeias de caracteres de finalidade no núcleo do
ASP.NET
10/04/2018 • 6 min to read • Edit Online

Componentes que consomem IDataProtectionProvider deve passar em uma única fins parâmetro para o
CreateProtector método. A finalidade parâmetro é inerente à segurança do sistema de proteção de dados,
como ele fornece isolamento entre consumidores de criptografia, mesmo se as chaves de criptografia de raiz
são as mesmas.
Quando um consumidor Especifica a finalidade, a cadeia de caracteres de finalidade é usada junto com as
chaves de criptografia de raiz para derivar criptográficas subchaves exclusivas para que o consumidor. Isso isola
o consumidor de todos os outros consumidores criptográficos do aplicativo: nenhum outro componente pode
ler suas cargas de e não pode ler cargas do qualquer outro componente. Esse isolamento também processa
inviável categorias inteiras de ataque em relação ao componente.

No diagrama acima, IDataProtector instâncias A e B não é possível ler umas das outras cargas, somente seus
próprios.
A cadeia de caracteres de fim não precisa ser segredo. Ele simplesmente deve ser exclusivo no sentido de que
nenhum outro componente com bom comportamento já fornecem a mesma cadeia de caracteres de finalidade.

DICA
Usando o nome de namespace e o tipo do componente consumindo as APIs de proteção de dados é uma boa regra geral,
como prática que essas informações nunca entrarão em conflito.
Um componente de autoria do Contoso que é responsável por minting tokens de portador pode usar
Contoso.Security.BearerToken como cadeia de caracteres sua finalidade. Ou - ainda melhor - ele pode usar
Contoso.Security.BearerToken.v1 como cadeia de caracteres sua finalidade. Anexar o número de versão permite que uma
versão futura usar Contoso.Security.BearerToken.v2 como sua finalidade e as versões diferentes seria completamente
isoladas uma da outra quanto cargas ir.

Desde o parâmetro fins CreateProtector é uma matriz de cadeia de caracteres, acima poderiam ter sido em vez
disso, especificadas como [ "Contoso.Security.BearerToken", "v1" ] . Isso permite o estabelecimento de uma
hierarquia de propósitos e abre a possibilidade de cenários de multilocação com o sistema de proteção de
dados.
AVISO
Componentes não devem permitir que a entrada do usuário não confiável ser a única origem de entrada para a cadeia de
fins.
Por exemplo, considere um componente Contoso.Messaging.SecureMessage que é responsável por armazenar mensagens
seguras. Se o componente de mensagens seguro chamar CreateProtector([ username ]) , em seguida, um usuário
mal-intencionado pode criar uma conta com o nome de usuário "Contoso.Security.BearerToken" em uma tentativa de
obter o componente para chamar CreateProtector([ "Contoso.Security.BearerToken" ]) , inadvertidamente
provocando mensagens seguras sistema cargas Menta que pode ser considerada como tokens de autenticação.
Uma cadeia de fins melhor para o componente de mensagens seria
CreateProtector([ "Contoso.Messaging.SecureMessage", "User: username" ]) , que fornece isolamento apropriado.

O isolamento fornecido pelos e comportamentos de IDataProtectionProvider , IDataProtector , e fins é o


seguinte:
Para um determinado IDataProtectionProvider objeto, o CreateProtector método criará uma
IDataProtector objeto vinculado exclusivamente para ambos os IDataProtectionProvider objeto que
criou e o parâmetro de fins que foi passado para o método.
O parâmetro de fim não deve ser nulo. (Se fins é especificado como uma matriz, isso significa que a
matriz não deve ser de comprimento zero e todos os elementos da matriz devem ser não-nulo.) Uma
finalidade de cadeia de caracteres vazia é tecnicamente permitida, mas não é recomendada.
Argumentos de duas finalidades são equivalentes se e somente se eles contêm as mesmas cadeias de
caracteres (usando um comparador ordinal) na mesma ordem. Um argumento de finalidade única é
equivalente à matriz de elemento único fins correspondente.
Dois IDataProtector objetos são equivalentes e apenas se eles são criados a partir equivalente
IDataProtectionProvider objetos com parâmetros de fins equivalente.
Para um determinado IDataProtector objeto, uma chamada para Unprotect(protectedData) retornará
original unprotectedData se e somente se protectedData := Protect(unprotectedData) para um
equivalente IDataProtector objeto.

OBSERVAÇÃO
Não estamos considerando o caso onde algum componente intencionalmente escolhe uma cadeia de caracteres de
finalidade que é conhecida em conflito com outro componente. Esse componente essencialmente seria considerado mal-
intencionados e este sistema não se destina a fornecer garantias de segurança que um código mal-intencionado já está
em execução dentro do processo de trabalho.
Hierarquia de propósito e multilocação no núcleo do
ASP.NET
10/04/2018 • 3 min to read • Edit Online

Como um IDataProtector também é implicitamente um IDataProtectionProvider , fins podem ser encadeada.


Nesse sentido, provider.CreateProtector([ "purpose1", "purpose2" ]) é equivalente a
provider.CreateProtector("purpose1").CreateProtector("purpose2") .

Isso permite que algumas relações hierárquicas interessantes através do sistema de proteção de dados. No
exemplo anterior de Contoso.Messaging.SecureMessage, pode chamar o componente SecureMessage
provider.CreateProtector("Contoso.Messaging.SecureMessage") inicial de uma vez e armazenar em cache o resultado
em uma particular _myProvide campo. Protetores futuras, em seguida, podem ser criados por meio de chamadas
para _myProvider.CreateProtector("User: username") , e os protetores são usados para proteger as mensagens
individuais.
Isso também pode ser invertido. Considere que hospeda vários locatários (um CMS parece razoável) e cada
locatário podem ser configurado com seu próprio sistema de gerenciamento de autenticação e o estado de um
único aplicativo lógico. O aplicativo de proteção tem um único provedor mestre e chama
provider.CreateProtector("Tenant 1") e provider.CreateProtector("Tenant 2") para fornecer seu próprio fatia
isolada do sistema de proteção de dados de cada locatário. Os locatários, em seguida, podem derivar seus próprio
protetores individuais com base em suas necessidades, mas, independentemente de como eles tentam não é
possível criar protetores que entrarem em conflito com qualquer outro locatário no sistema. Graficamente, isso é
representado como abaixo.

AVISO
Isso pressupõe que a proteção controles de aplicativo que APIs estão disponíveis para locatários individuais e que os
locatários não podem executar código arbitrário no servidor. Se um locatário pode executar código arbitrário, eles executaria
privada reflexão para interromper as garantias de isolamento ou pode apenas ler o material de chave mestre diretamente e
derivar qualquer subchaves que desejarem.

Na verdade, o sistema de proteção de dados usa uma classificação de multilocação na configuração padrão da
caixa. Por padrão, o material de chave mestra é armazenado na pasta de perfil de usuário da conta de processo do
operador (ou o registro, para identidades de pool de aplicativos do IIS ). Mas é na verdade bastante comum para
usar uma única conta para executar vários aplicativos e, portanto, todos esses aplicativos acabar compartilhando a
material da chave mestra. Para resolver isso, o sistema de proteção de dados automaticamente insere um
identificador exclusivo por aplicativo como o primeiro elemento da cadeia de finalidade geral. Essa finalidade
implícita serve para isolar aplicativos individuais uns dos outros tratando efetivamente cada aplicativo como um
locatário exclusivo dentro do sistema e o processo de criação de protetor é idêntico à imagem acima.
Senhas de hash no núcleo do ASP.NET
04/05/2018 • 2 min to read • Edit Online

O código de proteção de dados base inclui um pacote Microsoft.AspNetCore.Cryptography.KeyDerivation que


contém funções de derivação de chave de criptografia. Este pacote é um componente autônomo e não tem
nenhuma dependência no restante do sistema de proteção de dados. Ele pode ser usado independentemente
completamente. A origem existe junto com o código de proteção de dados base como uma conveniência.
O pacote atualmente oferece um método KeyDerivation.Pbkdf2 que permite o hash de uma senha usando o
PBKDF2 algoritmo. Essa API é muito semelhante à existente do .NET Framework Rfc2898DeriveBytes tipo, mas
há três diferenças importantes:
1. O KeyDerivation.Pbkdf2 método dá suporte ao consumo vários PRFs (atualmente HMACSHA1 , HMACSHA256 ,
e HMACSHA512 ), enquanto o Rfc2898DeriveBytes digite dá suporte apenas à HMACSHA1 .
2. O KeyDerivation.Pbkdf2 método detecta o sistema operacional atual e tenta escolha a implementação mais
otimizada da rotina, fornecendo um desempenho muito melhor em determinados casos. (No Windows 8,
ele oferece cerca de 10 vezes a taxa de transferência Rfc2898DeriveBytes .)
3. O KeyDerivation.Pbkdf2 método requer que o chamador pode especificar todos os parâmetros (salt, PRF e
contagem de iteração). O Rfc2898DeriveBytes tipo fornece valores padrão para eles.
using System;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;

public class Program


{
public static void Main(string[] args)
{
Console.Write("Enter a password: ");
string password = Console.ReadLine();

// generate a 128-bit salt using a secure PRNG


byte[] salt = new byte[128 / 8];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(salt);
}
Console.WriteLine($"Salt: {Convert.ToBase64String(salt)}");

// derive a 256-bit subkey (use HMACSHA1 with 10,000 iterations)


string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: password,
salt: salt,
prf: KeyDerivationPrf.HMACSHA1,
iterationCount: 10000,
numBytesRequested: 256 / 8));
Console.WriteLine($"Hashed: {hashed}");
}
}

/*
* SAMPLE OUTPUT
*
* Enter a password: Xtw9NMgx
* Salt: NZsP6NnmfBuYeJrrAKNuVQ==
* Hashed: /OOoOer10+tGwTRDTrQSoeCxVTFr6dtYly7d0cPxIak=
*/

Consulte o código-fonte para o ASP.NET Core Identity PasswordHasher caso de uso de tipo para um mundo real.
Limite o tempo de vida das cargas protegidos no
núcleo do ASP.NET
10/04/2018 • 5 min to read • Edit Online

Há cenários onde quer que o desenvolvedor do aplicativo para criar uma carga protegida que expira após um
período de tempo definido. Por exemplo, a carga protegida pode representar um token de redefinição de senha
que deve ser válido somente por uma hora. É certamente possível para o desenvolvedor criar seu próprio formato
de carga que contém uma data de expiração incorporados e os desenvolvedores avançados talvez queira fazer isso
mesmo assim, mas para a maioria dos desenvolvedores gerenciar esses expirações pode crescer entediante.
Para facilitar isso para nossos público de desenvolvedor, o pacote
Microsoft.AspNetCore.DataProtection.Extensions contém APIs do utilitário para criar cargas expirarem
automaticamente após um período de tempo definido. Essas APIs de suspensão do ITimeLimitedDataProtector
tipo.

Uso de API
O ITimeLimitedDataProtector interface é a interface principal para proteger e ao desproteger cargas de tempo
limitado / expiração automática. Para criar uma instância de um ITimeLimitedDataProtector , você precisará de uma
instância de uma expressão IDataProtector construído com uma finalidade específica. Uma vez o IDataProtector
instância estiver disponível, chame o IDataProtector.ToTimeLimitedDataProtector método de extensão para
retornar um protetor com recursos internos de expiração.
ITimeLimitedDataProtector apresenta os seguintes métodos de extensão e de superfície de API:
CreateProtector (objetivo de cadeia de caracteres): ITimeLimitedDataProtector - esta API é semelhante à
existente IDataProtectionProvider.CreateProtector em que ele pode ser usado para criar finalidade cadeias
de um protetor de tempo limite de raiz.
Proteger (byte [] texto sem formatação, expiração de DateTimeOffset): byte]
Proteger (texto sem formatação do byte [], tempo de vida de TimeSpan): byte]
Proteger (byte [] texto sem formatação): byte]
Proteger (cadeia de caracteres em texto sem formatação, expiração de DateTimeOffset): cadeia de
caracteres
Proteger (texto sem formatação de cadeia de caracteres, tempo de vida de TimeSpan): cadeia de caracteres
Proteger (cadeia de caracteres em texto sem formatação): cadeia de caracteres
Além das principais Protect métodos que levam apenas o texto não criptografado, não há novas sobrecargas que
permitem especificar a data de validade da carga. A data de validade pode ser especificada como uma data
absoluta (por meio de um DateTimeOffset ) ou como uma hora relativa (do sistema atual de tempo, por meio de
um TimeSpan ). Se uma sobrecarga que não tem uma expiração é chamada, a carga será considerada nunca para
expirar.
Desproteger (byte [] protectedData, limite de expiração de DateTimeOffset): byte]
Desproteger (byte [] protectedData): byte]
Desproteger (protectedData de cadeia de caracteres, limite de expiração de DateTimeOffset): cadeia de
caracteres
Desproteger (cadeia de caracteres protectedData): cadeia de caracteres
O Unprotect métodos retornam os dados desprotegidos originais. Se a carga ainda não tiver expirado, a
expiração absoluta é retornada como um parâmetro junto com os dados desprotegidos originais out opcional. Se
a carga tiver expirada, todas as sobrecargas do método Desproteger lançará CryptographicException.

AVISO
Ele não tem recomendável usar essas APIs para proteger cargas que requerem a persistência de longo prazo ou indefinida.
"Pode suportar para as cargas protegidas sejam irrecuperáveis permanentemente após um mês?" pode servir como uma boa
regra prática; Se a resposta for nenhum desenvolvedores, em seguida, considere APIs alternativas.

O exemplo abaixo usa o caminhos de código não DI para instanciar o sistema de proteção de dados. Para executar
este exemplo, certifique-se de que você adicionou uma referência ao pacote
Microsoft.AspNetCore.DataProtection.Extensions primeiro.

using System;
using System.IO;
using System.Threading;
using Microsoft.AspNetCore.DataProtection;

public class Program


{
public static void Main(string[] args)
{
// create a protector for my application

var provider = DataProtectionProvider.Create(new DirectoryInfo(@"c:\myapp-keys\"));


var baseProtector = provider.CreateProtector("Contoso.TimeLimitedSample");

// convert the normal protector into a time-limited protector


var timeLimitedProtector = baseProtector.ToTimeLimitedDataProtector();

// get some input and protect it for five seconds


Console.Write("Enter input: ");
string input = Console.ReadLine();
string protectedData = timeLimitedProtector.Protect(input, lifetime: TimeSpan.FromSeconds(5));
Console.WriteLine($"Protected data: {protectedData}");

// unprotect it to demonstrate that round-tripping works properly


string roundtripped = timeLimitedProtector.Unprotect(protectedData);
Console.WriteLine($"Round-tripped data: {roundtripped}");

// wait 6 seconds and perform another unprotect, demonstrating that the payload self-expires
Console.WriteLine("Waiting 6 seconds...");
Thread.Sleep(6000);
timeLimitedProtector.Unprotect(protectedData);
}
}

/*
* SAMPLE OUTPUT
*
* Enter input: Hello!
* Protected data: CfDJ8Hu5z0zwxn...nLk7Ok
* Round-tripped data: Hello!
* Waiting 6 seconds...
* <<throws CryptographicException with message 'The payload expired at ...'>>

*/
Desproteger cargas cujas chaves foram revogados no
núcleo do ASP.NET
10/04/2018 • 5 min to read • Edit Online

A proteção de dados do ASP.NET Core APIs não são principalmente para persistência indefinida de cargas
confidenciais. Outras tecnologias, como Windows CNG DPAPI e Azure Rights Management são mais adequados
para o cenário de armazenamento indefinido, e eles têm recursos de gerenciamento de chave forte de forma
correspondente. Dito isso, não há nada proibindo um desenvolvedor usa as APIs de proteção de dados ASP.NET
Core para proteção de longo prazo de dados confidenciais. Chaves nunca são removidas do anel chave, portanto
IDataProtector.Unprotect sempre pode recuperar conteúdos existentes desde que as chaves estão disponíveis e
válido.
No entanto, um problema surge quando o desenvolvedor tenta Desproteger dados que foi protegidos com uma
chave revogada, como IDataProtector.Unprotect lançará uma exceção, nesse caso. Isso pode ser bom para cargas
de curta duração ou transitórias (como tokens de autenticação), pois esses tipos de cargas podem ser facilmente
recriados pelo sistema e na pior das hipóteses, o visitante do site pode ser necessário para fazer logon novamente.
Mas para cargas persistentes, tendo Unprotect throw pode levar a perda inaceitável de dados.

IPersistedDataProtector
Para suportar o cenário de permitir que conteúdos a ser desprotegido mesmo em face chaves revogadas, o
sistema de proteção de dados contém um IPersistedDataProtector tipo. Para obter uma instância de
IPersistedDataProtector , simplesmente obter uma instância de IDataProtector no modo normal e tente
converter o IDataProtector para IPersistedDataProtector .

OBSERVAÇÃO
Nem todos os IDataProtector instâncias podem ser convertidas em IPersistedDataProtector . Os desenvolvedores
devem usar o c# como operador ou semelhante evitar exceções de tempo de execução causado por conversões inválidos e
devem estar preparados para tratar o caso de falha de maneira adequada.

IPersistedDataProtector expõe a superfície de API a seguir:

DangerousUnprotect(byte[] protectedData, bool ignoreRevocationErrors,


out bool requiresMigration, out bool wasRevoked) : byte[]

Essa API usa a carga protegida (como uma matriz de bytes) e retorna a carga desprotegida. Não há nenhuma
sobrecarga baseada em cadeia de caracteres. Os dois parâmetros de saída são da seguinte maneira.
requiresMigration : será definido como true se a chave usada para proteger essa carga não é mais a chave
padrão ativo, por exemplo, a chave usada para proteger essa carga é antiga e tem uma chave sem
interrupção operação desde tomado local. O chamador poderá proteger novamente a carga dependendo de
suas necessidades de negócios.
wasRevoked : será definido como verdadeiro se a chave usada para proteger essa carga foi revogada.
AVISO
Tenha bastante cuidado ao passar ignoreRevocationErrors: true para o DangerousUnprotect método. Se, depois de
chamar esse método de wasRevoked valor for true, em seguida, a chave usada para proteger essa carga foi revogada e
autenticidade da carga deve ser tratada como suspeito. Nesse caso, apenas continue a operar na carga de desprotegidos se
você tiver alguma garantia separada que é autêntica, por exemplo, se ele é proveniente de um banco de dados seguro em
vez de sendo enviada por um cliente da web não confiável.

using System;
using System.IO;
using System.Text;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.Extensions.DependencyInjection;

public class Program


{
public static void Main(string[] args)
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddDataProtection()
// point at a specific folder and use DPAPI to encrypt keys
.PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys"))
.ProtectKeysWithDpapi();
var services = serviceCollection.BuildServiceProvider();

// get a protector and perform a protect operation


var protector = services.GetDataProtector("Sample.DangerousUnprotect");
Console.Write("Input: ");
byte[] input = Encoding.UTF8.GetBytes(Console.ReadLine());
var protectedData = protector.Protect(input);
Console.WriteLine($"Protected payload: {Convert.ToBase64String(protectedData)}");

// demonstrate that the payload round-trips properly


var roundTripped = protector.Unprotect(protectedData);
Console.WriteLine($"Round-tripped payload: {Encoding.UTF8.GetString(roundTripped)}");

// get a reference to the key manager and revoke all keys in the key ring
var keyManager = services.GetService<IKeyManager>();
Console.WriteLine("Revoking all keys in the key ring...");
keyManager.RevokeAllKeys(DateTimeOffset.Now, "Sample revocation.");

// try calling Protect - this should throw


Console.WriteLine("Calling Unprotect...");
try
{
var unprotectedPayload = protector.Unprotect(protectedData);
Console.WriteLine($"Unprotected payload: {Encoding.UTF8.GetString(unprotectedPayload)}");
}
catch (Exception ex)
{
Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
}

// try calling DangerousUnprotect


Console.WriteLine("Calling DangerousUnprotect...");
try
{
IPersistedDataProtector persistedProtector = protector as IPersistedDataProtector;
if (persistedProtector == null)
{
throw new Exception("Can't call DangerousUnprotect.");
}

bool requiresMigration, wasRevoked;


bool requiresMigration, wasRevoked;
var unprotectedPayload = persistedProtector.DangerousUnprotect(
protectedData: protectedData,
ignoreRevocationErrors: true,
requiresMigration: out requiresMigration,
wasRevoked: out wasRevoked);
Console.WriteLine($"Unprotected payload: {Encoding.UTF8.GetString(unprotectedPayload)}");
Console.WriteLine($"Requires migration = {requiresMigration}, was revoked = {wasRevoked}");
}
catch (Exception ex)
{
Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
}
}
}

/*
* SAMPLE OUTPUT
*
* Input: Hello!
* Protected payload: CfDJ8LHIzUCX1ZVBn2BZ...
* Round-tripped payload: Hello!
* Revoking all keys in the key ring...
* Calling Unprotect...
* CryptographicException: The key {...} has been revoked.
* Calling DangerousUnprotect...
* Unprotected payload: Hello!
* Requires migration = True, was revoked = True
*/
Configuração da Proteção de Dados no ASP.NET
Core
10/04/2018 • 1 min to read • Edit Online

Consulte estes tópicos para obter informações sobre a configuração da Proteção de Dados no ASP.NET Core:
Configurar a proteção de dados do ASP.NET Core
Uma visão geral sobre como configurar a Proteção de Dados do ASP.NET Core.
Tempo de vida e gerenciamento de chaves da Proteção de Dados
Informações sobre tempo de vida e gerenciamento de chaves da Proteção de Dados.
Política de suporte da Proteção de Dados em todo o computador
Detalhes de como definir uma política padrão em todo o computador para todos os aplicativos que usam a
Proteção de Dados.
Cenários sem reconhecimento de DI para a Proteção de Dados no ASP.NET Core
Como usar o tipo concreto DataProtectionProvider para usar a Proteção de Dados sem passar por
caminhos de código específicos de DI.
Configurar a proteção de dados do ASP.NET Core
27/04/2018 • 16 min to read • Edit Online

Por Rick Anderson


Quando o sistema de proteção de dados é inicializado, ele se aplica configurações padrão com base no
ambiente operacional. Essas configurações são geralmente apropriadas para os aplicativos executados em um
único computador. Há casos em que um desenvolvedor talvez queira alterar as configurações padrão:
O aplicativo é distribuído em vários computadores.
Por motivos de conformidade.
Nessas situações, o sistema de proteção de dados oferece uma API de configuração avançada.

AVISO
Semelhante aos arquivos de configuração, o anel de chave de proteção de dados devem ser protegido usando as
permissões apropriadas. Você pode optar por criptografar chaves em repouso, mas isso não impede que os invasores
criar novas chaves. Consequentemente, a segurança de seu aplicativo é afetada. O local de armazenamento configurado
com proteção de dados deve ter seu acesso limitado para o aplicativo em si, semelhante à forma como você protegerá
os arquivos de configuração. Por exemplo, se você optar por armazenar o anel de chave no disco, use as permissões do
sistema de arquivos. Certifique-se somente a identidade sob qual seu aplicativo web é executado tem leitura, gravação
e criação de acesso a esse diretório. Se você usar o armazenamento de tabela do Azure, apenas o aplicativo web deve
ter a capacidade de ler, gravar ou criar novas entradas no repositório de tabela, etc.
O método de extensão AddDataProtection retorna um IDataProtectionBuilder. IDataProtectionBuilder expõe
métodos de extensão que você pode encadear opções de configurar a proteção de dados.

PersistKeysToFileSystem
Para armazenar chaves em um compartilhamento UNC, em vez de no % LOCALAPPDATA % local padrão,
configurar o sistema com PersistKeysToFileSystem:

public void ConfigureServices(IServiceCollection services)


{
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"));
}

AVISO
Se você alterar o local de persistência de chave, o sistema não automaticamente criptografa as chaves em repouso,
desde que ele não sabe se a DPAPI é um mecanismo de criptografia apropriados.

ProtectKeysWith*
Você pode configurar o sistema para proteger as chaves em repouso chamando qualquer o ProtectKeysWith*
APIs de configuração. Considere o exemplo a seguir, que armazena as chaves em um compartilhamento UNC
e criptografa essas chaves em repouso com um certificado x. 509 específico:
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
.ProtectKeysWithCertificate("thumbprint");
}

Consulte chave de criptografia em repouso para obter mais exemplos e obter mais informações sobre os
mecanismos de criptografia de chave interna.

SetDefaultKeyLifetime
Para configurar o sistema para usar uma vida útil de 14 dias em vez do padrão 90 dias, use
SetDefaultKeyLifetime:

public void ConfigureServices(IServiceCollection services)


{
services.AddDataProtection()
.SetDefaultKeyLifetime(TimeSpan.FromDays(14));
}

SetApplicationName
Por padrão, o sistema de proteção de dados isola aplicativos umas das outras, mesmo que compartilham o
mesmo repositório de chave físico. Isso impede que os aplicativos Noções básicas sobre cargas protegidos
uns dos outros. Para compartilhar cargas protegidas entre dois aplicativos, use SetApplicationName com o
mesmo valor para cada aplicativo:

public void ConfigureServices(IServiceCollection services)


{
services.AddDataProtection()
.SetApplicationName("shared app name");
}

DisableAutomaticKeyGeneration
Você pode ter um cenário onde você não deseja que um aplicativo para reverter automaticamente chaves
(criar novas chaves), como eles se aproximarem expiração. Um exemplo disso pode ser configurados em um
relacionamento primário/secundário, em que apenas o aplicativo principal é responsável por questões de
gerenciamento de chaves e aplicativos secundários simplesmente tem uma exibição somente leitura do anel
de chave de aplicativos. Os aplicativos secundários podem ser configurados para tratar o anel de chave como
somente leitura ao configurar o sistema com DisableAutomaticKeyGeneration:

public void ConfigureServices(IServiceCollection services)


{
services.AddDataProtection()
.DisableAutomaticKeyGeneration();
}

Isolamento de aplicativo
Quando o sistema de proteção de dados é fornecido por um host do ASP.NET Core, ela isola
automaticamente aplicativos umas das outras, mesmo se esses aplicativos estejam executando sob a mesma
conta de processo de trabalho e estiver usando o mesmo material de chave mestra. Isso é semelhante ao
modificador IsolateApps do System. Web <machineKey > elemento.
O mecanismo de isolamento funciona, considerando cada aplicativo no computador local como um locatário
exclusivo, assim o IDataProtector raiz para qualquer aplicativo determinado automaticamente inclui a ID do
aplicativo como um discriminador. ID exclusiva do aplicativo vem de um dos seguintes locais:
1. Se o aplicativo é hospedado no IIS, o identificador exclusivo é o caminho de configuração do aplicativo.
Se um aplicativo é implantado em um ambiente de farm da web, esse valor deve ser estável, supondo
que os ambientes de IIS são configurados da mesma forma em todas as máquinas do web farm.
2. Se o aplicativo não está hospedado no IIS, o identificador exclusivo é o caminho físico do aplicativo.
O identificador exclusivo é projetado para sobreviver a reinicializações — do aplicativo individual e da própria
máquina.
Esse mecanismo de isolamento assume que os aplicativos não são mal-intencionados. Um aplicativo mal-
intencionado sempre pode afetar qualquer outro aplicativo em execução sob a mesma conta de processo de
trabalho. Em um ambiente de hospedagem compartilhado que os aplicativos são mutuamente confiáveis, o
provedor de hospedagem deve tomar medidas para garantir o isolamento de nível de sistema operacional
entre os aplicativos, incluindo a separação de repositórios de chave de base de aplicativos.
Se o sistema de proteção de dados não é fornecido por um host do ASP.NET Core (por exemplo, se você
instanciá-la por meio de DataProtectionProvider tipo concreto) isolamento do aplicativo é desabilitado por
padrão. Quando o isolamento de aplicativos estiver desabilitado, feitos pelo mesmo material de chave de
todos os aplicativos podem compartilhar cargas desde que eles fornecem apropriada fins. Para fornecer
isolamento de aplicativos nesse ambiente, chame o SetApplicationName método na configuração do objeto e
forneça um nome exclusivo para cada aplicativo.

Algoritmos de alteração com UseCryptographicAlgorithms


A pilha de proteção de dados permite que você altere o algoritmo padrão usado por chaves geradas
recentemente. A maneira mais simples de fazer isso é chamar UseCryptographicAlgorithms de retorno de
chamada de configuração:
ASP.NET Core 2.x
ASP.NET Core 1.x

services.AddDataProtection()
.UseCryptographicAlgorithms(
new AuthenticatedEncryptorConfiguration()
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
});

O padrão EncryptionAlgorithm é AES -256-CBC e o padrão ValidationAlgorithm é HMACSHA256. A política


padrão pode ser definida por um administrador do sistema por meio de um abrangendo política, mas uma
chamada explícita para UseCryptographicAlgorithms substitui a política padrão.
Chamando UseCryptographicAlgorithms permite que você especifique o algoritmo desejado em uma lista
predefinida de interno. Você não precisa se preocupar sobre a implementação do algoritmo. No cenário
acima, o sistema de proteção de dados tenta usar a implementação de CNG do AES se em execução no
Windows. Caso contrário, ele reverterá para o gerenciado System.Security.Cryptography.Aes classe.
Você pode especificar manualmente uma implementação por meio de uma chamada para
UseCustomCryptographicAlgorithms.
DICA
Alterar algoritmos não afeta as chaves existentes do anel de chave. Ela afeta apenas as chaves geradas recentemente.

Especificando algoritmos gerenciados personalizados


ASP.NET Core 2.x
ASP.NET Core 1.x
Para especificar algoritmos gerenciados personalizados, crie um
ManagedAuthenticatedEncryptorConfiguration instância que aponta para os tipos de implementação:

serviceCollection.AddDataProtection()
.UseCustomCryptographicAlgorithms(
new ManagedAuthenticatedEncryptorConfiguration()
{
// A type that subclasses SymmetricAlgorithm
EncryptionAlgorithmType = typeof(Aes),

// Specified in bits
EncryptionAlgorithmKeySize = 256,

// A type that subclasses KeyedHashAlgorithm


ValidationAlgorithmType = typeof(HMACSHA256)
});

Geralmente o *propriedades de tipo devem apontar para concreto, podem ser instanciadas (por meio de um
construtor sem parâmetros público) implementações de SymmetricAlgorithm e KeyedHashAlgorithm,
embora o sistema caso especial, como alguns valores typeof(Aes) para sua conveniência.

OBSERVAÇÃO
O SymmetricAlgorithm deve ter um comprimento de chave de ≥ 128 bits e um tamanho de bloco de ≥ 64 bits e ele
deve oferecer suporte à criptografia de modo CBC com preenchimento de PKCS #7. O KeyedHashAlgorithm deve ter
um tamanho de resumo de > = 128 bits, e ele deve oferecer suporte a chaves de comprimento igual ao comprimento
de resumo do algoritmo de hash. O KeyedHashAlgorithm não é estritamente necessária para ser HMAC.

Especificação de algoritmos personalizados de Windows CNG


ASP.NET Core 2.x
ASP.NET Core 1.x
Para especificar um algoritmo CNG Windows personalizado usando a criptografia de modo CBC com
validação HMAC, crie um CngCbcAuthenticatedEncryptorConfiguration instância que contém as informações
de algoritmos:
services.AddDataProtection()
.UseCustomCryptographicAlgorithms(
new CngCbcAuthenticatedEncryptorConfiguration()
{
// Passed to BCryptOpenAlgorithmProvider
EncryptionAlgorithm = "AES",
EncryptionAlgorithmProvider = null,

// Specified in bits
EncryptionAlgorithmKeySize = 256,

// Passed to BCryptOpenAlgorithmProvider
HashAlgorithm = "SHA256",
HashAlgorithmProvider = null
});

OBSERVAÇÃO
O algoritmo de criptografia simétrica de bloco deve ter um comprimento de chave de > = 128 bits, um tamanho de
bloco de > = 64 bits, e ele deve oferecer suporte à criptografia de modo CBC com preenchimento de PKCS #7. O
algoritmo de hash deve ter um tamanho de resumo de > = 128 bits e deve oferecer suporte à que está sendo aberta
com o BCRYPT_ALG_tratar_HMAC_sinalizador de sinalizador. O *propriedades do provedor podem ser definidas para
nulo para usar o provedor padrão para o algoritmo especificado. Consulte o BCryptOpenAlgorithmProvider
documentação para obter mais informações.

ASP.NET Core 2.x


ASP.NET Core 1.x
Para especificar um algoritmo CNG Windows personalizado usando a criptografia de modo Galois/contador
com a validação, crie um CngGcmAuthenticatedEncryptorConfiguration instância que contém as informações
de algoritmos:

services.AddDataProtection()
.UseCustomCryptographicAlgorithms(
new CngGcmAuthenticatedEncryptorConfiguration()
{
// Passed to BCryptOpenAlgorithmProvider
EncryptionAlgorithm = "AES",
EncryptionAlgorithmProvider = null,

// Specified in bits
EncryptionAlgorithmKeySize = 256
});

OBSERVAÇÃO
O algoritmo de criptografia simétrica de bloco deve ter um comprimento de chave de > = 128 bits, um tamanho de
bloco de exatamente de 128 bits, e ele deve oferecer suporte à criptografia do GCM. Você pode definir o
EncryptionAlgorithmProvider propriedade como nulo para usar o provedor padrão para o algoritmo especificado.
Consulte o BCryptOpenAlgorithmProvider documentação para obter mais informações.

Especificar outros algoritmos personalizados


Embora não exposta como uma API de primeira classe, o sistema de proteção de dados é extensível o
suficiente para permitir a especificação de praticamente qualquer tipo de algoritmo. Por exemplo, é possível
manter todas as chaves contidas dentro de um módulo HSM (Hardware Security) e fornecer uma
implementação personalizada do núcleo rotinas de criptografia e descriptografia. Consulte
IAuthenticatedEncryptor na extensibilidade da criptografia de núcleo para obter mais informações.

Chaves persistentes ao hospedar em um contêiner de Docker


Ao hospedar em um Docker contêiner, as chaves devem ser mantidas no:
Uma pasta que é um volume de Docker persiste por mais tempo de vida do contêiner, como um volume
compartilhado ou um volume montado de host.
Um provedor externo, como Azure Key Vault ou Redis.

Consulte também
Cenários sem reconhecimento de DI
Política para todo o computador
Gerenciamento de chaves de proteção de dados e o
tempo de vida no núcleo do ASP.NET
30/01/2018 • 5 min to read • Edit Online

Por Rick Anderson

Gerenciamento de chaves
O aplicativo tenta detectar seu ambiente operacional e controlar a configuração de chave por conta própria.
1. Se o aplicativo é hospedado em aplicativos do Azure, as chaves são mantidas para o
%HOME%\ASP.NET\DataProtection-Keys pasta. Esta pasta é apoiada pelo repositório de rede e é
sincronizada em todos os computadores que hospedam o aplicativo.
As chaves não são protegidas em repouso.
O DataProtection chaves pasta fornece o anel de chave para todas as instâncias de um aplicativo em
um slot de implantação única.
Slots de implantação separado, como preparação e produção, não compartilhem um anel de chave.
Quando você alternar entre os slots de implantação, por exemplo, troca de preparo para produção ou
usando A / B teste, qualquer aplicativo usando a proteção de dados não será capaz de descriptografar
dados armazenados usando o anel de chave dentro do slot anterior. Isso leva a usuários que estão
sendo conectados fora de um aplicativo que usa a autenticação de cookie padrão do ASP.NET Core,
pois ele usa a proteção de dados para proteger seus cookies. Se você desejar independente de slot
chave anéis, usar um provedor de anel de chave externa, como armazenamento Blob do Azure, Cofre
de chaves do Azure, um repositório SQL, ou o cache Redis.
2. Se o perfil de usuário estiver disponível, as chaves são mantidas para o
%LOCALAPPDATA%\ASP.NET\DataProtection-Keys pasta. Se o sistema operacional for Windows, as
chaves são criptografadas em repouso usando a DPAPI.
3. Se o aplicativo é hospedado no IIS, as chaves são persistentes no registro HKLM em uma chave do
registro especial que é ACLed apenas para a conta de processo do operador. As chaves são criptografadas
em repouso usando a DPAPI.
4. Se nenhuma dessas condições corresponderem, as chaves não são mantidas fora do processo atual.
Quando o processo é desligado, todos os geradas chaves serão perdidas.
O desenvolvedor está sempre no controle total e pode substituir como e onde as chaves são armazenadas. As
três primeiras opções acima devem fornecer padrões bom para a maioria dos aplicativos semelhante a como o
ASP.NET <machineKey > rotinas de geração automática funcionaram no passado. A opção de fallback, final é o
único cenário que requer que o desenvolvedor especifique configuração inicial se quiserem persistência de chave,
mas essa fallback ocorra apenas em situações raras.
Ao hospedar em um contêiner do Docker, chaves devem ser persistidas em uma pasta que é um volume de
Docker (um volume compartilhado ou um volume montado de host que persiste além do tempo de vida do
contêiner) ou em um provedor externo, como Azure Key Vault ou Redis. Um provedor externo também é útil em
cenários de farm da web se os aplicativos não podem acessar um volume compartilhado de rede (consulte
PersistKeysToFileSystem para obter mais informações).
AVISO
Se o desenvolvedor substitui as regras descritas acima e aponta o sistema de proteção de dados em um repositório de
chave específico, a criptografia automática de chaves em repouso está desabilitada. Proteção pode ser habilitada
novamente por meio de configuração.

Vida útil da chave


Por padrão, as chaves têm um tempo de vida de 90 dias. Quando uma chave expira, o aplicativo gera uma nova
chave automaticamente e define a nova chave como a chave ativa. Como chaves desativadas permanecerem no
sistema, seu aplicativo pode descriptografar os dados protegidos com eles. Consulte gerenciamento de chaves
para obter mais informações.

Algoritmos padrão
O algoritmo de proteção de carga padrão usado é AES -256-CBC para confidencialidade e HMACSHA256
autenticidade. Uma chave mestra de 512 bits, alterada a cada 90 dias, é usada para derivar as duas chaves
subdiretório usadas para esses algoritmos em uma base por carga. Consulte subchave derivação para obter mais
informações.

Consulte também
Extensibilidade de gerenciamento de chaves
Suporte a política de máquina de proteção de
dados no ASP.NET Core
10/04/2018 • 7 min to read • Edit Online

Por Rick Anderson


Quando em execução no Windows, o sistema de proteção de dados tem suporte limitado para definir uma
política de todo o computador padrão para todos os aplicativos que consomem a proteção de dados do ASP.NET
Core. A ideia geral é que um administrador pode querer alterar uma configuração padrão, como os algoritmos
usados ou a vida útil da chave, sem a necessidade de atualizar manualmente cada aplicativo no computador.

AVISO
O administrador do sistema pode definir a política padrão, mas eles não é possível impor a ele. O desenvolvedor do
aplicativo sempre pode substituir qualquer valor com um dos seus próprios escolhendo. A política padrão afeta somente os
aplicativos em que o desenvolvedor não foi especificado um valor explícito para uma configuração.

Configuração de política padrão


Para definir a política padrão, um administrador pode definir os valores conhecidos no registro do sistema na
seguinte chave do registro:
HKLM\SOFTWARE\Microsoft\DotNetPackages\Microsoft.AspNetCore.DataProtection
Se você estiver em um sistema operacional de 64 bits e quiser afetar o comportamento de aplicativos de 32 bits,
lembre-se de configurar o equivalente Wow6432Node a chave acima.
Os valores com suporte são mostrados abaixo.

VALOR TIPO DESCRIÇÃO

EncryptionType cadeia de caracteres Especifica os algoritmos que devem ser


usados para proteção de dados. O
valor deve ser CBC CNG, GCM CNG ou
gerenciado e é descrito com mais
detalhes abaixo.

DefaultKeyLifetime DWORD Especifica o tempo de vida para chaves


geradas recentemente. O valor é
especificado em dias e deve ser > = 7.

KeyEscrowSinks cadeia de caracteres Especifica os tipos que são usados para


caução de chaves. O valor é uma lista
separada por ponto-e-vírgula de
Coletores de caução de chaves, onde
cada elemento na lista é o nome
qualificado do assembly de um tipo
que implementa IKeyEscrowSink.

Tipos de criptografia
Se EncryptionType é CBC CNG, o sistema está configurado para usar uma codificação de bloco simétrica de
modo CBC para confidencialidade e HMAC autenticidade com serviços fornecidos pelo Windows CNG (consulte
especificando algoritmos personalizados de Windows CNG para mais detalhes). Os seguintes valores adicionais
são suportados, cada uma correspondendo a uma propriedade do tipo CngCbcAuthenticatedEncryptionSettings.

VALOR TIPO DESCRIÇÃO

EncryptionAlgorithm cadeia de caracteres O nome de um algoritmo de


criptografia simétrica bloco entendido
pelo CNG. Esse algoritmo é aberto no
modo CBC.

EncryptionAlgorithmProvider cadeia de caracteres O nome da implementação do


provedor CNG que pode produzir o
algoritmo EncryptionAlgorithm.

EncryptionAlgorithmKeySize DWORD O comprimento (em bits) da chave


derivada para o algoritmo de
criptografia simétrica de bloco.

HashAlgorithm cadeia de caracteres O nome de um algoritmo de hash


entendido pelo CNG. Esse algoritmo é
aberto no modo HMAC.

HashAlgorithmProvider cadeia de caracteres O nome da implementação do


provedor CNG que pode produzir o
algoritmo HashAlgorithm.

Se EncryptionType é GCM CNG, o sistema está configurado para usar uma codificação de bloco simétrica de
modo Galois/contador para autenticidade e confidencialidade com serviços fornecidos pelo Windows CNG
(consulte especificando algoritmos personalizados de Windows CNG Para obter mais detalhes). Os seguintes
valores adicionais são suportados, cada uma correspondendo a uma propriedade do tipo
CngGcmAuthenticatedEncryptionSettings.

VALOR TIPO DESCRIÇÃO

EncryptionAlgorithm cadeia de caracteres O nome de um algoritmo de


criptografia simétrica bloco entendido
pelo CNG. Esse algoritmo é aberto no
modo de Galois/contador.

EncryptionAlgorithmProvider cadeia de caracteres O nome da implementação do


provedor CNG que pode produzir o
algoritmo EncryptionAlgorithm.

EncryptionAlgorithmKeySize DWORD O comprimento (em bits) da chave


derivada para o algoritmo de
criptografia simétrica de bloco.

Se EncryptionType for gerenciado, o sistema está configurado para usar um SymmetricAlgorithm gerenciado
para confidencialidade e KeyedHashAlgorithm autenticidade (consulte especificando personalizado gerenciado
algoritmos para obter mais detalhes). Os seguintes valores adicionais são suportados, cada uma correspondendo
a uma propriedade do tipo ManagedAuthenticatedEncryptionSettings.

VALOR TIPO DESCRIÇÃO


VALOR TIPO DESCRIÇÃO

EncryptionAlgorithmType cadeia de caracteres O nome qualificado do assembly de


um tipo que implementa
SymmetricAlgorithm.

EncryptionAlgorithmKeySize DWORD O comprimento (em bits) da chave


para derivar o algoritmo de criptografia
simétrica.

ValidationAlgorithmType cadeia de caracteres O nome qualificado do assembly de


um tipo que implementa
KeyedHashAlgorithm.

Se EncryptionType tiver qualquer valor diferente de nulo ou vazio, o sistema de proteção de dados gera uma
exceção durante a inicialização.

AVISO
Ao configurar uma configuração de política padrão que envolve os nomes de tipo (EncryptionAlgorithmType,
ValidationAlgorithmType, KeyEscrowSinks), os tipos devem estar disponíveis para o aplicativo. Isso significa que para
aplicativos em execução no CLR de área de trabalho, os assemblies que contêm esses tipos devem estar presentes no
Cache de Assembly Global (GAC). Para aplicativos do ASP.NET Core em execução no .NET Core, os pacotes que contêm
esses tipos devem ser instalados.
Cenários de reconhecimento não DI para proteção
de dados no ASP.NET Core
03/03/2018 • 4 min to read • Edit Online

Por Rick Anderson


O sistema de proteção de dados do ASP.NET Core é normalmente adicionada a um contêiner de serviço e
consumido por componentes dependentes por meio de injeção de dependência (DI). No entanto, há casos em
que isso não é possível ou desejado, especialmente ao importar o sistema para um aplicativo existente.
Para dar suporte a esses cenários, o Microsoft.AspNetCore.DataProtection.Extensions pacote fornece um tipo
concreto, DataProtectionProvider, que oferece uma maneira simples de usar a proteção de dados sem depender
de injeção de dependência. O DataProtectionProvider tipo implementa IDataProtectionProvider. Construindo
DataProtectionProvider requer apenas fornecer um DirectoryInfo instância para indicar onde as chaves de
criptografia do provedor devem ser armazenadas, como mostrado no exemplo de código a seguir:
using System;
using System.IO;
using Microsoft.AspNetCore.DataProtection;

public class Program


{
public static void Main(string[] args)
{
// Get the path to %LOCALAPPDATA%\myapp-keys
var destFolder = Path.Combine(
System.Environment.GetEnvironmentVariable("LOCALAPPDATA"),
"myapp-keys");

// Instantiate the data protection system at this folder


var dataProtectionProvider = DataProtectionProvider.Create(
new DirectoryInfo(destFolder));

var protector = dataProtectionProvider.CreateProtector("Program.No-DI");


Console.Write("Enter input: ");
var input = Console.ReadLine();

// Protect the payload


var protectedPayload = protector.Protect(input);
Console.WriteLine($"Protect returned: {protectedPayload}");

// Unprotect the payload


var unprotectedPayload = protector.Unprotect(protectedPayload);
Console.WriteLine($"Unprotect returned: {unprotectedPayload}");

Console.WriteLine();
Console.WriteLine("Press any key...");
Console.ReadKey();
}
}

/*
* SAMPLE OUTPUT
*
* Enter input: Hello world!
* Protect returned: CfDJ8FWbAn6...ch3hAPm1NJA
* Unprotect returned: Hello world!
*
* Press any key...
*/

Por padrão, o DataProtectionProvider tipo concreto não criptografa a chave primas persisti-los antes do sistema
de arquivos. Isso é para dar suporte a cenários onde os pontos de desenvolvedor para um compartilhamento de
rede e o sistema de proteção de dados não é possível deduzir automaticamente um mecanismo de criptografia
de chave apropriado em repouso.
Além disso, o DataProtectionProvider tipo concreto não isolar os aplicativos por padrão. Todos os aplicativos
usando o mesmo diretório principal podem compartilhar cargas, contanto que seus finalidade parâmetros
corresponder.
O DataProtectionProvider construtor aceita um retorno de chamada de configuração opcional que pode ser
usado para ajustar os comportamentos do sistema. O exemplo a seguir demonstra o isolamento de restauração
com uma chamada explícita para SetApplicationName. O exemplo também demonstra como configurar o
sistema para criptografar automaticamente chaves persistentes usando Windows DPAPI. Se o diretório aponta
para um compartilhamento UNC, você poderá distribuir um certificado compartilhado por todos os
computadores relevantes e para configurar o sistema para usar a criptografia baseada em certificado com uma
chamada para ProtectKeysWithCertificate.
using System;
using System.IO;
using Microsoft.AspNetCore.DataProtection;

public class Program


{
public static void Main(string[] args)
{
// Get the path to %LOCALAPPDATA%\myapp-keys
var destFolder = Path.Combine(
System.Environment.GetEnvironmentVariable("LOCALAPPDATA"),
"myapp-keys");

// Instantiate the data protection system at this folder


var dataProtectionProvider = DataProtectionProvider.Create(
new DirectoryInfo(destFolder),
configuration =>
{
configuration.SetApplicationName("my app name");
configuration.ProtectKeysWithDpapi();
});

var protector = dataProtectionProvider.CreateProtector("Program.No-DI");


Console.Write("Enter input: ");
var input = Console.ReadLine();

// Protect the payload


var protectedPayload = protector.Protect(input);
Console.WriteLine($"Protect returned: {protectedPayload}");

// Unprotect the payload


var unprotectedPayload = protector.Unprotect(protectedPayload);
Console.WriteLine($"Unprotect returned: {unprotectedPayload}");

Console.WriteLine();
Console.WriteLine("Press any key...");
Console.ReadKey();
}
}

DICA
Instâncias de DataProtectionProvider tipo concreto são caros de criar. Se um aplicativo mantém várias instâncias desse
tipo e se eles estão usando o mesmo diretório de armazenamento de chaves, pode afetar o desempenho do aplicativo. Se
você usar o DataProtectionProvider tipo, recomendamos que você cria esse tipo de uma vez e reutilizá-la tanto quanto
possível. O DataProtectionProvider tipo e todos os IDataProtector instâncias criadas a partir dela são thread-safe para
chamadores vários.
APIs de extensibilidade de proteção de dados do
ASP.NET Core
10/04/2018 • 1 min to read • Edit Online

Extensibilidade da criptografia básica


Extensibilidade de gerenciamento de chaves
APIs diversas
Extensibilidade da criptografia de núcleo no núcleo
do ASP.NET
10/04/2018 • 11 min to read • Edit Online

AVISO
Tipos que implementam qualquer uma das seguintes interfaces devem ser thread-safe para chamadores vários.

IAuthenticatedEncryptor
O IAuthenticatedEncryptor interface é o bloco de construção básico do subsistema de criptografia. Em geral,
há um IAuthenticatedEncryptor por chave e a instância IAuthenticatedEncryptor encapsula todas as material de
chave de criptografia e algoritmos informações necessárias para executar operações criptográficas.
Como o nome sugere, o tipo é responsável por fornecer serviços de criptografia e descriptografia autenticados.
Expõe as APIs a seguir.
Descriptografar (ArraySegment texto cifrado, ArraySegment additionalAuthenticatedData): byte]
Criptografar (ArraySegment texto sem formatação, ArraySegment additionalAuthenticatedData): byte]
O método Encrypt retorna um blob que inclui o texto não criptografado enciphered e uma marca de autenticação.
A marca de autenticação deve abranger os dados adicionais autenticados (AAD ), embora o AAD em si não precisa
ser recuperável da carga final. O método Decrypt valida a marca de autenticação e retorna a carga deciphered.
Todas as falhas (exceto ArgumentNullException e semelhante) devem ser homogenized para
CryptographicException.

OBSERVAÇÃO
A própria instância IAuthenticatedEncryptor, na verdade, não precisa conter o material da chave. Por exemplo, a
implementação pudesse ser delegado a um HSM para todas as operações.

Como criar um IAuthenticatedEncryptor


ASP.NET Core 2.x
ASP.NET Core 1.x
O IAuthenticatedEncryptorFactory interface representa um tipo que sabe como criar um
IAuthenticatedEncryptor instância. Sua API é o seguinte.
CreateEncryptorInstance (chave IKey): IAuthenticatedEncryptor
Para qualquer instância específica de IKey os qualquer intermediária autenticada criada por seu método
CreateEncryptorInstance deve ser consideradas equivalentes, como o exemplo de código abaixo.
// we have an IAuthenticatedEncryptorFactory instance and an IKey instance
IAuthenticatedEncryptorFactory factory = ...;
IKey key = ...;

// get an encryptor instance and perform an authenticated encryption operation


ArraySegment<byte> plaintext = new ArraySegment<byte>(Encoding.UTF8.GetBytes("plaintext"));
ArraySegment<byte> aad = new ArraySegment<byte>(Encoding.UTF8.GetBytes("AAD"));
var encryptor1 = factory.CreateEncryptorInstance(key);
byte[] ciphertext = encryptor1.Encrypt(plaintext, aad);

// get another encryptor instance and perform an authenticated decryption operation


var encryptor2 = factory.CreateEncryptorInstance(key);
byte[] roundTripped = encryptor2.Decrypt(new ArraySegment<byte>(ciphertext), aad);

// the 'roundTripped' and 'plaintext' buffers should be equivalent

IAuthenticatedEncryptorDescriptor (ASP.NET Core 2. x somente)


ASP.NET Core 2.x
ASP.NET Core 1.x
O IAuthenticatedEncryptorDescriptor interface representa um tipo que sabe como exportar em si para XML.
Sua API é o seguinte.
ExportToXml() : XmlSerializedDescriptorInfo

Serialização XML
A principal diferença entre IAuthenticatedEncryptor e IAuthenticatedEncryptorDescriptor é que o descritor sabe
como criar o Criptografador e fornecê-lo com os argumentos válidos. Considere um IAuthenticatedEncryptor cuja
implementação depende do SymmetricAlgorithm e KeyedHashAlgorithm. Trabalho do Criptografador é consumir
esses tipos, mas não necessariamente souber onde esses tipos de origem, para que ele realmente não é possível
gravar uma descrição adequada do como recriar a mesmo se o aplicativo for reiniciado. O descritor de atua como
um nível superior sobre isso. Desde que o descritor sabe como criar a instância do Criptografador (por exemplo,
sabe como criar os algoritmos necessários), ele pode serializar esse conhecimento em formato XML para que a
instância do Criptografador pode ser recriada após a redefinição de um aplicativo.
O descritor de pode ser serializado por meio de sua rotina de ExportToXml. Esta rotina retorna um
XmlSerializedDescriptorInfo que contém duas propriedades: a representação de XElement do descritor e o tipo
que representa um IAuthenticatedEncryptorDescriptorDeserializer que pode ser usado para lembrar Esse
descritor fornecido o XElement correspondente.
O descritor de serializado pode conter informações confidenciais, como o material de chave de criptografia. O
sistema de proteção de dados tem suporte interno para criptografar informações antes de ele tem persistidos
para armazenamento. Para tirar proveito disso, o descritor deve marcar o elemento que contém informações
confidenciais com o nome do atributo "requiresEncryption" (xmlns
"http://schemas.asp.net/2015/03/dataProtection"), valor "true".

DICA
Há um auxiliar de API para a configuração deste atributo. Chame o método de extensão que
XElement.markasrequiresencryption() localizado no namespace
Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.

Também pode haver casos onde o descritor serializado não contêm informações confidenciais. Considere
novamente o caso de uma chave de criptografia armazenada em um HSM. Não é possível gravar o descritor de
material de chave ao serializar em si, pois o HSM não expõe o material em forma de texto sem formatação. Em
vez disso, o descritor pode gravar a versão de chave de linha de chave (se o HSM permite exportar dessa forma)
ou o identificador exclusivo do HSM para a chave.

IAuthenticatedEncryptorDescriptorDeserializer
O IAuthenticatedEncryptorDescriptorDeserializer interface representa um tipo que sabe como desserializar
uma instância IAuthenticatedEncryptorDescriptor de um XElement. Ela expõe um único método:
ImportFromXml (elemento de XElement): IAuthenticatedEncryptorDescriptor
O método ImportFromXml usa o XElement foi retornado por IAuthenticatedEncryptorDescriptor.ExportToXml e
cria um equivalente a IAuthenticatedEncryptorDescriptor original.
Tipos que implementam IAuthenticatedEncryptorDescriptorDeserializer devem ter um dos dois construtores
públicos a seguir:
.ctor(IServiceProvider)
.ctor()

OBSERVAÇÃO
O IServiceProvider transmitido ao construtor pode ser nulo.

A fábrica de nível superior


ASP.NET Core 2.x
ASP.NET Core 1.x
O AlgorithmConfiguration classe representa um tipo que sabe como criar IAuthenticatedEncryptorDescriptor
instâncias. Ela expõe uma única API.
CreateNewDescriptor() : IAuthenticatedEncryptorDescriptor
Pense AlgorithmConfiguration como a fábrica de nível superior. A configuração funciona como um modelo. Ela
inclui informações de algoritmos (por exemplo, essa configuração produz descritores com uma chave mestra de
AES -128-GCM ), mas ela ainda não está associada com uma chave específica.
Quando CreateNewDescriptor é chamado, novo material de chave é criada somente para essa chamada e um
novo IAuthenticatedEncryptorDescriptor é produzido que encapsula o material de chave e as informações de
algoritmos necessária para consumir o material. O material da chave pode ser criado no software (e mantido na
memória), ele pode ser criado e mantido em um HSM e assim por diante. O ponto fundamental é que as duas
chamadas para CreateNewDescriptor nunca devem criar instâncias de IAuthenticatedEncryptorDescriptor
equivalentes.
O tipo de AlgorithmConfiguration serve como ponto de entrada para rotinas de criação da chave como chave
automática. Para alterar a implementação para todas as chaves futuras, defina a propriedade de
AuthenticatedEncryptorConfiguration em KeyManagementOptions.
Extensibilidade de gerenciamento de chaves no
núcleo do ASP.NET
10/04/2018 • 14 min to read • Edit Online

DICA
Leitura de gerenciamento de chaves seção antes de ler esta seção, como explica alguns dos conceitos fundamentais dessas
APIs.

AVISO
Tipos que implementam qualquer uma das seguintes interfaces devem ser thread-safe para chamadores vários.

Chave
O IKey interface é a representação básica de uma chave em criptográfico. A chave de termo é usada aqui no
sentido abstrato, não no sentido de literal de "material de chave criptográfica". Uma chave tem as seguintes
propriedades:
Datas de expiração, a criação e a ativação
Status de revogação
Identificador de chave (uma GUID )
ASP.NET Core 2.x
ASP.NET Core 1.x
Além disso, IKey expõe um CreateEncryptor método que pode ser usado para criar um IAuthenticatedEncryptor
instância associada a essa chave.

OBSERVAÇÃO
Há uma API para recuperar o material criptográfico bruto de um IKey instância.

IKeyManager
O IKeyManager interface representa um objeto responsável pelo armazenamento de chaves geral, a recuperação e
manipulação. Ela apresenta três operações de alto nível:
Crie uma nova chave e mantê-lo para o armazenamento.
Obter todas as chaves de armazenamento.
Revogar uma ou mais chaves e manter as informações de revogação para o armazenamento.
AVISO
Escrevendo um IKeyManager é uma tarefa muito avançada e a maioria dos desenvolvedores não deve tentar. Em vez disso,
a maioria dos desenvolvedores deve aproveitar os recursos oferecidos pelo XmlKeyManager classe.

XmlKeyManager
O XmlKeyManager tipo é a implementação concreta de caixa de entrada da IKeyManager . Ele fornece vários
recursos úteis, incluindo caução de chaves e criptografia de chaves em repouso. As chaves no sistema são
representadas como elementos XML (especificamente, XElement).
XmlKeyManager depende de vários outros componentes no decorrer de atender às suas tarefas:
ASP.NET Core 2.x
ASP.NET Core 1.x
AlgorithmConfiguration , que determina os algoritmos usados por novas chaves.
IXmlRepository , que controla onde as chaves são mantidas no armazenamento.
IXmlEncryptor [opcional], que permite a criptografia de chaves em repouso.
IKeyEscrowSink [opcional], que fornece serviços de caução de chaves.

Abaixo estão os diagramas de alto nível que indicam como esses componentes são conectados em XmlKeyManager .
ASP.NET Core 2.x
ASP.NET Core 1.x

Criação de chave / CreateNewKey


Na implementação de CreateNewKey , o AlgorithmConfiguration componente é usado para criar uma única
IAuthenticatedEncryptorDescriptor , que, em seguida, é serializado como XML. Se houver um coletor de caução de
chaves, o XML bruto de (não criptografado) é fornecido para o coletor para armazenamento de longo prazo. O
XML não criptografado é executado por meio de um IXmlEncryptor (se necessário) para gerar o documento XML
criptografado. Este documento criptografado é persistido no armazenamento de longo prazo por meio de
IXmlRepository . ( Se nenhum IXmlEncryptor é configurado, o documento não criptografado é mantido no
IXmlRepository .)

ASP.NET Core 2.x


ASP.NET Core 1.x
Recuperação de chave / GetAllKeys
Na implementação de GetAllKeys , representa chaves de documentos XML e revogações são lidas do subjacente
IXmlRepository . Se esses documentos são criptografados, o sistema automaticamente descriptografá-los.
XmlKeyManager cria apropriada IAuthenticatedEncryptorDescriptorDeserializer instâncias para desserializar os
documentos de volta para o IAuthenticatedEncryptorDescriptor instâncias, que, em seguida, são quebradas em
individuais IKey instâncias. Esta coleção de IKey instâncias é retornado ao chamador.
Obter mais informações sobre os elementos XML específicos podem ser encontradas na documento do formato
de armazenamento de chaves.

IXmlRepository
O IXmlRepository interface representa um tipo que pode persistir XML e recuperar o XML de um repositório de
backup. Ela apresenta duas APIs:
GetAllElements() : IReadOnlyCollection
StoreElement (elemento XElement, friendlyName de cadeia de caracteres)
Implementações de IXmlRepository não é necessário analisar o XML passando por eles. Eles devem tratar os
documentos XML como opaco e permitir que camadas superiores se preocupar sobre como gerar e analisar os
documentos.
Há dois tipos internos concretos que implementam IXmlRepository : FileSystemXmlRepository e
RegistryXmlRepository . Consulte o documento de provedores de armazenamento de chaves para obter mais
informações. Registrando um personalizado IXmlRepository seria da maneira adequada para usar um
armazenamento de backup diferente, por exemplo, o armazenamento de BLOBs do Azure.
Para alterar o repositório padrão do nível de aplicativo, registre um personalizado IXmlRepository instância:
ASP.NET Core 2.x
ASP.NET Core 1.x

services.Configure<KeyManagementOptions>(options => options.XmlRepository = new MyCustomXmlRepository());

IXmlEncryptor
O IXmlEncryptor interface representa um tipo que pode criptografar um elemento XML de texto sem formatação.
Ela apresenta uma única API:
Criptografar (plaintextElement XElement): EncryptedXmlInfo
Se um serializado IAuthenticatedEncryptorDescriptor contém todos os elementos marcados como "requer
criptografia", em seguida, XmlKeyManager executará desses elementos por meio de IXmlEncryptor do Encrypt
método e ele serão mantido o elemento enciphered em vez de elemento de texto sem formatação para o
IXmlRepository . A saída de Encrypt método é um EncryptedXmlInfo objeto. O objeto é um wrapper que contém
ambos os resultantes enciphered XElement e o tipo que representa um IXmlDecryptor que pode ser usado para o
elemento correspondente de decifrar.
Há quatro tipos internos concretos que implementam IXmlEncryptor :
CertificateXmlEncryptor
DpapiNGXmlEncryptor
DpapiXmlEncryptor
NullXmlEncryptor

Consulte o criptografia de chave no documento de rest para obter mais informações.


Para alterar o mecanismo padrão de chave criptografia em repouso todo o aplicativo, registre um personalizado
IXmlEncryptor instância:

ASP.NET Core 2.x


ASP.NET Core 1.x

services.Configure<KeyManagementOptions>(options => options.XmlEncryptor = new MyCustomXmlEncryptor());

IXmlDecryptor
O IXmlDecryptor interface representa um tipo que sabe como descriptografar um XElement que foi enciphered
por meio de um IXmlEncryptor . Ela apresenta uma única API:
Descriptografar (encryptedElement XElement): XElement
O Decrypt método desfaz a criptografia executada pelo IXmlEncryptor.Encrypt . Em geral, cada concreto
IXmlEncryptor implementação terá um concreto correspondente IXmlDecryptor implementação.
Tipos que implementam IXmlDecryptor devem ter um dos dois construtores públicos a seguir:
.ctor(IServiceProvider)
.ctor()

OBSERVAÇÃO
O IServiceProvider passado para o construtor pode ser nulo.

IKeyEscrowSink
O IKeyEscrowSink interface representa um tipo que pode executar caução de informações confidenciais. Lembre-
se de que descritores serializados podem conter informações confidenciais (por exemplo, o material criptográfico),
e isso é o que levou à introdução do IXmlEncryptor digite em primeiro lugar. No entanto, acidentes acontecem e
anéis de chave podem ser excluídos ou corrompidos.
A interface de caução fornece uma trava de escape de emergência, permitindo o acesso ao XML serializado bruto
antes que ele é transformado por qualquer configurado IXmlEncryptor. A interface expõe uma única API:
Armazenamento (keyId Guid, o elemento de XElement)
Ele é até o IKeyEscrowSink implementação para lidar com o elemento fornecido de maneira segura consistente
com a política de negócios. Uma possível implementação pode ser para o coletor de caução criptografar o
elemento XML usando um certificado x. 509 corporativo conhecido em que a chave privada do certificado tem
foram enviado; o CertificateXmlEncryptor tipo pode ajudar com isso. O IKeyEscrowSink implementação também
é responsável por manter o elemento fornecido adequadamente.
Por padrão nenhum mecanismo caução estiver habilitado, embora os administradores de servidor podem
configurar isso globalmente. Também pode ser configurado programaticamente por meio de
IDataProtectionBuilder.AddKeyEscrowSink método conforme mostrado no exemplo abaixo. O AddKeyEscrowSink
espelho de sobrecargas do método de IServiceCollection.AddSingleton e IServiceCollection.AddInstance
sobrecargas, como IKeyEscrowSink instâncias devem ser singletons. Se vários IKeyEscrowSink instâncias
estiverem registradas, cada um deles será chamado durante a geração de chaves para as chaves podem ser
mantidas em garantia para diversos mecanismos simultaneamente.
Há uma API para ler o material de um IKeyEscrowSink instância. Isso é consistente com a teoria de design do
mecanismo de caução: destinado disponibilizar o material da chave para uma autoridade confiável e como o
aplicativo em si não é uma autoridade confiável, ele não deve ter acesso ao seu próprio material caucionada.
O código de exemplo a seguir demonstra a criação e registrando um IKeyEscrowSink onde as chaves são
mantidas em garantia, de modo que somente os membros do grupo "administradores de CONTOSODomain"
poderá recuperá-los.

OBSERVAÇÃO
Para executar este exemplo, você deve estar em um domínio do Windows 8 / máquina Windows Server 2012 e o
controlador de domínio devem ser Windows Server 2012 ou posterior.

using System;
using System.IO;
using System.Xml.Linq;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.AspNetCore.DataProtection.XmlEncryption;
using Microsoft.Extensions.DependencyInjection;

public class Program


{
public static void Main(string[] args)
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys"))
.ProtectKeysWithDpapi()
.AddKeyEscrowSink(sp => new MyKeyEscrowSink(sp));
var services = serviceCollection.BuildServiceProvider();

// get a reference to the key manager and force a new key to be generated
Console.WriteLine("Generating new key...");
var keyManager = services.GetService<IKeyManager>();
keyManager.CreateNewKey(
activationDate: DateTimeOffset.Now,
expirationDate: DateTimeOffset.Now.AddDays(7));
}

// A key escrow sink where keys are escrowed such that they
// can be read by members of the CONTOSO\Domain Admins group.
private class MyKeyEscrowSink : IKeyEscrowSink
{
private readonly IXmlEncryptor _escrowEncryptor;
private readonly IXmlEncryptor _escrowEncryptor;

public MyKeyEscrowSink(IServiceProvider services)


{
// Assuming I'm on a machine that's a member of the CONTOSO
// domain, I can use the Domain Admins SID to generate an
// encrypted payload that only they can read. Sample SID from
// https://technet.microsoft.com/library/cc778824(v=ws.10).aspx.
_escrowEncryptor = new DpapiNGXmlEncryptor(
"SID=S-1-5-21-1004336348-1177238915-682003330-512",
DpapiNGProtectionDescriptorFlags.None,
services);
}

public void Store(Guid keyId, XElement element)


{
// Encrypt the key element to the escrow encryptor.
var encryptedXmlInfo = _escrowEncryptor.Encrypt(element);

// A real implementation would save the escrowed key to a


// write-only file share or some other stable storage, but
// in this sample we'll just write it out to the console.
Console.WriteLine($"Escrowing key {keyId}");
Console.WriteLine(encryptedXmlInfo.EncryptedElement);

// Note: We cannot read the escrowed key material ourselves.


// We need to get a member of CONTOSO\Domain Admins to read
// it for us in the event we need to recover it.
}
}
}

/*
* SAMPLE OUTPUT
*
* Generating new key...
* Escrowing key 38e74534-c1b8-4b43-aea1-79e856a822e5
* <encryptedKey>
* <!-- This key is encrypted with Windows DPAPI-NG. -->
* <!-- Rule: SID=S-1-5-21-1004336348-1177238915-682003330-512 -->
* <value>MIIIfAYJKoZIhvcNAQcDoIIIbTCCCGkCAQ...T5rA4g==</value>
* </encryptedKey>
*/
APIs de proteção de dados de diversos principais do
ASP.NET
10/04/2018 • 1 min to read • Edit Online

AVISO
Tipos que implementam qualquer uma das seguintes interfaces devem ser thread-safe para chamadores vários.

ISecret
O ISecret interface representa um valor de segredo, como o material de chave de criptografia. Ele contém a
superfície de API a seguir:
Length : int

Dispose() : void

WriteSecretIntoBuffer(ArraySegment<byte> buffer) : void

O WriteSecretIntoBuffer método preenche o buffer fornecido com o valor bruto de segredo. O motivo pelo qual
essa API usa o buffer como um parâmetro em vez de retornar um byte[] diretamente é que isso permite que o
chamador fixar o objeto de buffer, limitar a exposição de segredo para o coletor de lixo gerenciada.
O Secret tipo é uma implementação concreta de ISecret onde o valor de segredo é armazenado na memória no
processo. Em plataformas Windows, o valor do segredo é criptografado por meio de CryptProtectMemory.
Implementação da proteção de dados do ASP.NET
Core
10/04/2018 • 1 min to read • Edit Online

Detalhes de criptografia autenticada


Derivação de subchaves e criptografia autenticada
Cabeçalhos de contexto
Gerenciamento de chaves
Provedores de armazenamento de chaves
Criptografia de chave em repouso
Imutabilidade de chave e configurações
Formato do armazenamento de chaves
Provedores de proteção de dados efêmeros
Detalhes de criptografia autenticada no núcleo do
ASP.NET
10/04/2018 • 4 min to read • Edit Online

Chamadas para IDataProtector.Protect são as operações de criptografia autenticada. O método Protect oferece
confidencialidade e a autenticidade e ela é vinculada à cadeia finalidade que foi usada para essa instância
específica do IDataProtector derivam da sua raiz IDataProtectionProvider.
IDataProtector.Protect usa um parâmetro de texto sem formatação do byte [] e produz uma byte [] protegido
carga, cujo formato é descrito abaixo. (Também há uma sobrecarga de método de extensão que usa um parâmetro
de texto sem formatação da cadeia de caracteres e retorna uma carga protegido de cadeia de caracteres. Se essa
API é usada ainda terá o formato de carga protegido o abaixo de estrutura, mas será codificado base64url.)

Formato de conteúdo protegido


O formato de carga protegido consiste em três componentes principais:
Um cabeçalho mágico de 32 bits que identifica a versão do sistema de proteção de dados.
Uma id de 128 bits chave que identifica a chave usada para proteger essa carga específica.
O restante da carga protegido é específico para o Criptografador encapsulado por esta chave. No exemplo
a seguir a chave representa um AES -256-CBC + Criptografador HMACSHA256 e a carga é mais
subdividida da seguinte maneira: * modificador chave A 128 bits. * Um vetor de inicialização de 128 bits. *
48 bytes de saída de AES -256-CBC. * Uma marca de autenticação HMACSHA256.
Uma carga protegido exemplo ilustrada abaixo.

09 F0 C9 F0 80 9C 81 0C 19 66 19 40 95 36 53 F8
AA FF EE 57 57 2F 40 4C 3F 7F CC 9D CC D9 32 3E
84 17 99 16 EC BA 1F 4A A1 18 45 1F 2D 13 7A 28
79 6B 86 9C F8 B7 84 F9 26 31 FC B1 86 0A F1 56
61 CF 14 58 D3 51 6F CF 36 50 85 82 08 2D 3F 73
5F B0 AD 9E 1A B2 AE 13 57 90 C8 F5 7C 95 4E 6A
8A AA 06 EF 43 CA 19 62 84 7C 11 B2 C8 71 9D AA
52 19 2E 5B 4C 1E 54 F0 55 BE 88 92 12 C1 4B 5E
52 C9 74 A0

O formato de carga acima os primeiros 32 bits ou 4 bytes são o cabeçalho magic identifica a versão (09 F0 C9 F0)
O próximos 128 bits ou 16 bytes é o identificador de chave (80 9 81 de C 0C 19 66 19 40 95 36 53 F8 AA FF EE
57)
O restante contém a carga e é específico para o formato usado.
AVISO
Todas as cargas protegidas para uma determinada chave começa com o mesmo cabeçalho de 20 bytes (valor mágico, id de
chave). Os administradores podem usar esse fato para fins de diagnóstico para aproximar quando uma carga foi gerada. Por
exemplo, a carga acima corresponde à chave {0c819c80-6619-4019-9536-53f8aaffee57}. Se depois de verificar se o
repositório de chave achar que a data de ativação dessa chave específica era 2015-01-01 e data de expiração foi 2015-03-
01, é razoável pressupor que a carga (se não violada) foi gerada dentro dessa janela, dê ou levar um pequeno fator em
ambos os lados.
Derivação subchave e criptografia autenticada no
núcleo do ASP.NET
10/04/2018 • 8 min to read • Edit Online

A maioria das chaves do anel de chave contém alguma forma de entropia e terá informações algorítmicos
informando "criptografia de modo CBC + validação HMAC" ou "criptografia GCM + validação". Nesses casos, nos
referimos a entropia inserida como o material de chave mestre (ou KM ) para essa chave e podemos executar uma
função de derivação de chave para derivar as chaves que serão usadas para operações de criptografia reais.

OBSERVAÇÃO
As chaves são abstratas e uma implementação personalizada pode não se comportar como abaixo. Se a chave fornece sua
própria implementação do IAuthenticatedEncryptor em vez de usar uma das nossas fábricas internas, o mecanismo
descrito nesta seção não se aplica.

Dados autenticados adicionais e subchave derivação


O IAuthenticatedEncryptor interface serve como a interface principal para todas as operações de criptografia
autenticada. Seu Encrypt método usa dois buffers: texto sem formatação e additionalAuthenticatedData (AAD ). O
fluxo de conteúdo de texto sem formatação inalterado a chamada para IDataProtector.Protect , mas o AAD é
gerado pelo sistema e consiste em três componentes:
1. 32 bits magic cabeçalho 09 F0 C9 F0 que identifica esta versão do sistema de proteção de dados.
2. A id de chave de 128 bits.
3. Uma cadeia de caracteres de comprimento variável formada da cadeia de finalidade que criou o
IDataProtector que está executando esta operação.

Como o AAD é exclusivo para a tupla de todos os três componentes, podemos usá-lo derivar novas chaves KM
em vez de usar KM em si em todos os nossos operações criptográficas. Para todas as chamadas para
IAuthenticatedEncryptor.Encrypt , ocorre o processo de derivação de chaves a seguir:

( K_E, K_H ) = SP800_108_CTR_HMACSHA512(K_M, AAD, contextHeader || keyModifier)


Aqui, estamos ligando para o NIST SP800-108 KDF no modo de contador (consulte NIST SP800-108, SEC 5.1)
com os seguintes parâmetros:
Chave de derivação de chave (KDK) = K_M
PRF = HMACSHA512
label = additionalAuthenticatedData
context = contextHeader || keyModifier
O cabeçalho de contexto é de comprimento variável e essencialmente serve como uma impressão digital dos
algoritmos para a qual estamos estiver derivando K_E e K_H. O modificador de chave é uma cadeia de caracteres
de 128 bits gerada aleatoriamente para cada chamada Encrypt e serve para garantir com grandes probabilidade
que KE e KH são exclusivas para essa operação de criptografia de autenticação específico, mesmo se todas as
outras entradas para o KDF é constante.
Para criptografia de modo CBC + operações de validação de HMAC, | K_E | é o comprimento da chave de
criptografia simétrica bloco, e | K_H | é o tamanho do resumo da rotina de HMAC. Para criptografia GCM +
operações de validação, | K_H | = 0.

Criptografia de modo CBC + validação HMAC


Depois de K_E é gerado por meio do mecanismo acima, podemos gerar um vetor de inicialização aleatória e
executar o algoritmo de criptografia simétrica de bloco para codificar o texto não criptografado. O vetor de
inicialização e o texto cifrado são, em seguida, executar a rotina HMAC inicializada com a chave K_H para produzir
Mac. Esse processo e o valor de retorno é representado graficamente abaixo.

output:= keyModifier || iv || E_cbc (K_E,iv,data ) || HMAC (K_H, iv || E_cbc (K_E,iv,data ))

OBSERVAÇÃO
O IDataProtector.Protect implementação será preceda o cabeçalho magic e a id de chave a saída antes de retorná-lo ao
chamador. Porque o cabeçalho magic e a id de chave são implicitamente parte do AAD, e porque o modificador de chave é
passado como entrada para o KDF, isso significa que cada byte único da carga final retornada é autenticado pelo Mac.

Criptografia de modo Galois/contador + validação


Quando K_E é gerado por meio do mecanismo acima, podemos gerar um nonce de 96 bits aleatório e executar o
algoritmo de criptografia simétrica de bloco para codificar o texto não criptografado e produzir a marca de
autenticação de 128 bits.

saída: = keyModifier | | nonce | | E_gcm (dados de uso único, K_E,) | | authTag


OBSERVAÇÃO
Embora GCM nativamente dá suporte ao conceito de AAD, podemos estiver ainda de alimentação AAD somente para o KDF
original, aceitar para passar uma cadeia de caracteres vazia para GCM para seu parâmetro AAD. A razão para isso é dupla.
Primeiro, para dar suporte a agilidade nunca queremos usar K_M diretamente como a chave de criptografia. Além disso,
GCM impõe requisitos de exclusividade muito estrita em suas entradas. Define a probabilidade de que a rotina de
criptografia do GCM é sempre invocado em dois ou mais distintos de dados de entrada com o mesmo (chave, nonce) par
não deve exceder 2 ^ 32. Se corrigir K_E não foi possível realizar mais de 2 ^ 32 operações de criptografia antes de podemos
executar afoul da 2 ^ limitar -32. Isso pode parecer um grande número de operações, mas um servidor web de alto tráfego
pode passar por solicitações de 4 bilhões em alguns dias, dentro do tempo de vida normal para essas chaves. Para manter a
conformidade de 2 ^ limite de probabilidade-32, podemos continuar a usar um modificador de chave de 128 bits e nonce de
96 bits, o que aumenta a contagem de operação utilizável para qualquer K_M determinado. Para manter a simplicidade de
design compartilha o caminho do código KDF entre as operações CBC e GCM e como AAD já é considerado o KDF não é
necessário encaminhá-lo para a rotina do GCM.
Cabeçalhos de contexto no núcleo do ASP.NET
10/04/2018 • 16 min to read • Edit Online

Plano de fundo e a teoria


No sistema de proteção de dados, uma "chave" significa que um objeto que pode fornecer serviços de criptografia
de autenticação. Cada chave é identificado por uma id exclusiva (uma GUID ) e transporta informações
algorítmicos e material entropic. Ele destina-se que cada chave realizar entropia exclusiva, mas o sistema não pode
impor que e precisamos para desenvolvedores que podem alterar manualmente o anel de chave, modificando as
informações de algoritmos de uma chave existente do anel de chave de conta. Para alcançar nossos requisitos de
segurança fornecidos nesses casos, o sistema de proteção de dados tem um conceito de agilidade criptográfica,
que permite a com segurança usando um único valor entropic entre vários algoritmos de criptografia.
A maioria dos sistemas que oferecem suporte a agilidade criptográfica de fazer isso, incluindo algumas
informações de identificação sobre o algoritmo de carga. OID do algoritmo geralmente é uma boa candidata para
isso. No entanto, um problema que tivemos é que há várias maneiras de especificar o mesmo algoritmo: "AES"
(CNG ) e o gerenciado Aes, AesManaged, AesCryptoServiceProvider, AesCng e RijndaelManaged (fornecida
parâmetros específicos) classes são, na verdade, todos os mesmos coisa e é necessário manter um mapeamento
de tudo isso para a identificação de objeto correta. Se um desenvolvedor deseja fornecer um algoritmo
personalizado (ou até mesmo outra implementação do AES!), eles precisam Conte-nos sua OID. Essa etapa de
registro extra facilita a configuração do sistema particularmente penoso.
Recuando, decidimos que podemos foram chegando o problema de direção errada. Um OID informa o que é o
algoritmo, mas, na verdade, não importa sobre isso. Se for necessário usar um único valor entropic com segurança
em dois algoritmos diferentes, não é necessário para que possamos saber o que os algoritmos realmente são. O
que é realmente importante é como eles se comportam. Qualquer algoritmo de codificação de bloco simétrica
razoável também é uma forte permutação pseudoaleatórios (PRP ): corrija as entradas (chave, cadeia de texto sem
formatação de modo, o IV,) e a saída de texto cifrado com grandes probabilidade serão diferente dos outros
qualquer codificação de bloco simétrica algoritmo considerando as entradas do mesmo. Da mesma forma,
qualquer função de hash com chave razoável também é uma função pseudoaleatórios forte (PRF ) e fornecido um
conjunto fixo de entrada sua saída predominantemente serão diferente de qualquer outra função de hash com
chave.
Podemos usar esse conceito de alta segurança PRPs e PRFs para criar um cabeçalho de contexto. Esse cabeçalho
de contexto atua essencialmente como uma impressão digital estável sobre os algoritmos em uso para qualquer
operação especificada e fornece a agilidade criptográfica necessária ao sistema de proteção de dados. Esse
cabeçalho pode ser reproduzido e é usado posteriormente como parte do processo de derivação de subchave. Há
duas maneiras diferentes para criar o cabeçalho de contexto dependendo os modos de operação dos algoritmos
subjacentes.

Criptografia de modo CBC + autenticação HMAC


O cabeçalho de contexto consiste dos seguintes componentes:
[16 bits] O valor 00 00, o que é um marcador que significa "criptografia CBC + autenticação HMAC".
[32 bits] O comprimento da chave (em bytes, big-endian) do algoritmo de criptografia simétrica de bloco.
[32 bits] O tamanho de bloco (em bytes, big-endian) do algoritmo de criptografia simétrica de bloco.
[32 bits] O comprimento da chave (em bytes, big-endian) do algoritmo HMAC. (Atualmente o tamanho da
chave sempre coincide com o tamanho do resumo.)
[32 bits] O tamanho (em bytes, big-endian) resumo do algoritmo HMAC.
EncCBC (K_E, IV, ""), que é a saída do algoritmo de criptografia simétrica bloco recebe uma entrada de
cadeia de caracteres vazia e onde o IV é um vetor de zeros. A construção de K_E é descrita abaixo.
MAC (K_H, ""), que é a saída do algoritmo HMAC recebe uma entrada de cadeia de caracteres vazia. A
construção de K_H é descrita abaixo.
Idealmente, poderíamos passar vetores de zeros para K_E e K_H. No entanto, queremos evitar a situação em que o
algoritmo subjacente verifica a existência de baixa segurança chaves antes de executar quaisquer operações
(especialmente DES e 3DES ), que impede usando um padrão simple ou repeatable como um vetor de zeros.
Em vez disso, usamos o NIST SP800-108 KDF no modo de contador (consulte NIST SP800-108, SEC 5.1) com
uma chave de comprimento zero, o rótulo e o contexto e a HMACSHA512 como o PRF subjacente. Podemos
derivar | K_E | + | K_H | bytes de saída, em seguida, decompor o resultado em K_E e K_H próprios.
Matematicamente, isso é representado como a seguir.
( K_E || K_H ) = SP800_108_CTR (prf = HMACSHA512, key = "", label = "", context = "")
Exemplo: AES de 192 de CBC + HMACSHA256
Por exemplo, considere o caso em que o algoritmo de criptografia simétrica de bloco é AES de 192 de CBC e o
algoritmo de validação é HMACSHA256. O sistema poderia gerar o cabeçalho de contexto usando as etapas a
seguir.
Primeiro, permitem (K_E | | K_H) = SP800_108_CTR (prf = HMACSHA512, chave = "", rótulo = "", contexto = ""),
onde | K_E | = 192 bits e | K_H | = 256 bits por algoritmos especificados. Isso leva a K_E = 5BB6. 21DD e K_H =
A04A. 00A9 no exemplo a seguir:

5B B6 C9 83 13 78 22 1D 8E 10 73 CA CF 65 8E B0
61 62 42 71 CB 83 21 DD A0 4A 05 00 5B AB C0 A2
49 6F A5 61 E3 E2 49 87 AA 63 55 CD 74 0A DA C4
B7 92 3D BF 59 90 00 A9

Em seguida, calcular Enc_CBC (K_E, IV, "") para AES -192-CBC fornecido IV = 0 * e K_E como acima.
result := F474B1872B3B53E4721DE19C0841DB6F
Em seguida, o MAC de computação (K_H, "") para HMACSHA256 determinado K_H como acima.
result := D4791184B996092EE1202F36E8608FA8FBD98ABDFF5402F264B1D7211536220C
Isso gera o cabeçalho de contexto completo abaixo:

00 00 00 00 00 18 00 00 00 10 00 00 00 20 00 00
00 20 F4 74 B1 87 2B 3B 53 E4 72 1D E1 9C 08 41
DB 6F D4 79 11 84 B9 96 09 2E E1 20 2F 36 E8 60
8F A8 FB D9 8A BD FF 54 02 F2 64 B1 D7 21 15 36
22 0C

Esse cabeçalho de contexto é a impressão digital do par de algoritmo de criptografia autenticada (criptografia AES
de 192 de CBC + HMACSHA256 validação). Os componentes, conforme descrito acima são:
o marcador (00 00)
o comprimento da chave de codificação de bloco (00 00 00 18)
o tamanho de bloco de codificação de bloco (00 00 00 10)
o comprimento da chave HMAC (00 00 00 20)
o tamanho do resumo HMAC (00 00 00 20)
a codificação de bloco saída PRP (F4 74 - DB 6F ) e
a saída PRF HMAC (D4 79 - end).

OBSERVAÇÃO
A criptografia de modo CBC + HMAC cabeçalho de contexto de autenticação baseia-se da mesma maneira,
independentemente de se as implementações de algoritmos são fornecidas pelo Windows CNG ou por tipos
SymmetricAlgorithm e KeyedHashAlgorithm gerenciados. Isso permite que os aplicativos executados em diferentes sistemas
operacionais confiável produzem o mesmo cabeçalho de contexto, embora as implementações de algoritmos diferem entre
sistemas operacionais. (Na prática, o KeyedHashAlgorithm não precisa ser um HMAC adequado. Ele pode ser qualquer tipo
de algoritmo de hash com chave.)

Exemplo: 3DES -192-CBC + HMACSHA1


Primeiro, permitem (K_E | | K_H) = SP800_108_CTR (prf = HMACSHA512, chave = "", rótulo = "", contexto = ""),
onde | K_E | = 192 bits e | K_H | = 160 bits por algoritmos especificados. Isso leva a K_E = A219. E2BB e K_H =
DC4A. B464 no exemplo a seguir:

A2 19 60 2F 83 A9 13 EA B0 61 3A 39 B8 A6 7E 22
61 D9 F8 6C 10 51 E2 BB DC 4A 00 D7 03 A2 48 3E
D1 F7 5A 34 EB 28 3E D7 D4 67 B4 64

Em seguida, calcular Enc_CBC (K_E, IV, "") para 3DES -192-CBC fornecido IV = 0 * e K_E como acima.
result := ABB100F81E53E10E
Em seguida, o MAC de computação (K_H, "") para HMACSHA1 determinado K_H como acima.
result := 76EB189B35CF03461DDF877CD9F4B1B4D63A7555
Isso gera o cabeçalho de contexto completo que é uma impressão digital do autenticado par de algoritmo do
encryption (criptografia 3DES -192-CBC + HMACSHA1 validação), mostrado abaixo:

00 00 00 00 00 18 00 00 00 08 00 00 00 14 00 00
00 14 AB B1 00 F8 1E 53 E1 0E 76 EB 18 9B 35 CF
03 46 1D DF 87 7C D9 F4 B1 B4 D6 3A 75 55

Os componentes são divididos da seguinte maneira:


o marcador (00 00)
o comprimento da chave de codificação de bloco (00 00 00 18)
o tamanho de bloco de codificação de bloco (00 00 00 08)
o comprimento da chave HMAC (00 00 00 14)
o tamanho do resumo HMAC (00 00 00 14)
a codificação de bloco saída PRP (B1 AB - E1 0E ) e
a saída PRF HMAC (76 EB - end).

Criptografia de modo Galois/contador + autenticação


O cabeçalho de contexto consiste dos seguintes componentes:
[16 bits] O valor 00 01, que é um marcador que significa "criptografia GCM + autenticação".
[32 bits] O comprimento da chave (em bytes, big-endian) do algoritmo de criptografia simétrica de bloco.
[32 bits] O tamanho nonce (em bytes, big-endian) usado durante as operações de criptografia autenticada.
(Para nosso sistema, isso é fixo em tamanho nonce = 96 bits.)
[32 bits] O tamanho de bloco (em bytes, big-endian) do algoritmo de criptografia simétrica de bloco. (Para
GCM, isso é fixo em tamanho de bloco = 128 bits.)
[32 bits] O autenticação marca tamanho (em bytes, big-endian) produzido pela função criptografia
autenticada. (Para nosso sistema, isso é fixo em tamanho de marca = 128 bits.)
[128 bits] A marca de Enc_GCM (K_E, nonce, ""), que é a saída do algoritmo de criptografia simétrica bloco
recebe uma entrada de cadeia de caracteres vazia e onde nonce é um vetor de zeros de 96 bits.
K_E é obtida usando o mesmo mecanismo como a criptografia CBC + o cenário de autenticação de HMAC. No
entanto, já que não há nenhum K_H em jogo aqui, essencialmente temos | K_H | = 0, e o algoritmo recolhe para o
formulário abaixo.
K_E = SP800_108_CTR (prf = HMACSHA512, key = "", label = "", context = "")
Exemplo: AES -256-GCM
Primeiramente, vamos K_E = SP800_108_CTR (prf = HMACSHA512, chave = "", rótulo = "", contexto = ""), onde |
K_E | = 256 bits.
K_E := 22BC6F1B171C08C4AE2F27444AF8FC8B3087A90006CAEA91FDCFB47C1B8733B8
Em seguida, calcular a marca de autenticação de Enc_GCM (K_E, nonce, "") para AES -256-GCM fornecido nonce =
096 e K_E como acima.
result := E7DCCE66DF855A323A6BB7BD7A59BE45
Isso gera o cabeçalho de contexto completo abaixo:

00 01 00 00 00 20 00 00 00 0C 00 00 00 10 00 00
00 10 E7 DC CE 66 DF 85 5A 32 3A 6B B7 BD 7A 59
BE 45

Os componentes são divididos da seguinte maneira:


o marcador (00 01)
o comprimento da chave de codificação de bloco (00 00 00 20)
o tamanho de nonce (00 00 00 0 C )
o tamanho de bloco de codificação de bloco (00 00 00 10)
o tamanho de marca de autenticação (00 00 00 10) e
a marca de autenticação da execução de codificação de bloco (controlador de domínio E7 - end).
Gerenciamento de chaves no núcleo do ASP.NET
10/04/2018 • 12 min to read • Edit Online

O sistema de proteção de dados gerencia automaticamente o tempo de vida das chaves mestras usado para
proteger e Desproteger cargas. Cada chave pode existir em um dos quatro estágios:
Criado - a chave existe em anel chave, mas ainda não foi ativada. A chave não deve ser usada para novas
operações de proteção até que tenha decorrido tempo suficiente que a chave tenha tido a oportunidade de
se propague para todos os computadores que estão consumindo anel essa chave.
Ativo - a chave existe do anel de chave e deve ser usado para todas as operações de proteção de novo.
A chave expirada - ficou seu tempo de vida natural e não deve ser usada para novas operações de
proteção.
A chave revogada - estiver comprometida e não deve ser usada para novas operações de proteção.
Todas as chaves expiradas, ativas e criadas podem ser usadas para desproteger cargas de entrada. Chaves
revogadas por padrão não podem ser usadas para desproteger cargas, mas o desenvolvedor do aplicativo pode
substituir esse comportamento se necessário.

AVISO
O desenvolvedor pode ser tentado para excluir uma chave do anel de chave (por exemplo, excluindo o arquivo
correspondente do sistema de arquivos). Nesse ponto, todos os dados protegidos pela chave é indecifráveis
permanentemente e não há nenhuma substituição de emergência como ocorre com chaves revogadas. Exclusão de uma
chave é realmente destrutivo comportamento e, consequentemente, o sistema de proteção de dados não expõe nenhuma
API de primeira classe para executar esta operação.

Seleção de chave padrão


Quando o sistema de proteção de dados lê o anel de chave do repositório de backup, ele tentará localizar uma
chave "padrão" do anel de chave. A chave padrão é usada para novas operações de proteção.
A heurística geral é que o sistema de proteção de dados escolhe a chave com a data mais recente de ativação
como a chave padrão. (Há um pequeno fator para permitir o relógio do servidor-para-servidor distorção.) Se a
chave expirou ou foi revogada, e se o aplicativo não tiver desabilitado automática da chave de geração, uma nova
chave será gerada com a ativação imediata pelo chave expiração e sem interrupção diretiva abaixo.
O motivo pelo qual o sistema de proteção de dados gera uma nova chave imediatamente em vez de fazer fallback
para uma chave diferente é que a nova geração de chave deve ser tratada como uma expiração implícita de todas
as chaves que foram ativados antes da nova chave. A ideia geral é que novas chaves podem ter sido configuradas
com algoritmos diferentes ou mecanismos de criptografia em repouso que as chaves antigas, e o sistema deve
preferir a configuração atual em vez de fazer o fallback.
Há uma exceção. Se o desenvolvedor do aplicativo tiver desabilitado a geração automática de chaves, em seguida,
o sistema de proteção de dados deve escolher algo como a chave padrão. Neste cenário de fallback, o sistema
escolherá a chave não revogado com a data de ativação mais recente, com preferência para chaves que tem tido
tempo para ser propagada para outros computadores no cluster. O sistema de fallback pode acabar escolhendo
uma chave padrão expiradas como resultado. O sistema de fallback nunca escolherá uma chave revogada como a
chave padrão e se o anel de chave está vazio ou foi revogado cada chave, em seguida, o sistema produzirá um
erro na inicialização.
Expiração e sem interrupção
Quando uma chave é criada, ela automaticamente concedeu uma data de ativação de {agora + 2 dias} e uma data
de expiração de {agora + 90 dias}. O atraso de 2 dias antes da ativação fornece o momento chave se propague
através do sistema. Ou seja, permite que outros aplicativos apontando para o armazenamento de backup
observar a chave em seu próximo período de atualização automática, portanto, maximizando a probabilidade de
que quando a chave de anel faz se tornar ativo foi propagado para todos os aplicativos que talvez precisem usá-
la.
Se a chave padrão expirará dentro de 2 dias e o anel de chave ainda não tiver uma chave que ficará ativa após a
expiração da chave padrão, o sistema de proteção de dados persistirá automaticamente uma nova chave para o
anel de chave. Essa nova chave tem uma data de ativação de {data de validade da chave padrão} e a data de
expiração de {agora + 90 dias}. Isso permite que o sistema automaticamente reverter chaves regularmente sem
interrupção do serviço.
Pode haver circunstâncias em que uma chave será criada com a ativação imediata. Um exemplo seria quando o
aplicativo não tiver sido executada por um tempo e todas as chaves do anel de chave expiradas. Quando isso
acontece, a chave é fornecida uma data de ativação de {agora} sem o atraso de ativação de 2 dias normal.
O tempo de vida de chave padrão é de 90 dias, embora isso é configurado como no exemplo a seguir.

services.AddDataProtection()
// use 14-day lifetime instead of 90-day lifetime
.SetDefaultKeyLifetime(TimeSpan.FromDays(14));

Um administrador também pode alterar o padrão para todo o sistema, embora uma chamada explícita para
SetDefaultKeyLifetime substituirá qualquer política de todo o sistema. O tempo de vida de chave padrão não
pode ser menor do que 7 dias.

Atualização automática anel de chave


Quando o sistema de proteção de dados inicializado, ele lê o anel de chave do repositório subjacente e armazena
em cache na memória. Esse cache permite proteger e Desproteger operações continuem sem atingir o
armazenamento de backup. O sistema verifica automaticamente o repositório de backup para alterações de
aproximadamente a cada 24 horas ou quando a chave padrão atual expirar, o que ocorrer primeiro.

AVISO
Os desenvolvedores devem muito raramente (se) precisa usar APIs de gerenciamento de chave diretamente. O sistema de
proteção de dados executará gerenciamento automático de chaves, conforme descrito acima.

O sistema de proteção de dados expõe uma interface IKeyManager que pode ser usado para inspecionar e fazer
alterações para o anel de chave. O sistema de DI que forneceu a instância do IDataProtectionProvider também
pode fornecer uma instância de IKeyManager para seu consumo. Como alternativa, você pode extrair o
IKeyManager diretamente do IServiceProvider como no exemplo a seguir.

Qualquer operação que modifica o anel de chave (Criando uma nova chave explicitamente ou uma revogação)
invalida o cache na memória. A próxima chamada para Protect ou Unprotect fará com que o sistema de
proteção de dados reler o anel de chave e recriar o cache.
O exemplo a seguir demonstra como usar o IKeyManager interface para inspecionar e manipular o anel de chave,
incluindo a revogação existente chaves e gerar uma nova chave manualmente.

using System;
using System.IO;
using System.IO;
using System.Threading;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.Extensions.DependencyInjection;

public class Program


{
public static void Main(string[] args)
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddDataProtection()
// point at a specific folder and use DPAPI to encrypt keys
.PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys"))
.ProtectKeysWithDpapi();
var services = serviceCollection.BuildServiceProvider();

// perform a protect operation to force the system to put at least


// one key in the key ring
services.GetDataProtector("Sample.KeyManager.v1").Protect("payload");
Console.WriteLine("Performed a protect operation.");
Thread.Sleep(2000);

// get a reference to the key manager


var keyManager = services.GetService<IKeyManager>();

// list all keys in the key ring


var allKeys = keyManager.GetAllKeys();
Console.WriteLine($"The key ring contains {allKeys.Count} key(s).");
foreach (var key in allKeys)
{
Console.WriteLine($"Key {key.KeyId:B}: Created = {key.CreationDate:u}, IsRevoked =
{key.IsRevoked}");
}

// revoke all keys in the key ring


keyManager.RevokeAllKeys(DateTimeOffset.Now, reason: "Revocation reason here.");
Console.WriteLine("Revoked all existing keys.");

// add a new key to the key ring with immediate activation and a 1-month expiration
keyManager.CreateNewKey(
activationDate: DateTimeOffset.Now,
expirationDate: DateTimeOffset.Now.AddMonths(1));
Console.WriteLine("Added a new key.");

// list all keys in the key ring


allKeys = keyManager.GetAllKeys();
Console.WriteLine($"The key ring contains {allKeys.Count} key(s).");
foreach (var key in allKeys)
{
Console.WriteLine($"Key {key.KeyId:B}: Created = {key.CreationDate:u}, IsRevoked =
{key.IsRevoked}");
}
}
}

/*
* SAMPLE OUTPUT
*
* Performed a protect operation.
* The key ring contains 1 key(s).
* Key {1b948618-be1f-440b-b204-64ff5a152552}: Created = 2015-03-18 22:20:49Z, IsRevoked = False
* Revoked all existing keys.
* Added a new key.
* The key ring contains 2 key(s).
* Key {1b948618-be1f-440b-b204-64ff5a152552}: Created = 2015-03-18 22:20:49Z, IsRevoked = True
* Key {2266fc40-e2fb-48c6-8ce2-5fde6b1493f7}: Created = 2015-03-18 22:20:51Z, IsRevoked = False
*/
Armazenamento de chaves
O sistema de proteção de dados tem uma heurística na qual ele tenta deduzir um local de armazenamento de
chave apropriado e criptografia no mecanismo de rest automaticamente. Isso também é configurável pelo
desenvolvedor do aplicativo. Os documentos a seguir discutem as implementações de caixa de entrada desses
mecanismos:
Na caixa de provedores de armazenamento de chaves
Na caixa de criptografia de chave nos provedores de rest
Provedores de armazenamento de chaves no núcleo
do ASP.NET
10/04/2018 • 4 min to read • Edit Online

Por padrão, o sistema de proteção de dados emprega uma heurística para determinar onde o material de chave de
criptografia deve ser persistente. O desenvolvedor pode substituir a heurística e especificar manualmente o local.

OBSERVAÇÃO
Se você especificar um local de persistência de chave explícita, o sistema de proteção de dados irá cancelar o registro a
criptografia de chave padrão no mecanismo de rest que a heurística fornecida, para que as chaves não serão criptografadas
em repouso. É recomendável que você adicionalmente especificar um mecanismo de criptografia de chave explícita para
aplicativos de produção.

O sistema de proteção de dados é fornecido com vários provedores de armazenamento de chaves da caixa de
entrada.

Sistema de arquivos
Estimamos que muitos aplicativos usarão um repositório chave baseado no sistema de arquivos. Para configurar
isso, chame o PersistKeysToFileSystem rotina de configuração, conforme mostrado abaixo. Forneça um
DirectoryInfo apontando para o repositório onde as chaves devem ser armazenadas.

sc.AddDataProtection()
// persist keys to a specific directory
.PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys\"));

O DirectoryInfo pode apontar para um diretório no computador local ou ela pode apontar para uma pasta em
um compartilhamento de rede. Se apontando para um diretório no computador local (e o cenário é que apenas os
aplicativos no computador local precisa usar esse repositório), considere o uso de Windows DPAPI para
criptografar as chaves em repouso. Caso contrário, considere o uso de um certificado x. 509 para criptografar as
chaves em repouso.

Azure e Redis
O Microsoft.AspNetCore.DataProtection.AzureStorage e Microsoft.AspNetCore.DataProtection.Redis pacotes
permitem armazenar as chaves de proteção de dados no armazenamento do Azure ou um cache Redis. As chaves
podem ser compartilhadas entre várias instâncias de um aplicativo web. Seu aplicativo ASP.NET Core pode
compartilhar os cookies de autenticação ou proteção CSRF entre vários servidores. Para configurar o Azure,
chame um do PersistKeysToAzureBlobStorage sobrecargas conforme mostrado abaixo.

public void ConfigureServices(IServiceCollection services)


{
services.AddDataProtection()
.PersistKeysToAzureBlobStorage(new Uri("<blob URI including SAS token>"));

services.AddMvc();
}
Consulte também o código de teste do Azure.
Para configurar o Redis, chame um do PersistKeysToRedis sobrecargas conforme mostrado abaixo.

public void ConfigureServices(IServiceCollection services)


{
// Connect to Redis database.
var redis = ConnectionMultiplexer.Connect("<URI>");
services.AddDataProtection()
.PersistKeysToRedis(redis, "DataProtection-Keys");

services.AddMvc();
}

Para obter mais informações, consulte o seguinte:


StackExchange.Redis ConnectionMultiplexer
Cache Redis do Azure
Código de teste de redis.

Registro
Às vezes, o aplicativo talvez não tenha acesso de gravação para o sistema de arquivos. Considere um cenário em
que um aplicativo está em execução como uma conta de serviço virtual (como a identidade do pool de aplicativos
do w3wp.exe). Nesses casos, o administrador pode ter provisionado uma chave do registro ACLed apropriado
para a identidade da conta de serviço. Chamar o PersistKeysToRegistry rotina de configuração, conforme
mostrado abaixo. Forneça um RegistryKey apontando para o local onde os valores/chaves de criptografia deve
ser armazenados.

sc.AddDataProtection()
// persist keys to a specific location in the system registry
.PersistKeysToRegistry(Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Sample\keys"));

Se você usar o registro do sistema como um mecanismo de persistência, considere o uso de Windows DPAPI
para criptografar as chaves em repouso.

Repositório de chave personalizado


Se os mecanismos de caixa de entrada não forem apropriados, o desenvolvedor pode especificar seu próprio
mecanismo de persistência de chave, fornecendo um personalizado IXmlRepository .
Criptografia de chave em repouso no núcleo do
ASP.NET
10/04/2018 • 7 min to read • Edit Online

Por padrão, o sistema de proteção de dados emprega uma heurística para determinar o material de chave de
criptografia como devem ser criptografados em repouso. O desenvolvedor pode substituir a heurística e
especificar manualmente como chaves devem ser criptografadas em repouso.

OBSERVAÇÃO
Se você especificar uma criptografia de chave explícita no mecanismo de rest, o sistema de proteção de dados irá cancelar o
registro o mecanismo de armazenamento de chave padrão que a heurística fornecida. Você deve especificar um mecanismo
de armazenamento de chave explícita, caso contrário, o sistema de proteção de dados não será iniciado.

O sistema de proteção de dados é fornecido com três mecanismos de criptografia de chave na caixa.

Windows DPAPI
Esse mecanismo está disponível apenas no Windows.
Quando o Windows DPAPI é usado, material de chave será criptografada por meio de CryptProtectData antes de
ser persistidos para armazenamento. A DPAPI é um mecanismo de criptografia apropriadas para os dados que
nunca serão lidas fora do computador atual (no entanto é possível fazer essas chaves até do Active Directory;
Consulte DPAPI e perfis móveis). Por exemplo configurar a criptografia de chave em repouso DPAPI.

sc.AddDataProtection()
// only the local user account can decrypt the keys
.ProtectKeysWithDpapi();

Se ProtectKeysWithDpapi é chamado sem parâmetros, somente a conta de usuário atual do Windows pode
decifrar o material da chave persistente. Opcionalmente, você pode especificar que qualquer conta de usuário no
computador (não apenas a conta de usuário atual) deve ser capaz de decifrar o material da chave, conforme
mostrado no exemplo abaixo.

sc.AddDataProtection()
// all user accounts on the machine can decrypt the keys
.ProtectKeysWithDpapi(protectToLocalMachine: true);

Certificado x. 509
Esse mecanismo não está disponível em .NET Core 1.0 ou 1.1 .
Se seu aplicativo é distribuído em vários computadores, pode ser conveniente para distribuir um certificado x.
509 compartilhado entre as máquinas e configurar aplicativos para usar este certificado para criptografia de
chaves em repouso. Veja abaixo um exemplo.
sc.AddDataProtection()
// searches the cert store for the cert with this thumbprint
.ProtectKeysWithCertificate("3BCE558E2AD3E0E34A7743EAB5AEA2A9BD2575A0");

Devido às limitações do .NET Framework somente os certificados com chaves particulares CAPI têm suporte.
Consulte criptografia baseada em certificado com o Windows DPAPI-NG abaixo para obter possíveis soluções
para essas limitações.

Windows DPAPI-NG
Esse mecanismo está disponível somente no Windows 8 / Windows Server 2012 e posterior.
Começando com o Windows 8, o sistema operacional oferece suporte a DPAPI-NG (também chamado de CNG
DPAPI). Microsoft dispõe seu cenário de uso da seguinte maneira.
Computação em nuvem, no entanto, geralmente exige que conteúdo criptografado em um computador ser
descriptografado em outro. Portanto, começando com o Windows 8, estendido a ideia de usar uma API
relativamente simples para abranger os cenários de nuvem da Microsoft. Essa nova API chamada DPAPI-NG,
permite que você compartilhe com segurança segredos (chaves, senhas, material de chave) e mensagens
protegendo-os para um conjunto de entidades que podem ser usados para desprotegê-los em diferentes
computadores após a autorização e autenticação adequada.
De sobre DPAPI CNG
A entidade de segurança é codificada como uma regra de proteção do descritor. Considere o exemplo abaixo, que
criptografa material de chave, de modo que somente o usuário associado a um domínio com o SID especificado
pode descriptografar o material da chave.

sc.AddDataProtection()
// uses the descriptor rule "SID=S-1-5-21-..."
.ProtectKeysWithDpapiNG("SID=S-1-5-21-...",
flags: DpapiNGProtectionDescriptorFlags.None);

Também há uma sobrecarga sem parâmetros de ProtectKeysWithDpapiNG . Este é um método conveniente para
especificar a regra "SID = meu", onde meu é o SID da conta de usuário do Windows atual.

sc.AddDataProtection()
// uses the descriptor rule "SID={current account SID}"
.ProtectKeysWithDpapiNG();

Nesse cenário, o controlador de domínio do AD é responsável por distribuir as chaves de criptografia usadas
pelas operações NG DPAPI. O usuário de destino poderá decifrar a carga criptografada de qualquer computador
ingressado no domínio (desde que o processo está sendo executado sob sua identidade).

Criptografia baseada em certificado com o Windows DPAPI-NG


Se você estiver executando no Windows 8.1 / Windows Server 2012 R2 ou posterior, você pode usar Windows
DPAPI-NG para executar a criptografia baseada em certificado, mesmo se o aplicativo estiver em execução no
.NET Core. Para tirar proveito disso, use a cadeia de caracteres do descritor de regra "certificado =
HashId:thumbprint", onde a impressão digital é codificada em hexadecimal SHA1 impressão digital do certificado
a ser usado. Veja abaixo um exemplo.
sc.AddDataProtection()
// searches the cert store for the cert with this thumbprint
.ProtectKeysWithDpapiNG("CERTIFICATE=HashId:3BCE558E2AD3E0E34A7743EAB5AEA2A9BD2575A0",
flags: DpapiNGProtectionDescriptorFlags.None);

Qualquer aplicativo que está apontado para este repositório deve estar em execução no Windows 8.1 / Windows
Server 2012 R2 ou posterior para ser capaz de decifrar essa chave.

Criptografia de chave personalizada


Se os mecanismos de caixa de entrada não forem apropriados, o desenvolvedor pode especificar seu próprio
mecanismo de criptografia de chave, fornecendo um personalizado IXmlEncryptor .
A imutabilidade de chave e configurações de chave
no núcleo do ASP.NET
10/04/2018 • 2 min to read • Edit Online

Depois que um objeto é persistido para o armazenamento de backup, sua representação para sempre é fixo.
Novos dados podem ser adicionados ao repositório de backup, mas os dados existentes nunca podem ser
modificados. O objetivo principal deste comportamento é evitar a corrupção de dados.
Uma consequência esse comportamento é que, quando uma chave é gravada para armazenamento de backup, é
imutável. As datas de criação, ativação e expiração nunca podem ser alteradas, embora ele pode ser revogada
usando IKeyManager . Além disso, suas informações algorítmicos subjacentes, material de chave mestre e
criptografia propriedades rest também são imutáveis.
Se o desenvolvedor altera qualquer configuração que afeta a persistência de chave, essas alterações não entram
em vigor até a próxima vez que uma chave é gerada, através de uma chamada explícita para
IKeyManager.CreateNewKey ou por meio do sistema proteção de dados próprio chave automática geração
comportamento. As configurações que afetam a persistência de chave são da seguinte maneira:
O tempo de vida de chave padrão
A criptografia de chave no mecanismo de rest
As informações de algoritmos contidas na chave do
Se você precisar essas configurações de anteriores a próxima chave automática sem interrupção tempo, considere
a possibilidade de fazer uma chamada explícita para IKeyManager.CreateNewKey para forçar a criação de uma nova
chave. Lembre-se de fornecer uma data de ativação explícita ({agora + 2 dias} é uma boa regra prática para
permitir um tempo para que a alteração se propague) e a data de expiração na chamada.

DICA
Tocar o repositório de todos os aplicativos devem especificar as mesmas configurações com o IDataProtectionBuilder
métodos de extensão. Caso contrário, as propriedades da chave persistente dependerá do aplicativo específico que invocou
as rotinas de geração de chave.
Formato de armazenamento de chaves no núcleo do
ASP.NET
10/04/2018 • 5 min to read • Edit Online

Objetos são armazenados em repouso na representação XML. O diretório padrão para armazenamento de chaves
é % LOCAL APPDATA%\ASP.NET\DataProtection-Keys.

O <chave > elemento


Chaves existirem como objetos de nível superior no repositório de chave. Por convenção, as chaves têm o nome
do arquivo chave-{guid}. XML, onde {guid} é a id da chave. Cada arquivo contém uma única chave. O formato do
arquivo é da seguinte maneira.

<?xml version="1.0" encoding="utf-8"?>


<key id="80732141-ec8f-4b80-af9c-c4d2d1ff8901" version="1">
<creationDate>2015-03-19T23:32:02.3949887Z</creationDate>
<activationDate>2015-03-19T23:32:02.3839429Z</activationDate>
<expirationDate>2015-06-17T23:32:02.3839429Z</expirationDate>
<descriptor deserializerType="{deserializerType}">
<descriptor>
<encryption algorithm="AES_256_CBC" />
<validation algorithm="HMACSHA256" />
<enc:encryptedSecret decryptorType="{decryptorType}" xmlns:enc="...">
<encryptedKey>
<!-- This key is encrypted with Windows DPAPI. -->
<value>AQAAANCM...8/zeP8lcwAg==</value>
</encryptedKey>
</enc:encryptedSecret>
</descriptor>
</descriptor>
</key>

O <chave > elemento contém os seguintes atributos e elementos filho:


A id de chave. Esse valor é tratado como autoritativo; o nome do arquivo é simplesmente uma sutileza para
facilitar a leitura humana.
A versão do <chave > elemento fixado no momento em 1.
Datas de expiração, ativação e criação da chave.
Um <descritor > elemento, que contém informações sobre a implementação de criptografia autenticada
contida dentro dessa chave.
No exemplo acima, id da chave é {80732141-ec8f-4b80-af9c-c4d2d1ff8901}, ele foi criado e ativado em 19 de
março de 2015, e ele tem um tempo de vida de 90 dias. (Ocasionalmente, a data de ativação pode ser um pouco
antes da data de criação, como neste exemplo. Isso ocorre devido a um nit em como as APIs de trabalho e é
inofensivas na prática.)

O <descritor > elemento


Externa <descritor > elemento contém um deserializerType de atributo, que é o nome qualificado do assembly de
um tipo que implementa IAuthenticatedEncryptorDescriptorDeserializer. Esse tipo é responsável por ler interna
<descritor > elemento e para analisar as informações contidas em.
O formato específico do <descritor > elemento depende da implementação do Criptografador autenticado
encapsulada pela chave e cada tipo desserializador espera um formato ligeiramente diferente para isso. Em geral,
no entanto, esse elemento conterá informações de algoritmos (nomes, tipos, OIDs, ou semelhante) e o material de
chave secreta. No exemplo acima, o descritor especifica que esta chave encapsula a criptografia AES -256-CBC +
HMACSHA256 validação.

O <encryptedSecret > elemento


Um elemento que contém o formulário criptografado do material de chave secreto pode estar presente se
criptografia de segredos em repouso está ativada. O atributo decryptorType será o nome qualificado do assembly
de um tipo que implementa IXmlDecryptor. Esse tipo é responsável por ler interna elemento e descriptografá-los
para recuperar o texto não criptografado original.
Assim como acontece com <descritor >, o formato específico do elemento depende do mecanismo de criptografia
em repouso em uso. No exemplo acima, a chave mestra é criptografada usando a DPAPI do Windows por
comentário.

O <revogação > elemento


Revogações existem como objetos de nível superior no repositório de chave. Por convenção revogações tem o
nome do arquivo . XML de revogação-{timestamp} (para revogar todas as chaves antes de uma data específica)
ou revogação-{guid}. XML (para revogar uma chave específica). Cada arquivo contém um único <revogação >
elemento.
Para revogações de chaves individuais, o conteúdo do arquivo será abaixo.

<?xml version="1.0" encoding="utf-8"?>


<revocation version="1">
<revocationDate>2015-03-20T22:45:30.2616742Z</revocationDate>
<key id="eb4fc299-8808-409d-8a34-23fc83d026c9" />
<reason>human-readable reason</reason>
</revocation>

Nesse caso, somente a chave especificada é revogada. Se a id de chave é "*", no entanto, como no exemplo abaixo,
todas as chaves cuja data de criação está antes da data de revogação especificada serão revogadas.

<?xml version="1.0" encoding="utf-8"?>


<revocation version="1">
<revocationDate>2015-03-20T15:45:45.7366491-07:00</revocationDate>
<!-- All keys created before the revocation date are revoked. -->
<key id="*" />
<reason>human-readable reason</reason>
</revocation>

O <motivo > elemento nunca é lidos pelo sistema. Ele é simplesmente um local conveniente para armazenar um
motivo legível revogação.
Provedores de proteção de dados efêmero no núcleo
do ASP.NET
10/04/2018 • 2 min to read • Edit Online

Há cenários em que um aplicativo precisa de um folheto de propaganda IDataProtectionProvider . Por exemplo, o


desenvolvedor pode apenas suas experiências em um aplicativo de console único, ou o aplicativo em si é
transitório (é inserido no script ou uma unidade de projeto de teste). Para dar suporte a esses cenários de
Microsoft.AspNetCore.DataProtection pacote inclui um tipo EphemeralDataProtectionProvider . Esse tipo fornece
uma implementação básica de IDataProtectionProvider cujo repositório de chave é mantido somente na memória
e não é gravado em qualquer armazenamento de backup.
Cada instância de EphemeralDataProtectionProvider usa sua própria chave mestra exclusiva. Portanto, se um
IDataProtector com raiz em um EphemeralDataProtectionProvider gera uma carga protegida, essa carga só pode
ser desprotegida por um equivalente IDataProtector (forem os mesmos finalidade cadeia) vinculados ao mesmo
EphemeralDataProtectionProvider instância.

O exemplo a seguir demonstra a instanciação de um EphemeralDataProtectionProvider e usá-lo para proteger e


Desproteger dados.
using System;
using Microsoft.AspNetCore.DataProtection;

public class Program


{
public static void Main(string[] args)
{
const string purpose = "Ephemeral.App.v1";

// create an ephemeral provider and demonstrate that it can round-trip a payload


var provider = new EphemeralDataProtectionProvider();
var protector = provider.CreateProtector(purpose);
Console.Write("Enter input: ");
string input = Console.ReadLine();

// protect the payload


string protectedPayload = protector.Protect(input);
Console.WriteLine($"Protect returned: {protectedPayload}");

// unprotect the payload


string unprotectedPayload = protector.Unprotect(protectedPayload);
Console.WriteLine($"Unprotect returned: {unprotectedPayload}");

// if I create a new ephemeral provider, it won't be able to unprotect existing


// payloads, even if I specify the same purpose
provider = new EphemeralDataProtectionProvider();
protector = provider.CreateProtector(purpose);
unprotectedPayload = protector.Unprotect(protectedPayload); // THROWS
}
}

/*
* SAMPLE OUTPUT
*
* Enter input: Hello!
* Protect returned: CfDJ8AAAAAAAAAAAAAAAAAAAAA...uGoxWLjGKtm1SkNACQ
* Unprotect returned: Hello!
* << throws CryptographicException >>
*/
Compatibilidade no ASP.NET Core
10/04/2018 • 1 min to read • Edit Online

Substituição do ASP.NET <machineKey> no ASP.NET Core


Substitua o machineKey ASP.NET no núcleo do
ASP.NET
10/04/2018 • 4 min to read • Edit Online

A implementação de <machineKey> elemento no ASP.NET é substituível. Isso permite que a maioria das chamadas
para rotinas criptográficas ASP.NET para ser roteada por meio de um mecanismo de proteção de dados de
substituição, incluindo o novo sistema de proteção de dados.

Instalação do pacote
OBSERVAÇÃO
O novo sistema de proteção de dados só pode ser instalado em um aplicativo ASP.NET existente direcionado ao .NET 4.5.1
ou posterior. Instalação irá falhar se o aplicativo tem como destino .NET 4.5 ou inferior.

Para instalar o novo sistema de proteção de dados em um projeto de 4.5.1+ ASP.NET existente, instale o pacote
Microsoft.AspNetCore.DataProtection.SystemWeb. Isso criará uma instância de sistema de proteção de dados
usando o configuração padrão configurações.
Quando você instala o pacote, ele insere uma linha em Web. config que diz ao ASP.NET para usá-la para mais
operações criptográficas, incluindo autenticação de formulários, o estado de exibição e chamadas para Protect. A
linha é inserida lê da seguinte maneira.

<machineKey compatibilityMode="Framework45" dataProtectorType="..." />

DICA
Você pode determinar se o novo sistema de proteção de dados está ativo inspecionando campos como __VIEWSTATE , que
deve começar com "CfDJ8" como no exemplo a seguir. "CfDJ8" é a representação de base64 do cabeçalho magic "09 F0 C9
F0" que identifica um conteúdo protegido pelo sistema de proteção de dados.

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="CfDJ8AWPr2EQPTBGs3L2GCZOpk..." />

Configuração de pacote
O sistema de proteção de dados é instanciado com uma configuração de zero a instalação padrão. No entanto,
uma vez que por padrão as chaves são mantidas para o sistema de arquivos local, isso não funcionará para
aplicativos que são implantados em um farm. Para resolver esse problema, você pode fornecer uma configuração
por meio da criação de um tipo que herda DataProtectionStartup e substitui o método ConfigureServices.
Abaixo está um exemplo de um tipo de inicialização de proteção de dados personalizados que configurado onde
as chaves são persistentes e como eles são criptografados em repouso. Ela também substitui a política de
isolamento de aplicativo padrão, fornecendo seu próprio nome de aplicativo.
using System;
using System.IO;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.SystemWeb;
using Microsoft.Extensions.DependencyInjection;

namespace DataProtectionDemo
{
public class MyDataProtectionStartup : DataProtectionStartup
{
public override void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.SetApplicationName("my-app")
.PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\myapp-keys\"))
.ProtectKeysWithCertificate("thumbprint");
}
}
}

DICA
Você também pode usar <machineKey applicationName="my-app" ... /> no lugar de uma chamada explícita para
SetApplicationName. Esse é um mecanismo de conveniência para evitar forçar o desenvolvedor para criar um tipo derivado
de DataProtectionStartup se todos os quisessem configurar foi definindo o nome do aplicativo.

Para habilitar essa configuração personalizada, volte para a Web. config e procure o <appSettings> elemento que
instala o pacote adicionado ao arquivo de configuração. Ele se parecerá com a seguinte marcação:

<appSettings>
<!--
If you want to customize the behavior of the ASP.NET Core Data Protection stack, set the
"aspnet:dataProtectionStartupType" switch below to be the fully-qualified name of a
type which subclasses Microsoft.AspNetCore.DataProtection.SystemWeb.DataProtectionStartup.
-->
<add key="aspnet:dataProtectionStartupType" value="" />
</appSettings>

Preencha o valor em branco com o nome qualificado do assembly do tipo derivado DataProtectionStartup que
você acabou de criar. Se o nome do aplicativo é DataProtectionDemo, isso seria semelhante a abaixo.

<add key="aspnet:dataProtectionStartupType"
value="DataProtectionDemo.MyDataProtectionStartup, DataProtectionDemo" />

O sistema de proteção de dados recém-configurada agora está pronto para uso dentro do aplicativo.
Impor HTTPS do núcleo do ASP.NET
27/04/2018 • 6 min to read • Edit Online

Por Rick Anderson


Este documento demonstra como:
Exigir HTTPS para todas as solicitações.
Redirecione todas as solicitações HTTP para HTTPS.

AVISO
Fazer não usar RequireHttpsAttribute em APIs da Web que recebe informações confidenciais. RequireHttpsAttribute
usa códigos de status HTTP para redirecionar navegadores de HTTP para HTTPS. Clientes de API podem não entender ou
obedecer redirecionamentos de HTTP para HTTPS. Esses clientes podem enviar informações sobre HTTP. APIs da Web
deverá:
Não escuta no HTTP.
Feche a conexão com o código de status 400 (solicitação incorreta) e não atender à solicitação.

Exigir HTTPS
OBSERVAÇÃO
ASP.NET Core 2.1 is in preview and not recommended for production use.

É recomendável ASP.NET Core todas as chamadas de aplicativos web UseHttpsRedirection para redirecionar
todas as solicitações HTTP para HTTPS. Se UseHsts é chamado no aplicativo, ele deve ser chamado antes de
UseHttpsRedirection .

O código a seguir chama UseHttpsRedirection no Startup classe:


[!code-csharpsample]
O código a seguir:
[!code-csharpsample]
Conjuntos de RedirectStatusCode .
Define a porta HTTPS para 5001.
O RequireHttpsAttribute é usada para exigir HTTPS. [RequireHttpsAttribute] pode decorar controladores ou
métodos, ou podem ser aplicadas globalmente. Para aplicar o atributo global, adicione o seguinte código ao
ConfigureServices em Startup :

[!code-csharp]
O código realçado anterior requer todas as solicitações usar HTTPS ; portanto, as solicitações HTTP são
ignoradas. O seguinte código realçado redireciona todas as solicitações HTTP para HTTPS:
[!code-csharp]
Para obter mais informações, consulte Middleware de regravação de URL.
Exigir HTTPS globalmente ( options.Filters.Add(new RequireHttpsAttribute()); ) é uma prática recomendada de
segurança. Aplicar o [RequireHttps] atributo para todas as páginas Razor/controladores não é considerado mais
seguro que exijam HTTPS globalmente. Você não pode garantir a [RequireHttps] atributo é aplicado quando
forem adicionadas novos controladores e páginas Razor.

Protocolo de segurança de transporte estrito de HTTP (HSTS)


Por OWASP, segurança de transporte estrito HTTP (HSTS ) é um aprimoramento de segurança de aceitação é
especificado por um aplicativo web com o uso de um cabeçalho de resposta especial. Depois que um navegador
com suporte recebe esse cabeçalho navegador impedirá todas as comunicações do que está sendo enviada via
HTTP no domínio especificado e enviará, em vez disso, todas as comunicações via HTTPS. Ele também evita
avisos em navegadores de cliques HTTPS.
ASP.NET Core preview1 2.1 ou posterior implementa HSTS com o UseHsts método de extensão. O código a
seguir chama UseHsts quando o aplicativo não está em modo de desenvolvimento:
[!code-csharpsample]
UseHsts não é recomendável em desenvolvimento porque o cabeçalho HSTS é altamente armazenável por
navegadores. Por padrão, UseHsts exclui o endereço de loopback local.
O código a seguir:
[!code-csharpsample]
Define o parâmetro de pré-carregamento do cabeçalho de segurança de transporte Strict. Pré-carregamento
não é parte do especificação RFC HSTS, mas é compatível com navegadores da web para pré-carregar sites
HSTS nova instalação. Veja https://hstspreload.org/ para obter mais informações.
Permite includeSubDomain, que se aplica a política HSTS a subdomínios de Host.
Define explicitamente o parâmetro de idade máxima do cabeçalho de segurança de transporte Strict para 60
dias. Se não for definido, o padrão é 30 dias. Consulte o max-age diretiva para obter mais informações.
Adiciona example.com à lista de hosts a serem excluídos.

UseHsts Exclui os seguintes hosts de loopback:


localhost : O endereço de loopback do IPv4.
127.0.0.1 : O endereço de loopback do IPv4.
[::1] : O endereço de loopback de IPv6.

O exemplo anterior mostra como adicionar outros hosts.

Recusar HTTPS na criação do projeto


Habilitam o ASP.NET Core 2.1 e posteriores modelos de aplicativo da web (do Visual Studio ou a linha de
comando dotnet) redirecionamento HTTPS e HSTS. Para implantações que não exijam HTTPS, você pode
recusar o HTTPS. Por exemplo, alguns serviços de back-end onde HTTPS está sendo tratado externamente na
borda, usando HTTPS em cada nó não é necessária.
Para recusar o HTTPS:
Visual Studio
CLI do .NET Core
Desmarque o configurar para HTTPS caixa de seleção.
Armazenamento seguro de segredos do
aplicativo em desenvolvimento no núcleo do
ASP.NET
04/05/2018 • 10 min to read • Edit Online

Por Rick Anderson, Daniel Roth, e Scott Addie


Este documento mostra como você pode usar a ferramenta Gerenciador de segredo em
desenvolvimento para manter os segredos fora do seu código. O ponto mais importante é que você
nunca deve armazenar senhas ou outros dados confidenciais no código-fonte, e você não deve usar os
segredos de produção no modo de desenvolvimento e teste. Em vez disso, você pode usar o
configuração sistema ler esses valores de variáveis de ambiente ou dos valores armazenados usando o
Gerenciador de segredo de ferramenta. A ferramenta Gerenciador de segredo ajuda a impedir que os
dados confidenciais que está sendo verificado no controle de origem. O configuração sistema pode ler os
segredos armazenados com a ferramenta Gerenciador de segredo descrita neste artigo.
A ferramenta Gerenciador de segredo é usada somente em desenvolvimento. Você pode proteger
segredos de teste e produção do Azure com o Microsoft Azure Key Vault provedor de configuração.
Consulte provedor de configuração do Azure Key Vault para obter mais informações.

Variáveis de ambiente
Para evitar armazenar segredos do aplicativo no código ou em arquivos de configuração local, você pode
armazenar segredos em variáveis de ambiente. Você pode configurar o configuração framework para ler
valores de variáveis de ambiente chamando AddEnvironmentVariables . Você pode usar variáveis de
ambiente para substituir valores de configuração para todas as fontes de configuração especificada
anteriormente.
Por exemplo, se você criar um novo aplicativo web ASP.NET Core com contas de usuário individuais, ele
irá adicionar uma cadeia de caracteres de conexão padrão para o appSettings. JSON arquivo no projeto
com a chave DefaultConnection . A cadeia de caracteres de conexão padrão está configurado para usar
LocalDB, que é executado no modo de usuário e não requer uma senha. Quando você implanta seu
aplicativo em um servidor de teste ou de produção, você pode substituir o DefaultConnection valor de
chave com uma configuração de variável de ambiente que contém a cadeia de caracteres de conexão
(possivelmente com credenciais confidenciais) para um banco de dados de teste ou de produção servidor.

AVISO
Variáveis de ambiente geralmente são armazenadas em texto sem formatação e não são criptografadas. Se o
computador ou o processo estiver comprometido, variáveis de ambiente podem ser acessadas por indivíduos não
confiáveis. Medidas adicionais para evitar a divulgação de segredos do usuário ainda podem ser necessárias.

Gerenciador de segredo
A ferramenta Gerenciador de segredo armazena dados confidenciais para o trabalho de desenvolvimento
fora da árvore do projeto. A ferramenta Gerenciador de segredo é uma ferramenta de projeto que pode
ser usada para armazenar segredos para um projeto .NET Core durante o desenvolvimento. Com a
ferramenta Gerenciador de segredo, você pode associar os segredos do aplicativo um projeto específico
e compartilhá-los em vários projetos.

AVISO
A ferramenta Gerenciador de segredo não criptografa os segredos armazenados e não deve ser tratada como um
repositório confiável. Ele destina-se apenas para fins de desenvolvimento. As chaves e valores são armazenados
em um arquivo de configuração JSON no diretório de perfil do usuário.

Instalando a ferramenta Gerenciador de segredo


Visual Studio
Visual Studio Code
Clique com botão direito no projeto no Gerenciador de soluções e selecione editar <project_name>.
csproj no menu de contexto. Adicione a linha realçada para o . csproj de arquivo e salvar para restaurar o
pacote NuGet associado:

<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0"
/>
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" />
</ItemGroup>
</Project>

Clique com botão direito no projeto no Gerenciador de soluções novamente e selecione gerenciar
segredos do usuário no menu de contexto. Esse gesto adiciona um novo UserSecretsId nó dentro de
um PropertyGroup do . csproj arquivo, como destacado no exemplo a seguir:

<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<UserSecretsId>User-Secret-ID</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0"
/>
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" />
</ItemGroup>
</Project>

Salvando o . csproj arquivo também abre uma secrets.json arquivo no editor de texto. Substitua o
conteúdo do secrets.json arquivo com o código a seguir:

{
"MySecret": "ValueOfMySecret"
}
Acessando os segredos do usuário por meio da configuração
Acessar o Gerenciador de segredo segredos através do sistema de configuração. Adicionar o
Microsoft.Extensions.Configuration.UserSecrets empacotar e executar restauração dotnet.

Adicione a fonte de configuração de segredos do usuário para o Startup método:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace UserSecrets
{
public class Startup
{
string _testSecret = null;
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder();

if (env.IsDevelopment())
{
builder.AddUserSecrets<Startup>();
}

Configuration = builder.Build();
}

public IConfigurationRoot Configuration { get; }

public void ConfigureServices(IServiceCollection services)


{
_testSecret = Configuration["MySecret"];
}

public void Configure(IApplicationBuilder app)


{
var result = string.IsNullOrEmpty(_testSecret) ? "Null" : "Not Null";
app.Run(async (context) =>
{
await context.Response.WriteAsync($"Secret is {result}");
});
}
}
}

Você pode acessar os segredos do usuário via a API de configuração:


using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace UserSecrets
{
public class Startup
{
string _testSecret = null;
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder();

if (env.IsDevelopment())
{
builder.AddUserSecrets<Startup>();
}

Configuration = builder.Build();
}

public IConfigurationRoot Configuration { get; }

public void ConfigureServices(IServiceCollection services)


{
_testSecret = Configuration["MySecret"];
}

public void Configure(IApplicationBuilder app)


{
var result = string.IsNullOrEmpty(_testSecret) ? "Null" : "Not Null";
app.Run(async (context) =>
{
await context.Response.WriteAsync($"Secret is {result}");
});
}
}
}

Como funciona a ferramenta Gerenciador de segredo


A ferramenta Gerenciador de segredo abstrai os detalhes de implementação, como onde e como os
valores são armazenados. Você pode usar a ferramenta sem conhecer esses detalhes de implementação.
Na versão atual, os valores são armazenados em um JSON arquivo de configuração no diretório de
perfil do usuário:
Windows: %APPDATA%\microsoft\UserSecrets\<userSecretsId>\secrets.json

Linux: ~/.microsoft/usersecrets/<userSecretsId>/secrets.json

macOS: ~/.microsoft/usersecrets/<userSecretsId>/secrets.json

O valor de userSecretsId vem do valor especificado em . csproj arquivo.


Você não deve escrever código que depende do local ou o formato dos dados salvos com a ferramenta
Gerenciador de segredo, como esses detalhes de implementação podem ser alterado. Por exemplo, os
valores secretos são atualmente não criptografado hoje, mas pode ser um dia.

Recursos adicionais
Configuração
Provedor de configuração do Cofre de chaves do
Azure no núcleo do ASP.NET
04/05/2018 • 17 min to read • Edit Online

Por Luke Latham e Andrew Stanton-enfermeiro


ASP.NET Core 2.x
ASP.NET Core 1.x
Exibir ou baixar o código de exemplo para 2. x:
Exemplo básico (como baixar)-lê valores secretos em um aplicativo.
Exemplo de prefixo do nome da chave (como baixar) - leituras valores secretos usando um prefixo de nome
da chave que representa a versão de um aplicativo, que permite que você carregue um conjunto diferente de
valores secretos para cada versão do aplicativo.
Este documento explica como usar o Microsoft Azure Key Vault provedor de configuração para carregar valores
de configuração do aplicativo de segredos de Cofre de chaves do Azure. Cofre de chaves do Azure é um serviço
baseado em nuvem que ajuda a proteger as chaves de criptografia e segredos usados por aplicativos e serviços.
Cenários comuns incluem a controlar o acesso aos dados de configuração confidenciais e atendem ao requisito
para FIPS 140-2 nível 2 validados módulos de segurança de Hardware (HSM ) ao armazenar os dados de
configuração. Esse recurso está disponível para aplicativos voltados para ASP.NET Core 1.1 ou posterior.

Pacote
Para usar o provedor, adicione uma referência para o Microsoft.Extensions.Configuration.AzureKeyVault pacote.

Configuração do aplicativo
Você pode explorar o provedor com o aplicativos de exemplo. Depois de estabelecer um cofre de chaves e criar
segredos no cofre, os aplicativos de amostra carregar os valores de segredo em suas configurações e exibem-los
em páginas da Web com segurança.
O provedor é adicionado para o ConfigurationBuilder com o AddAzureKeyVault extensão. Os aplicativos de
exemplo, a extensão usa três valores de configuração carregados a partir de appSettings. JSON arquivo.

CONFIGURAÇÃO DO APLICATIVO DESCRIÇÃO EXEMPLO

Vault Nome do Cofre de chaves do Azure contosovault

ClientId Id de aplicativo do Active Directory do 627e911e-43cc-61d4-992e-


Azure 12db9c81b413

ClientSecret Chave de aplicativo do Active Directory g58K3dtg59o1Pa+e59v2Tx829w6VxTB


do Azure 2yv9sv/101di=
config.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false)
.AddEnvironmentVariables();

var builtConfig = config.Build();

config.AddAzureKeyVault(
$"https://{builtConfig["Vault"]}.vault.azure.net/",
builtConfig["ClientId"],
builtConfig["ClientSecret"]);

Criando segredos de Cofre de chaves e carregar valores de


configuração (exemplo basic)
1. Criar um cofre de chaves e configurar o Azure Active Directory (AD do Azure) para o aplicativo seguindo
as orientações em Introdução ao Azure Key Vault.
Adicionar segredos no cofre de chaves usando o AzureRM chave cofre PowerShell módulo
disponíveis no Galeria do PowerShell, o API de REST do Cofre de chaves do Azure, ou o Portal do
azure. Os segredos são criados como Manual ou certificado segredos. Certificado segredos são
certificados para uso por aplicativos e serviços, mas não são suportados pelo provedor de
configuração. Você deve usar o Manual opção para criar os segredos do par nome-valor para uso com
o provedor de configuração.
Segredos simples são criados como pares nome-valor. Nomes de segredo de Cofre de chaves
do Azure são limitados a caracteres alfanuméricos e traços.
Usam valores hierárquicos (seções de configuração) -- (dois traços) como um separador no
exemplo. Dois-pontos, que normalmente são usadas para delimitar uma seção de uma
subchave no configuração do ASP.NET Core, não são permitidos em nomes de segredo.
Portanto, dois traços são usados e trocados para dois-pontos quando os segredos são
carregados na configuração do aplicativo.
Criar dois Manual segredos com os seguintes pares de nome-valor. O segredo primeiro é um
nome simples e um valor e o segredo do segundo cria um valor secreto com uma seção e uma
subchave no nome do segredo:
SecretName : secret_value_1
Section--SecretName : secret_value_2
Registre o aplicativo de exemplo no Active Directory do Azure.
Autorize o aplicativo para acessar o Cofre de chaves. Quando você usa o
Set-AzureRmKeyVaultAccessPolicy cmdlet do PowerShell para autorizar o aplicativo para acessar o
Cofre de chave, fornecer List e Get acesso para segredos com -PermissionsToSecrets list,get .
2. Atualizar o aplicativo appSettings. JSON arquivo com os valores de Vault , ClientId , e ClientSecret .
3. Executar o aplicativo de exemplo, que obtém seus valores de configuração de IConfigurationRoot com o
mesmo nome que o nome do segredo.
Valores não hierárquicos: O valor de SecretName é obtido com config["SecretName"] .
Valores hierárquicos (seções): Use : notação (vírgula) ou o GetSection método de extensão. Use
qualquer uma dessas abordagens para obter o valor de configuração:
config["Section:SecretName"]
config.GetSection("Section")["SecretName"]

Quando você executa o aplicativo, uma página da Web mostra os valores de segredo carregados:
Criar Cofre de chaves prefixados segredos e carregar valores de
configuração (chave de nome-prefixo-amostra)
AddAzureKeyVault também fornece uma sobrecarga que aceita uma implementação de IKeyVaultSecretManager ,
que permite que você controle como chave segredos do cofre são convertidos em chaves de configuração. Por
exemplo, você pode implementar a interface para carregar valores secretos com base em um valor de prefixo
que você fornece durante a inicialização do aplicativo. Isso permite que você, por exemplo, para carregar os
segredos com base na versão do aplicativo.

AVISO
Não use prefixos em segredos de Cofre de chaves para colocar os segredos para vários aplicativos no mesmo Cofre de
chaves ou colocar segredos ambientais (por exemplo, desenvolvimento versus produção segredos) no mesmo cofre. É
recomendável que diferentes aplicativos e ambientes de desenvolvimento/produção usam cofres chave separados para
isolar os ambientes de aplicativo para o nível mais alto de segurança.

Usando o segundo aplicativo de exemplo, criar um segredo no cofre de chaves para 5000-AppSecret (períodos
não são permitidos em nomes de Cofre de chave secreta) que representa um segredo do aplicativo para a
versão 5.0.0.0 do seu aplicativo. Para outra versão, 5.1.0.0, você cria um segredo para 5100-AppSecret . Cada
versão do aplicativo carrega seu próprio valor secreto em sua configuração como AppSecret , remoção desativar
a versão que ele carrega o segredo. Implementação do exemplo é mostrada abaixo:

// The appVersion obtains the app version (5.0.0.0), which


// is set in the project file and obtained from the entry
// assembly. The versionPrefix holds the version without
// dot notation for the PrefixKeyVaultSecretManager.
var appVersion = Assembly.GetEntryAssembly().GetName().Version.ToString();
var versionPrefix = appVersion.Replace(".", string.Empty);

config.AddAzureKeyVault(
$"https://{builtConfig["Vault"]}.vault.azure.net/",
builtConfig["ClientId"],
builtConfig["ClientSecret"],
new PrefixKeyVaultSecretManager(versionPrefix));
public class PrefixKeyVaultSecretManager : IKeyVaultSecretManager
{
private readonly string _prefix;

public PrefixKeyVaultSecretManager(string prefix)


{
_prefix = $"{prefix}-";
}

public bool Load(SecretItem secret)


{
// Load a vault secret when its secret name starts with the
// prefix. Other secrets won't be loaded.
return secret.Identifier.Name.StartsWith(_prefix);
}

public string GetKey(SecretBundle secret)


{
// Remove the prefix from the secret name and replace two
// dashes in any name with the KeyDelimiter, which is the
// delimiter used in configuration (usually a colon). Azure
// Key Vault doesn't allow a colon in secret names.
return secret.SecretIdentifier.Name
.Substring(_prefix.Length)
.Replace("--", ConfigurationPath.KeyDelimiter);
}
}

O Load método é chamado por um algoritmo de provedor que itera os segredos do cofre para localizar os que
têm o prefixo de versão. Quando um prefixo de versão é encontrado com Load , o algoritmo usa o GetKey
método para retornar o nome da configuração do nome do segredo. Ele ignora o prefixo de versão do nome do
segredo e retorna o restante do nome do segredo para carregar a configuração do aplicativo pares nome-valor.
Quando você implementar essa abordagem:
1. Os segredos do Cofre de chaves são carregados.
2. O segredo de cadeia de caracteres para 5000-AppSecret for correspondida.
3. A versão 5000 (com o traço) é removidos de deixar o nome da chave AppSecret para carregar com o valor
de segredo na configuração do aplicativo.

OBSERVAÇÃO
Você também pode fornecer sua própria KeyVaultClient implementação AddAzureKeyVault . Fornecer um cliente
personalizado permite que você compartilhe uma única instância do cliente entre o provedor de configuração e outras
partes do seu aplicativo.

1. Criar um cofre de chaves e configurar o Azure Active Directory (AD do Azure) para o aplicativo seguindo
as orientações em Introdução ao Azure Key Vault.
Adicionar segredos no cofre de chaves usando o AzureRM chave cofre PowerShell módulo
disponíveis no Galeria do PowerShell, o API de REST do Cofre de chaves do Azure, ou o Portal do
azure. Os segredos são criados como Manual ou certificado segredos. Certificado segredos são
certificados para uso por aplicativos e serviços, mas não são suportados pelo provedor de
configuração. Você deve usar o Manual opção para criar os segredos do par nome-valor para uso com
o provedor de configuração.
Usam valores hierárquicos (seções de configuração) -- (dois traços) como separador.
Criar dois Manual segredos com os seguintes pares de nome-valor:
5000-AppSecret : 5.0.0.0_secret_value
5100-AppSecret : 5.1.0.0_secret_value
Registre o aplicativo de exemplo no Active Directory do Azure.
Autorize o aplicativo para acessar o Cofre de chaves. Quando você usa o
Set-AzureRmKeyVaultAccessPolicy cmdlet do PowerShell para autorizar o aplicativo para acessar o
Cofre de chave, fornecer List e Get acesso para segredos com -PermissionsToSecrets list,get .
2. Atualizar o aplicativo appSettings. JSON arquivo com os valores de Vault , ClientId , e ClientSecret .
3. Executar o aplicativo de exemplo, que obtém seus valores de configuração de IConfigurationRoot com o
mesmo nome que o nome do segredo prefixado. Neste exemplo, o prefixo é a versão do aplicativo, o que
você forneceu para o PrefixKeyVaultSecretManager quando você adicionou o provedor de configuração do
Cofre de chaves do Azure. O valor de AppSecret é obtido com config["AppSecret"] . A página da Web
gerada pelo aplicativo mostra o valor carregado:

4. Alterar a versão do assembly no arquivo de projeto do aplicativo 5.0.0.0 para 5.1.0.0 e execute o
aplicativo novamente. Neste momento, o valor de segredo retornado é 5.1.0.0_secret_value . A página
da Web gerada pelo aplicativo mostra o valor carregado:

Controlando o acesso para o ClientSecret


Use o ferramenta Gerenciador de segredo para manter o ClientSecret fora da árvore de origem do projeto.
Com o Gerenciador de segredo, que você associe os segredos do aplicativo um projeto específico e compartilhá-
los em vários projetos.
Ao desenvolver um aplicativo do .NET Framework em um ambiente que oferece suporte a certificados, você
pode autenticar para o Cofre de chaves do Azure com um certificado x. 509. Chave privada do certificado x. 509
é gerenciado pelo sistema operacional. Para obter mais informações, consulte autenticar com um certificado em
vez de um segredo do cliente. Use o AddAzureKeyVault sobrecarga que aceita um X509Certificate2 .

var store = new X509Store(StoreLocation.CurrentUser);


store.Open(OpenFlags.ReadOnly);
var cert = store.Certificates.Find(X509FindType.FindByThumbprint, config["CertificateThumbprint"], false);

builder.AddAzureKeyVault(
config["Vault"],
config["ClientId"],
cert.OfType<X509Certificate2>().Single(),
new EnvironmentSecretManager(env.ApplicationName));
store.Close();

Configuration = builder.Build();
Recarregar segredos
Os segredos são armazenados em cache até IConfigurationRoot.Reload() é chamado. Expirado, desabilitado, e
atualizados segredos no cofre de chaves não são respeitados pelo aplicativo até que Reload é executado.

Configuration.Reload();

Segredos desabilitados e expirados


Segredos desabilitados e expirados lançam um KeyVaultClientException . Para impedir que seu aplicativo
lançando, substitua o seu aplicativo ou atualizar o segredo desabilitado/expirado.

Solução de problemas
Quando o aplicativo falhar ao carregar a configuração usando o provedor, uma mensagem de erro é gravada
para o infraestrutura ASP.NET log. As seguintes condições impedirá que a configuração de carregamento:
O aplicativo não está configurado corretamente no Active Directory do Azure.
O Cofre de chaves não existe no cofre de chaves do Azure.
O aplicativo não está autorizado a acessar o Cofre de chaves.
A política de acesso não inclui Get e List permissões.
No cofre de chaves, os dados de configuração (par de nome-valor) são nomeados incorretamente, ausentes,
desabilitado ou expirou.
O aplicativo tem o nome do Cofre de chave incorreta ( Vault ), Id de aplicativo do Azure AD ( ClientId ), ou a
chave do Azure AD ( ClientSecret ).
A chave do AD do Azure ( ClientSecret ) expirou.
A chave de configuração (nome) está incorreta no aplicativo para o valor que você está tentando carregar.

Recursos adicionais
Configuração
Microsoft Azure: Cofre de chaves
Do Microsoft Azure: Documentação do Cofre de chaves
Chaves de como gerar e transferir protegida por HSM do Cofre de chaves do Azure
Classe KeyVaultClient
Ataques de evitar intersite solicitar CSRF
(falsificação XSRF /) no núcleo do ASP.NET
10/04/2018 • 25 min to read • Edit Online

Por Steve Smith, Fiyaz Hasan, e Rick Anderson


Falsificação de solicitação entre sites (também conhecido como XSRF ou CSRF, pronunciado
consulte navegação) é um ataque contra aplicativos web hospedados no qual um aplicativo web
mal-intencionado pode influenciar a interação entre o navegador do cliente e um aplicativo web
que confia que Navegador. Esses ataques são possíveis, como navegadores da web enviam alguns
tipos de tokens de autenticação automaticamente com todas as solicitações para um site. Essa
forma de exploração é também conhecido como um ataque de um clique ou sessão riding porque o
ataque aproveita o usuário do autenticado anteriormente sessão.
Um exemplo de um ataque CSRF:
1. Um usuário entra no www.good-banking-site.com usando a autenticação de formulários. O
servidor autentica o usuário e envia uma resposta que inclui um cookie de autenticação. O
site é vulnerável a ataques, pois ele confia qualquer solicitação recebida com um cookie de
autenticação válido.
2. O usuário acessa um site mal-intencionado, www.bad-crook-site.com .
O site mal-intencionado, www.bad-crook-site.com , contém um formulário HTML semelhante
à seguinte:

<h1>Congratulations! You're a Winner!</h1>


<form action="http://good-banking-site.com/api/account" method="post">
<input type="hidden" name="Transaction" value="withdraw">
<input type="hidden" name="Amount" value="1000000">
<input type="submit" value="Click to collect your prize!">
</form>

Observe que o formulário action postagens para o site vulnerável, não para o site mal-
intencionado. Esta é a parte de "sites" de CSRF.
3. O usuário seleciona o botão de envio. O navegador faz a solicitação e inclui
automaticamente o cookie de autenticação para o domínio solicitado,
www.good-banking-site.com .

4. A solicitação é executada www.good-banking-site.com server com o contexto de autenticação


do usuário e podem executar qualquer ação que um usuário autenticado tem permissão para
executar.
Quando o usuário seleciona o botão para enviar o formulário, o site mal-intencionado pode:
Execute um script que envia automaticamente o formulário.
Envia o envio de um formulário como uma solicitação AJAX.
Use um formulário oculto com o CSS.
Usar o HTTPS não impede que um ataque CSRF. O site mal-intencionado pode enviar um
https://www.good-banking-site.com/ solicitação tão fácil quanto que pode enviar uma solicitação
não segura.
Alguns ataques de destino pontos de extremidade que respondem às solicitações GET, caso em que
uma marca de imagem pode ser usada para executar a ação. Essa forma de ataque é comum em
sites de Fórum que permitem imagens mas bloqueiam JavaScript. Aplicativos que alteram o estado
em solicitações GET, onde variáveis ou os recursos são alterados, são vulneráveis a ataques mal-
intencionados. Solicitações GET que mudam de estado são seguros. Uma prática
recomendada é nunca alterar estado em uma solicitação GET.
Ataques CSRF são possíveis em relação a aplicativos web que usam cookies de autenticação
porque:
Os navegadores armazenam os cookies emitidos por um aplicativo web.
Armazenado cookies incluem cookies de sessão para usuários autenticados.
Navegadores enviam que todos os cookies associados a um domínio ao aplicativo web cada
solicitação, independentemente de como a solicitação ao aplicativo foi gerada dentro do
navegador.
No entanto, os ataques CSRF não estão limitadas a exploração de cookies. Por exemplo, a
autenticação básica e resumida também são vulneráveis. Depois que um usuário entra com
autenticação básica ou Digest, o navegador envia automaticamente as credenciais até que a sessão†
termina.
†Nesse contexto, sessão refere-se à sessão do lado do cliente durante o qual o usuário é
autenticado. É não relacionados para sessões do lado do servidor ou Middleware de sessão do
ASP.NET Core.
Os usuários podem proteger contra vulnerabilidades CSRF tomando precauções:
Inscreva-se de aplicativos da web quando terminar de usá-los.
Limpar os cookies do navegador periodicamente.
No entanto, as vulnerabilidades CSRF são basicamente um problema com o aplicativo web, não o
usuário final.

Conceitos básicos de autenticação


Autenticação baseada em cookie é uma forma popular de autenticação. Sistemas de autenticação
baseada em token estão crescendo popularidade, especialmente para aplicativos de página única
(SPAs).
Autenticação baseada em cookie
Quando um usuário é autenticado usando o nome de usuário e senha, eles são emitidos um token,
que contém um tíquete de autenticação que pode ser usado para autenticação e autorização. O
token é armazenado como um cookie que acompanha cada solicitação de cliente. Gerar e validar
esse cookie é executada pelo Middleware de autenticação de Cookie. O middleware serializa um
objeto de usuário em um cookie criptografado. Em solicitações subsequentes, o middleware valida
o cookie, recria a entidade de segurança e atribui a entidade de segurança de usuário propriedade
HttpContext.
Autenticação baseada em token
Quando um usuário é autenticado, eles são emitiu um token (não é um token antiforgery). O token
contém informações de usuário na forma de declarações ou um token de referência que aponta o
aplicativo para o estado do usuário mantido no aplicativo. Quando um usuário tenta acessar um
recurso que requer autenticação, o token é enviado para o aplicativo com um cabeçalho de
autorização adicionais na forma de token de portador. Isso torna o aplicativo sem monitoração de
estado. Em cada solicitação subsequente, o token é passado na solicitação de validação do lado do
servidor. Esse token não criptografado; ele tem codificado. No servidor, o token é decodificado para
acessar suas informações. Para enviar o token em solicitações subsequentes, armazene o token no
armazenamento local do navegador. Não se preocupar sobre vulnerabilidade CSRF se o token é
armazenado no armazenamento local do navegador. CSRF é uma preocupação quando o token é
armazenado em um cookie.
Vários aplicativos hospedados em um domínio
Ambientes de hospedagem compartilhados são vulneráveis a sequestro de sessão, logon CSRF e
outros ataques.
Embora example1.contoso.net e example2.contoso.net são hosts diferentes, há uma relação de
confiança implícita entre os hosts sob o *.contoso.net domínio. Essa relação de confiança implícita
permite que os hosts potencialmente não confiáveis afetam uns dos outros cookies (as políticas de
mesma origem que governam as solicitações do AJAX não necessariamente se aplicam a cookies
HTTP ).
Ataques que exploram cookies confiáveis entre aplicativos hospedados no mesmo domínio podem
ser evitados por compartilhamento não domínios. Quando cada aplicativo está hospedado em seu
próprio domínio, não há nenhuma relação de confiança implícita de cookie para explorar.

Configuração de ASP.NET Core antiforgery


AVISO
ASP.NET Core implementa usando antiforgery proteção de dados do ASP.NET Core. A pilha de proteção de
dados deve ser configurada para funcionar em um farm de servidores. Consulte Configurando a proteção
de dados para obter mais informações.

No ASP.NET Core 2.0 ou posterior, o FormTagHelper injeta antiforgery tokens em elementos de


formulário HTML. A seguinte marcação em um arquivo Razor gera automaticamente tokens
antiforgery:

<form method="post">
...
</form>

Similarily, IHtmlHelper.BeginForm gera tokens antiforgery por padrão, se o método do formulário


não é GET.
A geração automática de tokens antiforgery para elementos de formulário HTML acontece quando
o <form> marca contém o method="post" atributo e qualquer uma das opções a seguir forem
verdadeira:
O atributo de ação está vazio ( action="" ).
O atributo de ação não for fornecido ( <form method="post"> ).

Geração automática de tokens antiforgery para elementos de formulário HTML pode ser
desabilitada:
Desabilitar explicitamente tokens antiforgery com o asp-antiforgery atributo:
<form method="post" asp-antiforgery="false">
...
</form>

O elemento de formulário é desativado-out de auxiliares de marcação usando o auxiliar de


marca ! recusar símbolo:

<!form method="post">
...
</!form>

Remover o FormTagHelper do modo de exibição. O FormTagHelper podem ser removidas de


um modo de exibição, adicionando a seguinte diretiva para o modo de exibição do Razor:

@removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper,
Microsoft.AspNetCore.Mvc.TagHelpers

OBSERVAÇÃO
Páginas Razor são protegidos automaticamente contra XSRF/CSRF. Para obter mais informações, consulte
XSRF/CSRF e páginas Razor.

A abordagem mais comum para proteger contra ataques CSRF é usar o padrão de Token
sincronizador (STP ). STP é usado quando o usuário solicita uma página de dados do formulário:
1. O servidor envia um token associado com a identidade do usuário atual para o cliente.
2. O cliente envia o token para o servidor para verificação.
3. Se o servidor recebe um token que não corresponde a identidade do usuário autenticado, a
solicitação será rejeitada.
O token é exclusivo e imprevisível. O token também pode ser usado para garantir o
sequenciamento adequado de uma série de solicitações (por exemplo, garantindo a sequência de
solicitação de: a página 1 – página 2 – página 3). Todos os formulários em modelos do MVC do
ASP.NET Core e páginas Razor geram tokens antiforgery. O seguinte par de exemplos de exibição
gera tokens antiforgery:

<form asp-controller="Manage" asp-action="ChangePassword" method="post">


...
</form>

@using (Html.BeginForm("ChangePassword", "Manage"))


{
...
}

Adicionar explicitamente um token antiforgery para um <form> elemento sem o uso de auxiliares
de marcação com o auxiliar HTML @Html.AntiForgeryToken :

<form action="/" method="post">


@Html.AntiForgeryToken()
</form>

Em cada um dos casos anteriores, o ASP.NET Core adiciona um campo de formulário oculto
semelhante à seguinte:

<input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw">

ASP.NET Core inclui três filtros para trabalhar com tokens antiforgery:
ValidateAntiForgeryToken
AutoValidateAntiforgeryToken
IgnoreAntiforgeryToken

Opções de antiforgery
Personalizar opções antiforgery em Startup.ConfigureServices :

services.AddAntiforgery(options =>
{
options.CookieDomain = "contoso.com";
options.CookieName = "X-CSRF-TOKEN-COOKIENAME";
options.CookiePath = "Path";
options.FormFieldName = "AntiforgeryFieldname";
options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
options.RequireSsl = false;
options.SuppressXFrameOptionsHeader = false;
});

OPÇÃO DESCRIÇÃO

Cookie Determina as configurações usadas para criar o


cookie antiforgery.

CookieDomain O domínio do cookie. Assume o padrão de null .


Essa propriedade está obsoleta e será removida em
uma versão futura. A alternativa recomendada é
Cookie.Domain.

CookieName O nome do cookie. Se não estiver definida, o sistema


gera um nome exclusivo que começa com o
DefaultCookiePrefix (". AspNetCore.Antiforgery."). Essa
propriedade está obsoleta e será removida em uma
versão futura. A alternativa recomendada é
Cookie.Name.

CookiePath O caminho definido no cookie. Essa propriedade está


obsoleta e será removida em uma versão futura. A
alternativa recomendada é Cookie.Path.

FormFieldName O nome do campo de formulário oculto usado pelo


sistema antiforgery para processar tokens antiforgery
nos modos de exibição.

HeaderName O nome do cabeçalho usado pelo sistema antiforgery.


Se null , o sistema considerará apenas os dados de
formulário.
OPÇÃO DESCRIÇÃO

RequireSsl Especifica se o SSL é exigido pelo sistema antiforgery.


Se true , falham de solicitações de não SSL. Assume
o padrão de false . Essa propriedade está obsoleta
e será removida em uma versão futura. A alternativa
recomendada é definir Cookie.SecurePolicy.

SuppressXFrameOptionsHeader Especifica se é suprimir a geração do


X-Frame-Options cabeçalho. Por padrão, o
cabeçalho é gerado com um valor de "SAMEORIGIN".
Assume o padrão de false .

Para obter mais informações, consulte CookieAuthenticationOptions.

Configurar recursos antiforgery com IAntiforgery


IAntiforgery fornece a API para configurar recursos antiforgery. IAntiforgery pode ser solicitados
no Configure método o Startup classe. O exemplo a seguir usa o middleware da home page do
aplicativo para gerar um token antiforgery e enviá-lo na resposta como um cookie (usando a
convenção de nomenclatura Angular da padrão descrita mais adiante neste tópico):

public void Configure(IApplicationBuilder app, IAntiforgery antiforgery)


{
app.Use(next => context =>
{
string path = context.Request.Path.Value;

if (
string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase))
{
// The request token can be sent as a JavaScript-readable cookie,
// and Angular uses it by default.
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}

return next(context);
});
}

Exigir validação antiforgery


ValidateAntiForgeryToken é um filtro de ação que pode ser aplicado a uma ação individual, um
controlador ou globalmente. Solicitações feitas para ações com esse filtro aplicado são bloqueadas,
a menos que a solicitação inclui um token antiforgery válido.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveLogin(RemoveLoginViewModel account)
{
ManageMessageId? message = ManageMessageId.Error;
var user = await GetCurrentUserAsync();

if (user != null)
{
var result =
await _userManager.RemoveLoginAsync(
user, account.LoginProvider, account.ProviderKey);

if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
message = ManageMessageId.RemoveLoginSuccess;
}
}

return RedirectToAction(nameof(ManageLogins), new { Message = message });


}

O ValidateAntiForgeryToken atributo requer um token para solicitações para os métodos de ação


decora, incluindo solicitações HTTP GET. Se o ValidateAntiForgeryToken atributo é aplicado em
controladores do aplicativo, ele pode ser substituído com o IgnoreAntiforgeryToken atributo.

OBSERVAÇÃO
ASP.NET Core não oferece suporte à inclusão automaticamente tokens antiforgery solicitações GET.

Validar automaticamente tokens antiforgery para métodos HTTP não seguros somente
Aplicativos ASP.NET Core não geram tokens de antiforgery para métodos HTTP seguros (GET,
HEAD, opções e rastreamento). Em vez de em larga escala aplicar o ValidateAntiForgeryToken
atributo e, em seguida, substituindo-o com IgnoreAntiforgeryToken atributos, o
AutoValidateAntiforgeryToken atributo pode ser usado. Esse atributo funciona de forma idêntica ao
ValidateAntiForgeryToken de atributos, exceto pelo fato de que não exige tokens para solicitações
feitas usando os seguintes métodos HTTP:
OBTER
HOME
OPÇÕES
TRACE
Recomendamos o uso de AutoValidateAntiforgeryToken em larga escala para cenários de não-API.
Isso garante que as ações de POSTAGEM são protegidas por padrão. A alternativa é ignorar tokens
antiforgery por padrão, a menos que ValidateAntiForgeryToken é aplicado a métodos de ação
individual. Ele provavelmente neste cenário para um método de ação de POSTAGEM deve ser
desprotegido por engano, deixando o aplicativo vulnerável a ataques CSRF. Todas as postagens
devem enviar o token antiforgery.
APIs não tem um mecanismo automático para enviar a parte do cookie não do token. A
implementação provavelmente depende a implementação de código do cliente. Alguns exemplos
são mostrados abaixo:
Exemplo de nível de classe:
[Authorize]
[AutoValidateAntiforgeryToken]
public class ManageController : Controller
{

Exemplo global:

services.AddMvc(options =>
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()));

Substituição global ou atributos antiforgery controlador


O IgnoreAntiforgeryToken filtro é usado para eliminar a necessidade de um token de antiforgery
para uma determinada ação (ou controlador). Quando aplicado, esse filtro substitui
ValidateAntiForgeryToken e AutoValidateAntiforgeryToken filtros especificados em um nível mais
alto (global ou em um controlador).

[Authorize]
[AutoValidateAntiforgeryToken]
public class ManageController : Controller
{
[HttpPost]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> DoSomethingSafe(SomeViewModel model)
{
// no antiforgery token required
}
}

Tokens de atualização após a autenticação


Tokens devem ser atualizados depois que o usuário é autenticado ao redirecionar o usuário para
uma exibição ou página das páginas Razor.

JavaScript e AJAX SPAs


Em aplicativos tradicionais baseados em HTML, antiforgery tokens são passados para o servidor
usando campos ocultos do formulário. Em aplicativos modernos baseados em JavaScript e SPAs,
muitas solicitações são feitas por meio de programação. Essas solicitações do AJAX podem usar
outras técnicas (como cabeçalhos de solicitação ou cookies) para enviar o token.
Se os cookies são usados para armazenar os tokens de autenticação e para autenticar solicitações
de API no servidor, CSRF é um problema potencial. Se o armazenamento local é usado para
armazenar o token, vulnerabilidade CSRF pode ser reduzida porque os valores do armazenamento
local não são enviados automaticamente para o servidor com cada solicitação. Portanto, usando o
armazenamento local para armazenar o token antiforgery no cliente e enviar o token como um
cabeçalho de solicitação é uma abordagem recomendada.
JavaScript
Usando JavaScript com modos de exibição, o token pode ser criado usando um serviço a partir do
modo de exibição. Injetar o Microsoft.AspNetCore.Antiforgery.IAntiforgery serviço no modo de
exibição e chamada GetAndStoreTokens:
@{
ViewData["Title"] = "AJAX Demo";
}
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@functions{
public string GetAntiXsrfRequestToken()
{
return Xsrf.GetAndStoreTokens(Context).RequestToken;
}
}

<input type="hidden" id="RequestVerificationToken"


name="RequestVerificationToken" value="@GetAntiXsrfRequestToken()">

<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<div class="row">
<p><input type="button" id="antiforgery" value="Antiforgery"></p>
<script>
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState == XMLHttpRequest.DONE) {
if (xhttp.status == 200) {
alert(xhttp.responseText);
} else {
alert('There was an error processing the AJAX request.');
}
}
};

document.addEventListener('DOMContentLoaded', function() {
document.getElementById("antiforgery").onclick = function () {
xhttp.open('POST', '@Url.Action("Antiforgery", "Home")', true);
xhttp.setRequestHeader("RequestVerificationToken",
document.getElementById('RequestVerificationToken').value);
xhttp.send();
}
});
</script>
</div>

Essa abordagem elimina a necessidade de lidar diretamente com configuração cookies do servidor
ou lê-los do cliente.
O exemplo anterior usa JavaScript para ler o valor do campo oculto para o cabeçalho de
POSTAGEM de AJAX.
JavaScript também pode acessar tokens em cookies e usar o conteúdo do cookie para criar um
cabeçalho com o valor do token.

context.Response.Cookies.Append("CSRF-TOKEN", tokens.RequestToken,
new Microsoft.AspNetCore.Http.CookieOptions { HttpOnly = false });

Supondo que o script solicitações para enviar o token em um cabeçalho chamado X-CSRF-TOKEN ,
configure o serviço antiforgery para procurar o X-CSRF-TOKEN cabeçalho:

services.AddAntiforgery(options => options.HeaderName = "X-CSRF-TOKEN");

O exemplo a seguir usa JavaScript para fazer uma solicitação AJAX com o cabeçalho apropriado:
function getCookie(cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for(var i = 0; i <ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}

var csrfToken = getCookie("CSRF-TOKEN");

var xhttp = new XMLHttpRequest();


xhttp.onreadystatechange = function() {
if (xhttp.readyState == XMLHttpRequest.DONE) {
if (xhttp.status == 200) {
alert(xhttp.responseText);
} else {
alert('There was an error processing the AJAX request.');
}
}
};
xhttp.open('POST', '/api/password/changepassword', true);
xhttp.setRequestHeader("Content-type", "application/json");
xhttp.setRequestHeader("X-CSRF-TOKEN", csrfToken);
xhttp.send(JSON.stringify({ "newPassword": "ReallySecurePassword999$$$" }));

AngularJS
AngularJS usa uma convenção para endereço CSRF. Se o servidor envia um cookie com o nome
XSRF-TOKEN , o AngularJS $http serviço adiciona o valor do cookie para um cabeçalho quando ele
envia uma solicitação ao servidor. Esse processo é automático. O cabeçalho não precisa ser
definidos explicitamente. O nome do cabeçalho é X-XSRF-TOKEN . O servidor deve detectar esse
cabeçalho e validar seu conteúdo.
Para o ASP.NET Core API trabalhe com essa convenção:
Configure seu aplicativo para fornecer um token em um cookie chamado XSRF-TOKEN .
Configurar o serviço antiforgery para procurar um cabeçalho chamado X-XSRF-TOKEN .

services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");

Exibir ou baixar código de exemplo (como baixar)

Estender antiforgery
O IAntiForgeryAdditionalDataProvider tipo permite aos desenvolvedores estender o
comportamento do sistema anti-CSRF por dados adicionais do ciclo em cada token. O
GetAdditionalData método é chamado sempre que um token de campo é gerado e o valor de
retorno é inserido no token gerado. Um implementador poderia retornar um carimbo de hora, um
valor de uso único ou qualquer outro valor e, em seguida, chamar ValidateAdditionalData para
validar dados quando o token é validado. Nome de usuário do cliente já é inserido nos tokens
gerados, portanto, não há necessidade de incluir essas informações. Se um token inclui dados
complementares, mas não IAntiForgeryAdditionalDataProvider é configurado, os dados
complementares não são validados.

Recursos adicionais
CSRF na Abrir projeto de segurança de aplicativo da Web (OWASP ).
Evitar ataques de redirecionamento aberto no núcleo
do ASP.NET
10/04/2018 • 6 min to read • Edit Online

Um aplicativo web que redireciona para uma URL que é especificada por meio de solicitação, como os dados de
formulário ou querystring potencialmente pode ser violado para redirecionar usuários para uma URL externa e
mal-intencionados. Essa violação é chamado de um ataque de redirecionamento aberto.
Sempre que a lógica do aplicativo redireciona para uma URL específica, você deve verificar se a URL de
redirecionamento não foi adulterada. Núcleo do ASP.NET tem funcionalidade interna para ajudar a proteger
aplicativos contra ataques de redirecionamento aberto (também conhecido como open redirecionamento).

O que é um ataque de redirecionamento aberto?


Aplicativos da Web com frequência redirecionam os usuários para uma página de logon quando acessarem os
recursos que exigem autenticação. O redirecionamento typlically inclui um returnUrl parâmetro querystring para
que o usuário pode ser retornado para a URL solicitada originalmente depois que eles efetuou com êxito. Depois
que o usuário é autenticado, eles serão redirecionados para a URL que tinha originalmente solicitada.
Como a URL de destino é especificada na querystring da solicitação, um usuário mal-intencionado pode violar
querystring. Uma querystring violado pode permitir que o site redirecionar o usuário a um site externo, mal-
intencionado. Essa técnica é chamada de um ataque de redirecionamento (ou redirecionamento) aberto.
Um ataque de exemplo
Um usuário mal-intencionado poderá desenvolver um ataque de objetivo de permitir que o usuário mal-
intencionado acesso a informações confidenciais em seu aplicativo ou as credenciais de um usuário. Para iniciar o
ataque, eles convencer o usuário clicar em um link para a página de logon do site, com um returnUrl valor de
querystring adicionada à URL. Por exemplo, o NerdDinner.com (gravado para o ASP.NET MVC ) do aplicativo de
exemplo inclui tal uma página de logon aqui: http://nerddinner.com/Account/LogOn?returnUrl=/Home/About . O
ataque, em seguida, siga estas etapas:
1. Usuário clica em um link para
http://nerddinner.com/Account/LogOn?returnUrl=http://nerddiner.com/Account/LogOn (Observe que a URL
segundo é nerddiner, não nerddinner).
2. O usuário fizer logon com êxito.
3. O usuário é redirecionado (pelo site) para http://nerddiner.com/Account/LogOn (site mal-intencionado que se
parece com um site real).
4. O usuário fizer logon novamente (fornecendo mal-intencionado suas credenciais do site) e é redirecionado
para o site real.
O usuário provavelmente vai achar sua primeira tentativa de logon falha, e sua segunda foi bem-sucedida.
Provavelmente permanecerá não reconhecem as credenciais foram comprometidas.
Além das páginas de logon, alguns sites fornecem redirecionamento páginas ou pontos de extremidade. Imagine
que seu aplicativo tem uma página com um redirecionamento aberto, /Home/Redirect . Um invasor pode criar, por
exemplo, um link em um email que vão para [yoursite]/Home/Redirect?url=http://phishingsite.com/Home/Login .
Um usuário normal na URL e ver começa com o nome do site. Confiar em que, eles clicarem no link. O
redirecionamento aberto, em seguida, envia o usuário para o site de phishing, que é idêntico ao seu, e o usuário
provavelmente é de logon para o que eles acreditam seu site.

Proteção contra ataques de redirecionamento aberto


Ao desenvolver aplicativos da web, trate todos os dados fornecidos pelo usuário como não confiável. Se seu
aplicativo tem funcionalidade que redireciona o usuário com base no conteúdo da URL, certifique-se de que tais
redirecionamentos só são feitos localmente em seu aplicativo (ou uma URL conhecida, e não qualquer URL que
pode ser fornecido na querystring).
LocalRedirect
Use o LocalRedirect método auxiliar de base de Controller classe:

public IActionResult SomeAction(string redirectUrl)


{
return LocalRedirect(redirectUrl);
}

LocalRedirect lançará uma exceção se uma URL de local não for especificada. Caso contrário, ele se comporta
exatamente como o Redirect método.
IsLocalUrl
Use o IsLocalUrl método de teste URLs antes de redirecionar:
O exemplo a seguir mostra como verificar se uma URL é local antes de redirecionar.

private IActionResult RedirectToLocal(string returnUrl)


{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction(nameof(HomeController.Index), "Home");
}
}

O IsLocalUrl método impede que os usuários inadvertidamente sendo redirecionado para um site mal-
intencionado. Você pode registrar os detalhes da URL que foi fornecida uma URL de local não é fornecida em uma
situação em que você esperava um URL local. URLs de redirecionamento de log podem ajudar no diagnóstico de
ataques de redirecionamento.
Impedir que os sites script (XSS) no núcleo do
ASP.NET
10/04/2018 • 13 min to read • Edit Online

Por Rick Anderson


Execução de scripts entre sites (XSS ) é uma vulnerabilidade de segurança que permite que um invasor inserir os
scripts do lado do cliente (geralmente JavaScript) em páginas da web. Quando outros usuários carregar páginas
afetadas, os invasores scripts serão executados, permitindo que o invasor roubar cookies e tokens de sessão,
alterar o conteúdo da página da web por meio de manipulação de DOM ou redirecionar o navegador para outra
página. Vulnerabilidades XSS geralmente ocorrem quando um aplicativo usa a entrada do usuário e passa em
uma página sem validação, codificação ou escape-lo.

Protegendo seu aplicativo contra XSS


AT um XSS de nível básico funciona enganar seu aplicativo para inserir um <script> marca na página
renderizada, ou inserindo um On* evento em um elemento. Os desenvolvedores devem usar as seguintes etapas
de prevenção para evitar introduzir XSS em seu aplicativo.
1. Nunca coloque dados não confiáveis em sua entrada HTML, a menos que você siga o restante das etapas
a seguir. Dados não confiáveis são todos os dados que podem ser controlados por um invasor, entradas de
formulário HTML, cadeias de caracteres de consulta, os cabeçalhos HTTP, até mesmo dados originados de
um banco de dados como um invasor consiga violar seu banco de dados, mesmo se eles não podem violar
seu aplicativo.
2. Antes de colocar os dados não confiáveis dentro de um elemento HTML garanta é codificada em HTML.
Codificação HTML usa caracteres como < e alterá-los em um formato seguro como &lt;
3. Antes de colocar os dados não confiáveis em um atributo HTML garanta é atributo HTML codificado.
Codificação do atributo HTML é um superconjunto de codificação HTML e codifica caracteres adicionais,
como "e '.
4. Antes de colocar os dados não confiáveis em JavaScript coloca os dados em um elemento HTML cujo
conteúdo você recuperar em tempo de execução. Se isso não é possível assegurar que os dados é
codificado com o JavaScript. Codificação de JavaScript usa caracteres perigosas para JavaScript e
substitui-los com seu hex, por exemplo < poderia ser codificado como \u003C .
5. Antes de colocar os dados não confiáveis em uma cadeia de caracteres de consulta de URL, verifique se é
codificada de URL.

Codificação HTML usando o Razor


O mecanismo Razor usado no MVC automaticamente codifica todos saída originados de variáveis, a menos que
você trabalhar arduamente para evitar que isso. Ele usa o atributo HTML regras de codificação, sempre que você
usar o @ diretiva. Como HTML a codificação do atributo é um superconjunto de codificação de HTML, que isso
significa que você não precisa se preocupar com você deve usar a codificação HTML ou codificação do atributo
HTML. Certifique-se de que você só usar em um contexto HTML, não ao tentar inserir não confiáveis de entrada
diretamente em JavaScript. Auxiliares de marcação codificará também usam parâmetros de marca de entrada.
Executar a seguinte exibição Razor;
@{
var untrustedInput = "<\"123\">";
}

@untrustedInput

Essa exibição mostra o conteúdo do untrustedInput variável. Essa variável inclui alguns caracteres que são usadas
em ataques XSS, ou seja, <, "e >. Examinando a fonte mostra a saída renderizada codificada como:

&lt;&quot;123&quot;&gt;

AVISO
Núcleo ASP.NET MVC fornece uma HtmlString classe que não é codificado automaticamente após a saída. Isso nunca
deve ser usado em combinação com entradas não confiáveis, pois isso irá expor uma vulnerabilidade XSS.

Usando o Razor de codificação JavaScript


Pode haver momentos que você deseja inserir um valor em JavaScript para processar no modo de exibição. Há
duas formas de fazer isso. A maneira mais segura para inserir valores simples é colocar o valor em um atributo
de dados de uma marca e recuperá-lo em seu JavaScript. Por exemplo:

@{
var untrustedInput = "<\"123\">";
}

<div
id="injectedData"
data-untrustedinput="@untrustedInput" />

<script>
var injectedData = document.getElementById("injectedData");

// All clients
var clientSideUntrustedInputOldStyle =
injectedData.getAttribute("data-untrustedinput");

// HTML 5 clients only


var clientSideUntrustedInputHtml5 =
injectedData.dataset.untrustedinput;

document.write(clientSideUntrustedInputOldStyle);
document.write("<br />")
document.write(clientSideUntrustedInputHtml5);
</script>

Isso produzirá o HTML a seguir


<div
id="injectedData"
data-untrustedinput="&lt;&quot;123&quot;&gt;" />

<script>
var injectedData = document.getElementById("injectedData");

var clientSideUntrustedInputOldStyle =
injectedData.getAttribute("data-untrustedinput");

var clientSideUntrustedInputHtml5 =
injectedData.dataset.untrustedinput;

document.write(clientSideUntrustedInputOldStyle);
document.write("<br />")
document.write(clientSideUntrustedInputHtml5);
</script>

Que, quando ele é executado, processará a seguir:

<"123">
<"123">

Você também pode chamar o codificador de JavaScript diretamente.

@using System.Text.Encodings.Web;
@inject JavaScriptEncoder encoder;

@{
var untrustedInput = "<\"123\">";
}

<script>
document.write("@encoder.Encode(untrustedInput)");
</script>

Isso será renderizado no navegador:

<script>
document.write("\u003C\u0022123\u0022\u003E");
</script>

AVISO
Não concatene a entrada não confiável em JavaScript para criar elementos de DOM. Você deve usar createElement() e
atribuir valores de propriedade adequadamente como node.TextContent= , ou use element.SetAttribute() /
element[attribute]= contrário você expor ao XSS baseado em DOM.

Acessando codificadores no código


Os codificadores HTML, JavaScript e URL estão disponíveis para o código de duas maneiras, você pode colocá-
los por meio de injeção de dependência ou você pode usar os codificadores padrão contidos a
System.Text.Encodings.Web namespace. Se você usar os codificadores padrão e qualquer aplicada a intervalos de
caracteres a serem tratados como seguro não terão efeito - os codificadores padrão usem as regras de
codificação mais seguras possível.
Para usar os codificadores configuráveis por meio de DI seus construtores devem levar uma HtmlEncoder,
JavaScriptEncoder e UrlEncoder parâmetro conforme apropriado. Por exemplo,

public class HomeController : Controller


{
HtmlEncoder _htmlEncoder;
JavaScriptEncoder _javaScriptEncoder;
UrlEncoder _urlEncoder;

public HomeController(HtmlEncoder htmlEncoder,


JavaScriptEncoder javascriptEncoder,
UrlEncoder urlEncoder)
{
_htmlEncoder = htmlEncoder;
_javaScriptEncoder = javascriptEncoder;
_urlEncoder = urlEncoder;
}
}

Parâmetros de URL de codificação


Se você deseja criar uma cadeia de caracteres de consulta de URL com entradas não confiáveis, como um valor,
use o UrlEncoder para codificar o valor. Por exemplo,

var example = "\"Quoted Value with spaces and &\"";


var encodedValue = _urlEncoder.Encode(example);

Após a codificação de encodedValue variável conterá %22Quoted%20Value%20with%20spaces%20and%20%26%22 . Espaços,


aspas, pontuação e outros caracteres não seguros será porcentagem codificado para seu valor hexadecimal, por
exemplo, um caractere de espaço será % 20.

AVISO
Não use a entrada não confiável como parte de um caminho de URL. Sempre passe a entrada não confiável como um valor
de cadeia de caracteres de consulta.

Personalizando os codificadores
Por padrão, codificadores usam uma lista de segurança limitada para o intervalo Unicode Latim básico e codificar
todos os caracteres fora do intervalo como seus equivalentes do código de caractere. Esse comportamento
também afeta o processamento Razor TagHelper e HtmlHelper como ele usará os codificadores para suas
cadeias de caracteres de saída.
O raciocínio por trás disso é proteger contra erros de navegador desconhecido ou futuro (bugs de navegador
anterior tiveram ultrapassado a análise com base no processamento de caracteres estendidos). Se seu site faz uso
intenso de caracteres não latinos, como o chinês, cirílico ou outras pessoas isso provavelmente não é o
comportamento desejado.
Você pode personalizar as listas de codificador seguro para incluir Unicode intervalos adequados ao seu
aplicativo durante a inicialização, em ConfigureServices() .
Por exemplo, usando a configuração padrão que você pode usar um HtmlHelper Razor assim;

<p>This link text is in Chinese: @Html.ActionLink("汉语/漢語", "Index")</p>


Quando você exibir a origem da página da web, você verá que tenha sido processado como a seguir, com o texto
chinês codificado;

<p>This link text is in Chinese: <a href="/">&#x6C49;&#x8BED;/&#x6F22;&#x8A9E;</a></p>

Para ampliar os caracteres tratados como seguro pelo codificador você inseriria a linha a seguir para o
ConfigureServices() método startup.cs ;

services.AddSingleton<HtmlEncoder>(
HtmlEncoder.Create(allowedRanges: new[] { UnicodeRanges.BasicLatin,
UnicodeRanges.CjkUnifiedIdeographs }));

Este exemplo amplia a lista segura para incluir o CjkUnifiedIdeographs de intervalo de Unicode. Agora se torna a
saída renderizada

<p>This link text is in Chinese: <a href="/">汉语/漢語</a></p>

Listar seguro intervalos são especificados como gráficos de código Unicode, não os idiomas. O padrão Unicode
tem uma lista de código gráficos você pode usar para localizar o gráfico que contém os caracteres. Cada
codificador, Html, JavaScript e a Url, deve ser configurado separadamente.

OBSERVAÇÃO
Personalização da lista de afeta somente os codificadores originados por meio de injeção de dependência. Se você acessar
diretamente um codificador via System.Text.Encodings.Web.*Encoder.Default , em seguida, o padrão, Latim básico
somente lista segura será usada.

Qual codificação take deve colocar?


Gerais aceito prática é que a codificação ocorre no ponto de saída e valores codificados nunca devem ser
armazenados em um banco de dados. Codificação no ponto de saída permite que você altere o uso de dados, por
exemplo, de HTML para um valor de cadeia de caracteres de consulta. Ele também permite que você pesquise
facilmente os dados sem a necessidade de codificar valores antes de pesquisar e permite que você se beneficie de
quaisquer alterações ou correções feitas codificadores.

Validação como uma técnica de prevenção de XSS


A validação pode ser uma ferramenta útil limitar ataques XSS. Por exemplo, uma cadeia de caracteres numérica
simple que contém somente os caracteres 0-9 não aciona um ataque XSS. A validação se torna mais complicada
que você deseja aceitar HTML na entrada do usuário - análise de entrada HTML é difícil, se não impossível.
Redução e outros formatos de texto seria uma opção mais segura para a entrada avançada. Você nunca deve
depender somente de validação. Sempre codifica a entrada não confiável antes de saída, não importa o que você
executou a validação.
Habilitar solicitações entre origens (CORS) no núcleo
do ASP.NET
10/04/2018 • 16 min to read • Edit Online

Por Mike Wasson, Shayne Boyer, e Tom Dykstra


Segurança do navegador impede que uma página da web fazer solicitações do AJAX para outro domínio. Essa
restrição é chamada de política de mesma origeme impede que um site mal-intencionado lendo dados
confidenciais de outro site. No entanto, às vezes, convém permitir que outros sites fazer solicitações entre
origens sua API da web.
Entre o compartilhamento de recursos de origem (CORS ) é um padrão de W3C que permite que um servidor
atenuar a política de mesma origem. Usando CORS, um servidor pode permitir explicitamente algumas
solicitações entre origens durante a rejeição de outras pessoas. CORS é mais segura e mais flexível que técnicas
anteriores como JSONP. Este tópico mostra como habilitar o CORS em um aplicativo do ASP.NET Core.

O que é "origem mesmo"?


Duas URLs têm a mesma origem se eles tiverem portas, hosts e esquemas idênticas. (RFC 6454)
Essas duas URLs têm a mesma origem:
http://example.com/foo.html

http://example.com/bar.html

Essas URLs têm diferentes origens que anterior dois:


http://example.net -Domínio diferente
http://www.example.com/foo.html -Subdomínio diferente
https://example.com/foo.html -Esquema diferente
http://example.com:9000/foo.html -Porta diferente

OBSERVAÇÃO
Internet Explorer não considera a porta ao comparar as origens.

Configuração de CORS
Para configurar CORS para o seu aplicativo, adicione o Microsoft.AspNetCore.Cors pacote ao seu projeto.
Adicione os serviços CORS em Startup.cs:

public void ConfigureServices(IServiceCollection services)


{
services.AddCors();
}
Habilitar o CORS com middleware
Para habilitar o CORS para todo o seu aplicativo adicionar o middleware CORS ao seu pipeline de solicitação
usando o UseCors método de extensão. Observe que o middleware CORS deve preceder quaisquer pontos de
extremidade definidos no aplicativo que você deseja dar suporte a solicitações entre origens (ex. antes de
qualquer chamada para UseMvc ).
Você pode especificar uma política entre origens ao adicionar o uso de middleware CORS a CorsPolicyBuilder
classe. Há duas formas de fazer isso. A primeira é chamar UseCors com uma expressão lambda:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)


{
loggerFactory.AddConsole();

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

// Shows UseCors with CorsPolicyBuilder.


app.UseCors(builder =>
builder.WithOrigins("http://example.com"));

app.Run(async (context) =>


{
await context.Response.WriteAsync("Hello World!");
});

Observação: a URL deve ser especificada sem uma barra à direita ( / ). Se a URL termina com / ,a
comparação retornará false e nenhum cabeçalho será retornado.
O lambda leva um CorsPolicyBuilder objeto. Você encontrará uma lista da opções de configuração mais adiante
neste tópico. Neste exemplo, a política permite que as solicitações entre origens de http://example.com e não há
outras origens.
Observe que CorsPolicyBuilder tem uma API fluente, portanto, é possível encadear chamadas de método:

app.UseCors(builder =>
builder.WithOrigins("http://example.com")
.AllowAnyHeader()
);

A segunda abordagem é definir uma ou mais políticas CORS nomeadas e, em seguida, selecione a política por
nome em tempo de execução.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
builder => builder.WithOrigins("http://example.com"));
});
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)


{
loggerFactory.AddConsole();

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

// Shows UseCors with named policy.


app.UseCors("AllowSpecificOrigin");
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}

Este exemplo adiciona uma política CORS denominada "AllowSpecificOrigin". Para selecionar a política, passe o
nome para UseCors .

Habilitando CORS no MVC


Como alternativa, você pode usar MVC para aplicar CORS específicos por ação, por controlador ou globalmente
para todos os controladores. Ao usar o MVC para habilitar o CORS os mesmos serviços CORS são usados, mas
o middleware CORS não estiver.
Cada ação
Para especificar uma política CORS para uma ação específica, adicione o [EnableCors] de atributo para a ação.
Especifique o nome da política.

[HttpGet]
[EnableCors("AllowSpecificOrigin")]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}

Por controlador
Para especificar a política CORS para um controlador específico, adicione o [EnableCors] de atributo para a
classe do controlador. Especifique o nome da política.

[Route("api/[controller]")]
[EnableCors("AllowSpecificOrigin")]
public class ValuesController : Controller

Globalmente
Você pode habilitar o CORS globalmente para todos os controladores, adicionando o
CorsAuthorizationFilterFactory filtro para a coleção de filtros globais:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new CorsAuthorizationFilterFactory("AllowSpecificOrigin"));
});
}

A ordem de precedência é: ação, controlador, global. Políticas de nível de ação têm precedência sobre políticas de
nível de controlador e políticas no nível do controlador têm precedência sobre as políticas globais.
Desabilitar CORS
Para desabilitar CORS para um controlador ou ação, use o [DisableCors] atributo.

[HttpGet("{id}")]
[DisableCors]
public string Get(int id)
{
return "value";
}

Opções de política CORS


Esta seção descreve as várias opções que podem ser definidas em uma política CORS.
Definir as origens permitidas
Definir os métodos HTTP permitidos
Definir os cabeçalhos de solicitação permitido
Definir os cabeçalhos de resposta exposto
Credenciais nas solicitações entre origens
Definir o tempo de expiração de simulação
Para algumas opções pode ser útil ler funciona como CORS primeiro.
Definir as origens permitidas
Para permitir que um ou mais origens específicas:

options.AddPolicy("AllowSpecificOrigins",
builder =>
{
builder.WithOrigins("http://example.com", "http://www.contoso.com");
});

Para permitir que todas as origens:

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace CorsExample4
{
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?
LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
// BEGIN01
options.AddPolicy("AllowSpecificOrigins",
builder =>
{
builder.WithOrigins("http://example.com", "http://www.contoso.com");
});
// END01

// BEGIN02
options.AddPolicy("AllowAllOrigins",
builder =>
{
builder.AllowAnyOrigin();
});
// END02

// BEGIN03
options.AddPolicy("AllowSpecificMethods",
builder =>
{
builder.WithOrigins("http://example.com")
.WithMethods("GET", "POST", "HEAD");
});
// END03

// BEGIN04
options.AddPolicy("AllowAllMethods",
builder =>
{
builder.WithOrigins("http://example.com")
.AllowAnyMethod();
});
// END04

// BEGIN05
options.AddPolicy("AllowHeaders",
builder =>
{
builder.WithOrigins("http://example.com")
.WithHeaders("accept", "content-type", "origin", "x-custom-header");
});
// END05

// BEGIN06
options.AddPolicy("AllowAllHeaders",
builder =>
{
builder.WithOrigins("http://example.com")
.AllowAnyHeader();
});
// END06

// BEGIN07
options.AddPolicy("ExposeResponseHeaders",
builder =>
{
builder.WithOrigins("http://example.com")
.WithExposedHeaders("x-custom-header");
});
// END07

// BEGIN08
options.AddPolicy("AllowCredentials",
builder =>
{
builder.WithOrigins("http://example.com")
.AllowCredentials();
});
// END08

// BEGIN09
options.AddPolicy("SetPreflightExpiration",
builder =>
{
builder.WithOrigins("http://example.com")
.SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
});
// END09
});
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();

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

app.UseCors("AllowSpecificOrigins");
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}
}
}

Considere cuidadosamente antes de permitir solicitações de qualquer origem. Isso significa que literalmente
qualquer site pode fazer chamadas AJAX para sua API.
Definir os métodos HTTP permitidos
Para permitir que todos os métodos HTTP:

options.AddPolicy("AllowAllMethods",
builder =>
{
builder.WithOrigins("http://example.com")
.AllowAnyMethod();
});

Isso afeta a solicitações preliminares e cabeçalho Access-controle-Allow -Methods.


Definir os cabeçalhos de solicitação permitido
Uma solicitação de simulação de CORS pode incluir um cabeçalho Access-Control-Request-Headers, listando os
cabeçalhos HTTP definidos pelo aplicativo (os chamados "author cabeçalhos de solicitação").
Para cabeçalhos de específico de lista de permissões:
options.AddPolicy("AllowHeaders",
builder =>
{
builder.WithOrigins("http://example.com")
.WithHeaders("accept", "content-type", "origin", "x-custom-header");
});

Para permitir que todos os cabeçalhos de solicitação do autor:

options.AddPolicy("AllowAllHeaders",
builder =>
{
builder.WithOrigins("http://example.com")
.AllowAnyHeader();
});

Navegadores não são totalmente consistentes em como eles definidos Access-Control-Request-Headers. Se


você definir cabeçalhos para algo diferente de "*", você deve incluir pelo menos "aceitar", "content-type" e
"origem", além de quaisquer cabeçalhos personalizados que você deseja dar suporte.
Definir os cabeçalhos de resposta exposto
Por padrão, o navegador não expõe todos os cabeçalhos de resposta para o aplicativo. (Consulte
http://www.w3.org/TR/cors/#simple-response-header .) Os cabeçalhos de resposta que estão disponíveis por
padrão são:
Cache-Control
Content-Language
Tipo de conteúdo
Expirar
Last-Modified
Pragma
A especificação CORS chama esses cabeçalhos de resposta simples. Para disponibilizar outros cabeçalhos para o
aplicativo:

options.AddPolicy("ExposeResponseHeaders",
builder =>
{
builder.WithOrigins("http://example.com")
.WithExposedHeaders("x-custom-header");
});

Credenciais nas solicitações entre origens


Credenciais requerem tratamento especial em uma solicitação CORS. Por padrão, o navegador não envia
credenciais com uma solicitação entre origens. As credenciais incluem cookies, bem como os esquemas de
autenticação HTTP. Para enviar as credenciais com uma solicitação entre origens, o cliente deve definir
XMLHttpRequest.withCredentials como true.
Usando XMLHttpRequest diretamente:
var xhr = new XMLHttpRequest();
xhr.open('get', 'http://www.example.com/api/test');
xhr.withCredentials = true;

Em jQuery:

$.ajax({
type: 'get',
url: 'http://www.example.com/home',
xhrFields: {
withCredentials: true
}

Além disso, o servidor deve permitir que as credenciais. Para permitir que as credenciais de cross-origin:

options.AddPolicy("AllowCredentials",
builder =>
{
builder.WithOrigins("http://example.com")
.AllowCredentials();
});

Agora, a resposta HTTP incluirá um cabeçalho Access-controle-Allow -Credentials, que informa ao navegador
que o servidor permite que as credenciais para uma solicitação entre origens.
Se o navegador envia as credenciais, mas a resposta não incluir um cabeçalho Access-controle-Allow -
Credentials válido, o navegador não expõe a resposta para o aplicativo e haverá falha na solicitação AJAX.
Tenha cuidado ao permitir que as credenciais de entre origens. Um site da Web em outro domínio pode enviar
credenciais do usuário conectado para o aplicativo em nome do usuário sem o conhecimento do usuário. A
especificação de CORS também define essa configuração origens para "*" (todas as origens) não é válido se o
Access-Control-Allow-Credentials cabeçalho estiver presente.

Definir o tempo de expiração de simulação


O cabeçalho de acesso-controle-Max-Age Especifica quanto tempo a resposta à solicitação de simulação pode
ser armazenados em cache. Para definir esse cabeçalho:

options.AddPolicy("SetPreflightExpiration",
builder =>
{
builder.WithOrigins("http://example.com")
.SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
});

Como funciona o CORS


Esta seção descreve o que acontece em uma solicitação CORS no nível das mensagens HTTP. É importante
entender o funcionamento de CORS para que a política CORS possa ser configurada corretamente e
troubleshooted quando ocorrerem comportamentos inesperados.
A especificação CORS apresenta vários novos cabeçalhos HTTP que habilitam solicitações entre origens. Se um
navegador dá suporte a CORS, ele define esses cabeçalhos automaticamente para solicitações entre origens.
Código JavaScript personalizado não é necessário habilitar o CORS.
Aqui está um exemplo de uma solicitação entre origens. O Origin cabeçalho fornece o domínio do site que está
fazendo a solicitação:
GET http://myservice.azurewebsites.net/api/test HTTP/1.1
Referer: http://myclient.azurewebsites.net/
Accept: */*
Accept-Language: en-US
Origin: http://myclient.azurewebsites.net
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net

Se o servidor permite que a solicitação, ele define o cabeçalho Access-Control-Allow -Origin na resposta. O valor
desse cabeçalho corresponde o cabeçalho de origem da solicitação tanto é o valor de curinga "*", o que significa
que qualquer origem é permitida:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/plain; charset=utf-8
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Date: Wed, 20 May 2015 06:27:30 GMT
Content-Length: 12

Test message

Se a resposta não incluir o cabeçalho Access-Control-Allow -Origin, a solicitação AJAX falhará. Especificamente,
o navegador não permite a solicitação. Mesmo se o servidor retornará uma resposta bem-sucedida, o navegador
não disponibiliza a resposta para o aplicativo cliente.
Solicitações de simulação
Para algumas solicitações CORS, o navegador envia uma solicitação de adicional, chamada uma "solicitação de
simulação," antes de enviar a solicitação real do recurso. O navegador pode ignorar a solicitação de simulação se
as seguintes condições forem verdadeiras:
O método de solicitação é GET, HEAD ou POST, e
O aplicativo não definir os cabeçalhos de solicitação diferente de idioma do conteúdo Accept, Accept-
Language, Content-Type ou última--ID do evento, e
O cabeçalho Content-Type (se definido) é um dos seguintes:
application/x-www -form-urlencoded
multipart/form-data
texto/sem formatação
A regra sobre cabeçalhos de solicitação se aplica aos cabeçalhos que o aplicativo define chamando
setRequestHeader no objeto XMLHttpRequest. (A especificação CORS chama esses cabeçalhos de
solicitação"autor".) A regra não se aplica aos cabeçalhos que pode definir o navegador, como o agente do
usuário, o Host ou o comprimento do conteúdo.
Aqui está um exemplo de uma solicitação de simulação:
OPTIONS http://myservice.azurewebsites.net/api/test HTTP/1.1
Accept: */*
Origin: http://myclient.azurewebsites.net
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: accept, x-my-custom-header
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net
Content-Length: 0

A solicitação de simulação usa o método HTTP OPTIONS. Ele inclui dois cabeçalhos especiais:
Access-Control-Request-Method: O método HTTP que será usado para a solicitação real.
Access-Control-Request-Headers: Uma lista de cabeçalhos de solicitação que o aplicativo definido na
solicitação atual. (Novamente, isso não inclui os cabeçalhos que define o navegador).
Aqui está um exemplo de resposta, supondo que o servidor permite que a solicitação:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 0
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Access-Control-Allow-Headers: x-my-custom-header
Access-Control-Allow-Methods: PUT
Date: Wed, 20 May 2015 06:33:22 GMT

A resposta inclui um cabeçalho de acesso--permitir-métodos de controle que lista os métodos permitidos e,


opcionalmente, um cabeçalho Access-Control-permitir-Headers, que lista os cabeçalhos permitidos. Se a
solicitação de simulação for bem-sucedida, o navegador envia a solicitação real, conforme descrito
anteriormente.
Compartilhar cookies entre aplicativos com o
ASP.NET e ASP.NET Core
10/04/2018 • 10 min to read • Edit Online

Por Rick Anderson e Luke Latham


Sites geralmente consistem em aplicativos web individuais trabalhando juntos. Para fornecer uma experiência de
logon único (SSO ), aplicativos web dentro de um site devem compartilhar os cookies de autenticação. Para dar
suporte a esse cenário, a pilha de proteção de dados permite o compartilhamento de autenticação de cookie
Katana e permissões de autenticação de cookie do ASP.NET Core.
Exibir ou baixar código de exemplo (como baixar)
O exemplo ilustra o cookie compartilhamento entre três aplicativos que usam autenticação de cookie:
Aplicativo do ASP.NET Core 2.0 Razor páginas sem usar a identidade do ASP.NET Core
Aplicativo do ASP.NET Core 2.0 MVC com a identidade do ASP.NET Core
Aplicativo do MVC do ASP.NET Framework 4.6.1 com a identidade do ASP.NET
Nos exemplos a seguirem:
O nome do cookie de autenticação é definido como um valor comum de .AspNet.SharedCookie .
O AuthenticationType é definido como Identity.Application explicitamente ou por padrão.
Um nome de aplicativo comum é usado para habilitar o sistema de proteção de dados compartilhar as chaves
de proteção de dados ( SharedCookieApp ).
Identity.Application é usado como o esquema de autenticação. Qualquer esquema for usada, ele deve ser
usado de forma consistente dentro e entre os aplicativos de cookie compartilhado como o esquema padrão ou
definindo-o explicitamente. O esquema é usado ao criptografar e descriptografar cookies, para que um
esquema consistente deve ser usado em aplicativos.
Um comum chave de proteção de dados armazenamento local é usado. O aplicativo de exemplo usa uma pasta
chamada token de autenticação na raiz da solução para armazenar as chaves de proteção de dados.
Nos aplicativos do ASP.NET Core, PersistKeysToFileSystem é usado para definir o local de armazenamento de
chaves. SetApplicationName é usado para configurar um nome de aplicativo compartilhado comum.
No aplicativo do .NET Framework, o middleware de autenticação de cookie usa uma implementação de
DataProtectionProvider. DataProtectionProvider fornece serviços de proteção de dados para a criptografia e
descriptografia de dados de carga do cookie de autenticação. O DataProtectionProvider instância é isolada do
sistema de proteção de dados usado por outras partes do aplicativo.
DataProtectionProvider.Create (DirectoryInfo, ação<IDataProtectionBuilder >) aceita um DirectoryInfo
para especificar o local de armazenamento de chaves de proteção de dados. O aplicativo de exemplo
fornece o caminho do token de autenticação pasta DirectoryInfo .
DataProtectionBuilderExtensions.SetApplicationName define o nome comum do aplicativo.
DataProtectionProvider requer o Microsoft.AspNetCore.DataProtection.Extensions pacote NuGet. Para
obter esse pacote para o ASP.NET Core 2.0 e posteriores aplicativos, fazer referência a
Microsoft.AspNetCore.All metapackage. Durante o direcionamento do .NET Framework, adicione uma
referência de pacote para Microsoft.AspNetCore.DataProtection.Extensions .

Compartilhar os cookies de autenticação entre aplicativos do ASP.NET


Core
Ao usar a identidade do ASP.NET Core:
ASP.NET Core 2.x
ASP.NET Core 1.x
No ConfigureServices método, use o ConfigureApplicationCookie método de extensão para configurar o serviço
de proteção de dados para cookies.

services.AddDataProtection()
.PersistKeysToFileSystem(GetKeyRingDirInfo())
.SetApplicationName("SharedCookieApp");

services.ConfigureApplicationCookie(options => {
options.Cookie.Name = ".AspNet.SharedCookie";
});

Chaves de proteção de dados e o nome do aplicativo devem ser compartilhados entre aplicativos. Em aplicativos
de amostra, GetKeyRingDirInfo retorna o local de armazenamento de chaves comuns para o
PersistKeysToFileSystem método. Use SetApplicationName para configurar um nome de aplicativo compartilhado
comum ( SharedCookieApp no exemplo). Para obter mais informações, consulte Configurando a proteção de dados.
Consulte o CookieAuthWithIdentity.Core project no código de exemplo (como baixar).
Ao usar cookies diretamente:
ASP.NET Core 2.x
ASP.NET Core 1.x

services.AddDataProtection()
.PersistKeysToFileSystem(GetKeyRingDirInfo())
.SetApplicationName("SharedCookieApp");

services.AddAuthentication("Identity.Application")
.AddCookie("Identity.Application", options =>
{
options.Cookie.Name = ".AspNet.SharedCookie";
});

Chaves de proteção de dados e o nome do aplicativo devem ser compartilhados entre aplicativos. Em aplicativos
de amostra, GetKeyRingDirInfo retorna o local de armazenamento de chaves comuns para o
PersistKeysToFileSystem método. Use SetApplicationName para configurar um nome de aplicativo compartilhado
comum ( SharedCookieApp no exemplo). Para obter mais informações, consulte Configurando a proteção de dados.
Consulte o CookieAuth.Core project no código de exemplo (como baixar).

Criptografar chaves de proteção de dados em repouso


Para implantações de produção, configure o DataProtectionProvider para criptografar as chaves em repouso com
a DPAPI ou um X509Certificate. Consulte chave de criptografia em repouso para obter mais informações.
ASP.NET Core 2.x
ASP.NET Core 1.x

services.AddDataProtection()
.ProtectKeysWithCertificate("thumbprint");
Compartilhamento de cookies de autenticação entre o ASP.NET 4. x e
aplicativos ASP.NET Core
ASP.NET 4. x aplicativos que usam o middleware de autenticação de cookie Katana podem ser configurados para
gerar os cookies de autenticação que são compatíveis com o middleware de autenticação de cookie do ASP.NET
Core. Isso permite atualizar aplicativos individuais de um grande site gradativamente, proporcionando uma
experiência de SSO suave em todo o site.

DICA
Quando um aplicativo usa Katana middleware de autenticação de cookie, ele chama UseCookieAuthentication do projeto
Startup.Auth.cs arquivo. Projetos de aplicativo web do ASP.NET 4. x criadas com o Visual Studio 2013 e depois, usar o
middleware de autenticação de cookie Katana por padrão.

OBSERVAÇÃO
Um aplicativo do ASP.NET 4. x deve ter como destino do .NET Framework 4.5.1 ou posterior. Caso contrário, os pacotes do
NuGet necessário falharem na instalação.

Para compartilhar os cookies de autenticação entre aplicativos do ASP.NET 4. x e ASP.NET Core, configure o
aplicativo do ASP.NET Core, conforme mencionado acima e configure os aplicativos do ASP.NET 4. x, seguindo as
etapas abaixo.
1. Instalar o pacote Microsoft.Owin.Security.Interop em cada aplicativo do ASP.NET 4. x.
2. Em Startup.Auth.cs, localize a chamada para UseCookieAuthentication e modificá-lo da seguinte maneira.
Altere o nome do cookie para corresponder ao nome usado pelo middleware de autenticação de cookie do
ASP.NET Core. Fornecer uma instância de um DataProtectionProvider inicializado para o local de
armazenamento de chaves de proteção de dados comuns. Certifique-se de que o nome do aplicativo é
definido como o nome de aplicativo comuns usado por todos os aplicativos que compartilham cookies,
SharedCookieApp no aplicativo de exemplo.

ASP.NET Core 2.x


ASP.NET Core 1.x
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Identity.Application",
CookieName = ".AspNet.SharedCookie",
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity =
SecurityStampValidator
.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) =>
user.GenerateUserIdentityAsync(manager))
},
TicketDataFormat = new AspNetTicketDataFormat(
new DataProtectorShim(
DataProtectionProvider.Create(GetKeyRingDirInfo(),
(builder) => { builder.SetApplicationName("SharedCookieApp"); })
.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
"Identity.Application",
"v2"))),
CookieManager = new ChunkingCookieManager()
});

// If not setting http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier and


// http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider,
// then set UniqueClaimTypeIdentifier to a claim that distinguishes unique users.
System.Web.Helpers.AntiForgeryConfig.UniqueClaimTypeIdentifier =
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name";

Consulte o CookieAuthWithIdentity.NETFramework project no código de exemplo (como baixar).


Ao gerar uma identidade de usuário, o tipo de autenticação deve corresponder ao tipo definido na
AuthenticationType com UseCookieAuthentication .

Models/IdentityModels.cs:

public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)


{
// Note the authenticationType must match the one defined in
CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, "Identity.Application");
// Add custom user claims here
return userIdentity;
}

Usar um banco de dados comum do usuário


Confirme que o sistema de identidade para cada aplicativo é apontado para o mesmo banco de dados do usuário.
Caso contrário, o sistema de identidade produz falhas em tempo de execução quando ele tenta coincidir com as
informações no cookie de autenticação com as informações no banco de dados.
Desempenho no ASP.NET Core
10/04/2018 • 1 min to read • Edit Online

Respostas de cache
Cache na memória
Trabalhar com um cache distribuído
Cache de resposta
Middleware de compactação de resposta
Respostas de cache no ASP.NET Core
10/04/2018 • 1 min to read • Edit Online

Cache na memória
Trabalhar com um cache distribuído
Detectar alterações com tokens de alteração
Cache de resposta
Middleware de Cache de Resposta
Auxiliar de marca de cache
Auxiliar de marca de cache distribuído
Cache de memória no núcleo do ASP.NET
04/05/2018 • 8 min to read • Edit Online

Por Rick Anderson, John Luo, e Steve Smith


Exibir ou baixar código de exemplo (como baixar)

Noções básicas de cache


O cache pode melhorar significativamente o desempenho e a escalabilidade de um aplicativo, reduzindo o
trabalho necessário para gerar o conteúdo. O armazenamento em cache funciona melhor com dados que
raramente são alterados. O cache faz uma cópia dos dados que pode ser retornada muito mais rapidamente do
que a partir da origem. Você deve escrever e testar seu aplicativo para que ele nunca dependa de dados
armazenados em cache.
O ASP.NET Core dá suporte a vários caches diferentes. O cache mais simples se baseia o IMemoryCache, que
representa um cache armazenado na memória do servidor web. Aplicativos que são executados em um farm
de servidores devem garantir que as sessões sejam aderentes ao usar o cache na memória. Sessões aderentes
garantem que todas as solicitações subsequentes de um cliente vão para o mesmo servidor. Por exemplo, uso
de aplicativos da Web do Azure Application Request Routing (ARR ) para rotear todas as solicitações
subsequentes para o mesmo servidor.
Sessões não temporária em um farm da web exigem um cache distribuído para evitar problemas de
consistência de cache. Para alguns aplicativos, um cache distribuído pode dar suporte a mais alta de expansão
de um cache na memória. Usar um cache distribuído libera a memória de cache para um processo externo.
O cache IMemoryCache removerá entradas de cache sob pressão de memória, a menos que a prioridade de
cache seja definida como CacheItemPriority.NeverRemove . Você pode definir o CacheItemPriority para ajustar a
prioridade com que o cache remove itens sob pressão de memória.
O cache de memória pode armazenar qualquer objeto, enquanto a interface de cache distribuída é limitada a
byte[] .

Usando IMemoryCache
O cache de memória é um service que é referenciado em seu aplicativo usando injeção de dependência.
Chamar AddMemoryCache em ConfigureServices :
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

public class Startup


{
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
services.AddMvc();
}

public void Configure(IApplicationBuilder app)


{
app.UseMvcWithDefaultRoute();
}
}

Solicitar a IMemoryCache instância no construtor:

public class HomeController : Controller


{
private IMemoryCache _cache;

public HomeController(IMemoryCache memoryCache)


{
_cache = memoryCache;
}

IMemoryCache requer o pacote do NuGet "Microsoft.Extensions.Caching.Memory".


O código a seguir usa TryGetValue para verificar se um horário está no cache. Se um tempo não é armazenado
em cache, uma nova entrada é criada e adicionada ao cache com definir.

public IActionResult CacheTryGetValueSet()


{
DateTime cacheEntry;

// Look for cache key.


if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
{
// Key not in cache, so get data.
cacheEntry = DateTime.Now;

// Set cache options.


var cacheEntryOptions = new MemoryCacheEntryOptions()
// Keep in cache for this time, reset time if accessed.
.SetSlidingExpiration(TimeSpan.FromSeconds(3));

// Save data in cache.


_cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions);
}

return View("Cache", cacheEntry);


}

A hora atual e o tempo em cache são exibidos:


@model DateTime?

<div>
<h2>Actions</h2>
<ul>
<li><a asp-controller="Home" asp-action="CacheTryGetValueSet">TryGetValue and Set</a></li>
<li><a asp-controller="Home" asp-action="CacheGet">Get</a></li>
<li><a asp-controller="Home" asp-action="CacheGetOrCreate">GetOrCreate</a></li>
<li><a asp-controller="Home" asp-action="CacheGetOrCreateAsync">GetOrCreateAsync</a></li>
<li><a asp-controller="Home" asp-action="CacheRemove">Remove</a></li>
</ul>
</div>

<h3>Current Time: @DateTime.Now.TimeOfDay.ToString()</h3>


<h3>Cached Time: @(Model == null ? "No cached entry found" : Model.Value.TimeOfDay.ToString())</h3>

O valor DateTime em cache permanecerá no cache enquanto houver solicitações dentro do tempo limite (e
nenhuma remoção devido à pressão de memória). A imagem abaixo mostra a hora atual e uma hora mais
antiga recuperada do cache:

O código a seguir usa GetOrCreate e GetOrCreateAsync para fazer o cache dos dados.
public IActionResult CacheGetOrCreate()
{
var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(3);
return DateTime.Now;
});

return View("Cache", cacheEntry);


}

public async Task<IActionResult> CacheGetOrCreateAsync()


{
var cacheEntry = await
_cache.GetOrCreateAsync(CacheKeys.Entry, entry =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(3);
return Task.FromResult(DateTime.Now);
});

return View("Cache", cacheEntry);


}

O código a seguir chama o método Get para buscar a hora em cache:

public IActionResult CacheGet()


{
var cacheEntry = _cache.Get<DateTime?>(CacheKeys.Entry);
return View("Cache", cacheEntry);
}

Consulte IMemoryCache métodos e CacheExtensions métodos para obter uma descrição dos métodos de
cache.

Usando MemoryCacheEntryOptions
O exemplo a seguir:
Define o tempo de expiração absoluta. Isso é o tempo máximo que a entrada pode ser armazenado em
cache e impede que o item se tornam muito desatualizados quando a expiração deslizante é renovada
continuamente.
Define uma hora de expiração deslizante. Solicitações que acessam esse item em cache irá redefinir o
relógio de expiração deslizante.
Define a prioridade de cache para CacheItemPriority.NeverRemove .
Define uma PostEvictionDelegate que será chamado depois que a entrada é removida do cache. O retorno
de chamada é executado em um thread diferente do código que remove o item do cache.
public IActionResult CreateCallbackEntry()
{
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Pin to cache.
.SetPriority(CacheItemPriority.NeverRemove)
// Add eviction callback
.RegisterPostEvictionCallback(callback: EvictionCallback, state: this);

_cache.Set(CacheKeys.CallbackEntry, DateTime.Now, cacheEntryOptions);

return RedirectToAction("GetCallbackEntry");
}

public IActionResult GetCallbackEntry()


{
return View("Callback", new CallbackViewModel
{
CachedTime = _cache.Get<DateTime?>(CacheKeys.CallbackEntry),
Message = _cache.Get<string>(CacheKeys.CallbackMessage)
});
}

public IActionResult RemoveCallbackEntry()


{
_cache.Remove(CacheKeys.CallbackEntry);
return RedirectToAction("GetCallbackEntry");
}

private static void EvictionCallback(object key, object value,


EvictionReason reason, object state)
{
var message = $"Entry was evicted. Reason: {reason}.";
((HomeController)state)._cache.Set(CacheKeys.CallbackMessage, message);
}

Dependências de cache
O exemplo a seguir mostra como expirar uma entrada de cache, se uma entrada dependente expirar. Um
CancellationChangeToken é adicionado ao item em cache. Quando Cancel é chamado de
CancellationTokenSource , ambas as entradas de cache são removidas.
public IActionResult CreateDependentEntries()
{
var cts = new CancellationTokenSource();
_cache.Set(CacheKeys.DependentCTS, cts);

using (var entry = _cache.CreateEntry(CacheKeys.Parent))


{
// expire this entry if the dependant entry expires.
entry.Value = DateTime.Now;
entry.RegisterPostEvictionCallback(DependentEvictionCallback, this);

_cache.Set(CacheKeys.Child,
DateTime.Now,
new CancellationChangeToken(cts.Token));
}

return RedirectToAction("GetDependentEntries");
}

public IActionResult GetDependentEntries()


{
return View("Dependent", new DependentViewModel
{
ParentCachedTime = _cache.Get<DateTime?>(CacheKeys.Parent),
ChildCachedTime = _cache.Get<DateTime?>(CacheKeys.Child),
Message = _cache.Get<string>(CacheKeys.DependentMessage)
});
}

public IActionResult RemoveChildEntry()


{
_cache.Get<CancellationTokenSource>(CacheKeys.DependentCTS).Cancel();
return RedirectToAction("GetDependentEntries");
}

private static void DependentEvictionCallback(object key, object value,


EvictionReason reason, object state)
{
var message = $"Parent entry was evicted. Reason: {reason}.";
((HomeController)state)._cache.Set(CacheKeys.DependentMessage, message);
}

Usando um CancellationTokenSource permite que várias entradas de cache a ser removido como um grupo.
Com o using padrão no código acima, as entradas de cache criado dentro de using bloco herdará as
configurações de expiração e gatilhos.

Observações adicionais
Ao usar um retorno de chamada para preencher novamente um item de cache:
Várias solicitações podem encontrar o valor de chave em cache vazio porque o retorno de chamada
não foi concluída.
Isso pode resultar em vários threads popular novamente o item em cache.
Quando uma entrada de cache é usada para criar outra, o filho copia a entrada de pai tokens de
expiração e as configurações de expiração do tempo. O filho não está expirada pela remoção manual ou
atualização da entrada do pai.

Recursos adicionais
Trabalhar com um cache distribuído
Detectar alterações com tokens de alteração
Cache de resposta
Middleware de Cache de Resposta
Auxiliar de marca de cache
Auxiliar de marca de cache distribuído
Trabalhar com um cache distribuído no núcleo do
ASP.NET
10/04/2018 • 11 min to read • Edit Online

Por Steve Smith


Os caches distribuídos podem melhorar o desempenho e escalabilidade de aplicativos do ASP.NET Core,
especialmente quando hospedados em um ambiente de farm de servidor ou de nuvem. Este artigo explica
como trabalhar com implementações e abstrações de cache distribuído internos do ASP.NET Core.
Exibir ou baixar código de exemplo (como baixar)

O que é um cache distribuído


Um cache distribuído é compartilhado por vários servidores de aplicativo (consulte Noções básicas de Cache).
As informações em cache não são armazenadas na memória dos servidores web individuais, e os dados
armazenados em cache estão disponíveis para todos os servidores do aplicativo. Isso oferece várias vantagens:
1. Dados armazenados em cache são coerentes em todos os servidores web. Os usuários não ver
resultados diferentes dependendo de qual web server manipula a solicitação
2. Dados armazenados em cache sobrevive reinicializações do servidor web e implantações. Servidores
web individuais podem ser removidas ou adicionadas sem afetar o cache.
3. O repositório de dados de origem tem menos solicitações feitas a ele (de cache em todos os com vários
caches de memória ou não).

OBSERVAÇÃO
Se usar um Cache distribuído do SQL Server, algumas dessas vantagens são somente verdadeiras se uma instância
separada do banco de dados é usada para o cache de dados de origem do aplicativo.

Como qualquer cache, um cache distribuído pode melhorar drasticamente capacidade de resposta do
aplicativo, como normalmente os dados podem ser recuperados do cache muito mais rápido do que de um
banco de dados relacional (ou serviço da web).
Configuração de cache é específico da implementação. Este artigo descreve como configurar ambos Redis e
distribuídas do SQL Server armazena em cache. Independentemente de qual implementação for selecionada, o
aplicativo interage com o cache usando uma comum IDistributedCache interface.

A Interface IDistributedCache
O IDistributedCache interface inclui métodos síncronos e assíncronos. A interface permite que itens sejam
adicionados, recuperar e removido da implementação de cache distribuído. O IDistributedCache interface
inclui os seguintes métodos:
Get, GetAsync
Usa uma chave de cadeia de caracteres e recupera um item em cache como um byte[] se encontrado no
cache.
Set, SetAsync
Adiciona um item (como byte[] ) para o cache usando uma chave de cadeia de caracteres.
Atualização de RefreshAsync
Atualiza um item em cache com base em sua chave, redefinir seu tempo limite de expiração deslizante (se
houver).
Remove, RemoveAsync
Remove uma entrada de cache com base em sua chave.
Para usar o IDistributedCache interface:
1. Adicione os pacotes do NuGet necessários para o arquivo de projeto.
2. Configurar a implementação específica de IDistributedCache no seu Startup da classe
ConfigureServices método e adicione-o para o contêiner existe.

3. O aplicativo Middleware ou classes do controlador MVC, solicite uma instância do IDistributedCache a


partir do construtor. A instância será fornecida pelo injeção de dependência (DI).

OBSERVAÇÃO
Não é necessário usar um tempo de vida Singleton ou escopo para IDistributedCache instâncias (pelo menos para as
implementações internas). Você também pode criar uma instância sempre que talvez seja necessário um (em vez de usar
injeção de dependência), mas isso pode tornar seu código mais difícil de teste e viola o princípio de dependências
explícitas.

O exemplo a seguir mostra como usar uma instância de IDistributedCache em um componente de middleware
simples:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Distributed;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DistCacheSample
{
public class StartTimeHeader
{
private readonly RequestDelegate _next;
private readonly IDistributedCache _cache;

public StartTimeHeader(RequestDelegate next,


IDistributedCache cache)
{
_next = next;
_cache = cache;
}

public async Task Invoke(HttpContext httpContext)


{
string startTimeString = "Not found.";
var value = await _cache.GetAsync("lastServerStartTime");
if (value != null)
{
startTimeString = Encoding.UTF8.GetString(value);
}

httpContext.Response.Headers.Append("Last-Server-Start-Time", startTimeString);

await _next.Invoke(httpContext);
}
}

// Extension method used to add the middleware to the HTTP request pipeline.
public static class StartTimeHeaderExtensions
{
public static IApplicationBuilder UseStartTimeHeader(this IApplicationBuilder builder)
{
return builder.UseMiddleware<StartTimeHeader>();
}
}
}

No código acima, o valor armazenado em cache é de leitura, mas nunca foi gravado. Neste exemplo, o valor é
definido apenas quando um servidor é inicializado e não é alterado. Em um cenário de vários servidores, o
servidor mais recente para iniciar substituirá quaisquer valores anteriores que foram definidas por outros
servidores. O Get e Set métodos usam o byte[] tipo. Portanto, o valor de cadeia de caracteres deve ser
convertido usando Encoding.UTF8.GetString (para Get ) e Encoding.UTF8.GetBytes (para Set ).
O código a seguir de Startup.cs mostra o valor que está sendo definido:
public void Configure(IApplicationBuilder app,
IDistributedCache cache)
{
var serverStartTimeString = DateTime.Now.ToString();
byte[] val = Encoding.UTF8.GetBytes(serverStartTimeString);
var cacheEntryOptions = new DistributedCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(30));
cache.Set("lastServerStartTime", val, cacheEntryOptions);

OBSERVAÇÃO
Como IDistributedCache é configurado no ConfigureServices método, ele está disponível para o Configure
método como um parâmetro. Adicioná-lo como um parâmetro permitirá que a instância configurada ser fornecido por
meio de injeção de dependência.

Usando um cache Redis distribuído


Redis é um repositório de dados na memória do código-fonte aberto, que geralmente é usado como um cache
distribuído. Você pode usá-lo localmente, e você pode configurar um Cache Redis do Azure para seus
aplicativos hospedados do Azure ASP.NET Core. Seu aplicativo ASP.NET Core configura a implementação de
cache usando uma RedisDistributedCache instância.
Configurar a implementação do Redis em ConfigureServices e acessá-lo no código do aplicativo solicitando
uma instância de IDistributedCache (consulte o código acima).
No código de exemplo, um RedisCache implementação é usada quando o servidor está configurado para um
Staging ambiente. Assim, o ConfigureStagingServices método configura o RedisCache :

/// <summary>
/// Use Redis Cache in Staging
/// </summary>
/// <param name="services"></param>
public void ConfigureStagingServices(IServiceCollection services)
{

services.AddDistributedRedisCache(options =>
{
options.Configuration = "localhost";
options.InstanceName = "SampleInstance";
});
}

OBSERVAÇÃO
Para instalar o Redis em seu computador local, instale o pacote chocolatey https://chocolatey.org/packages/redis-64/ e
executar redis-server em um prompt de comando.

Usando um SQL Server de cache distribuído


A implementação de SqlServerCache permite que o cache distribuído usar um banco de dados do SQL Server
como seu armazenamento de backup. Para SQL Server de criar tabela, você pode usar a ferramenta de cache
de sql, a ferramenta cria uma tabela com o nome e o esquema que você especificar.
Para usar a ferramenta de cache de sql, adicione SqlConfig.Tools para o <ItemGroup> elemento o . csproj de
arquivo e execute a restauração de dotnet.

<ItemGroup>
<DotNetCliToolReference Include="Microsoft.Extensions.Caching.SqlConfig.Tools" Version="1.0.0-msbuild3-
final" />
</ItemGroup>

Teste SqlConfig.Tools executando o seguinte comando

C:\DistCacheSample\src\DistCacheSample>dotnet sql-cache create --help

ferramenta de cache de SQL exibirá a Ajuda de uso e opções de comando, agora você pode criar tabelas no sql
server, executando o comando "criar cache de sql":

C:\DistCacheSample\src\DistCacheSample>dotnet sql-cache create "Data Source=(localdb)\v11.0;Initial


Catalog=DistCache;Integrated Security=True;" dbo TestCache
info: Microsoft.Extensions.Caching.SqlConfig.Tools.Program[0]
Table and index were created successfully.

A tabela criada tem o esquema a seguir:

Como todas as implementações de cache, seu aplicativo deve obter e definir valores de cache usando uma
instância de IDistributedCache , não um SqlServerCache . O exemplo implementa SqlServerCache no
Production ambiente (para que ele é configurado em ConfigureProductionServices ).

/// Use SQL Server Cache in Production


/// </summary>
/// <param name="services"></param>
public void ConfigureProductionServices(IServiceCollection services)
{

services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = @"Data Source=(localdb)\v11.0;Initial Catalog=DistCache;Integrated
Security=True;";
options.SchemaName = "dbo";
options.TableName = "TestCache";
});

OBSERVAÇÃO
O ConnectionString (e, opcionalmente, SchemaName e TableName ) normalmente devem ser armazenadas fora do
controle de origem (como UserSecrets), pois eles podem conter credenciais.
Recomendações
Ao decidir qual implementação de IDistributedCache é ideal para seu aplicativo, escolha entre Redis e do SQL
Server com base em sua infraestrutura existente e ambiente, seus requisitos de desempenho e experiência da
sua equipe. Se sua equipe mais confortável com Redis, é uma excelente opção. Se sua equipe prefere SQL
Server, você pode ter certeza de que implementação também. Observe que uma solução tradicional de cache
armazena dados na memória que permite a rápida recuperação de dados. Você deve armazenar dados usados
em um cache e armazenar todos os dados em um armazenamento persistente de back-end, como o SQL
Server ou do armazenamento do Azure. Cache redis é uma solução de cache que oferece alta taxa de
transferência e baixa latência em comparação com o Cache de SQL.

Recursos adicionais
Redis Cache no Azure
Banco de dados do SQL Azure
Cache na memória
Detectar alterações com tokens de alteração
Cache de resposta
Middleware de Cache de Resposta
Auxiliar de marca de cache
Auxiliar de marca de cache distribuído
O cache de resposta no núcleo do ASP.NET
10/04/2018 • 16 min to read • Edit Online

Por John Luo, Rick Anderson, Steve Smith, e Luke Latham

OBSERVAÇÃO
O cache de resposta não tem suporte em páginas Razor com o ASP.NET 2.0 de núcleo. Esse recurso terá suporte no versão
2.1 do ASP.NET Core.

Exibir ou baixar código de exemplo (como baixar)


O cache de resposta reduz o número de solicitações de que um cliente ou um proxy faz a um servidor web. O
cache de resposta também reduz a quantidade de trabalho do servidor web executa para gerar uma resposta. O
cache de resposta é controlado por cabeçalhos que especifique como deseja middleware para respostas de cache
de cliente e proxy.
O servidor web pode armazenar em cache respostas quando você adiciona Middleware de cache de resposta.

O cache de resposta baseado em HTTP


O especificação HTTP 1.1 cache descreve o comportamento do caches de Internet. O cabeçalho HTTP principal
usado para armazenar em cache é Cache-Control, que é usado para especificar o cache diretivas. As diretivas de
controlam o comportamento de cache conforme solicitações passarão de clientes para servidores e respostas
passarão de servidores de volta aos clientes. Solicitações e respostas movem através de servidores proxy e
servidores proxy também devem estar em conformidade com a especificação de cache do HTTP 1.1.
Comuns Cache-Control diretivas são mostradas na tabela a seguir.

DIRETIVA AÇÃO

public Um cache pode armazenar a resposta.

private A resposta não deve ser armazenada por um cache


compartilhado. Um cache privado pode armazenar e reutilizar
a resposta.

max-age O cliente não aceitará uma resposta cuja idade seja maior
que o número especificado de segundos. Exemplos:
max-age=60 (60 segundos), max-age=2592000 (mês)

no-cache Em solicitações: um cache não deve usar uma resposta


armazenada para satisfazer a solicitação. Observação: O
servidor de origem gera novamente a resposta para o cliente
e o middleware atualiza a resposta armazenada em seu
cache.

As respostas: A resposta não deve ser usada para uma


solicitação subsequente sem validação no servidor de origem.
DIRETIVA AÇÃO

no-store Em solicitações: um cache não deve armazenar a solicitação.

As respostas: um cache não deve armazenar qualquer parte


da resposta.

Outros cabeçalhos de cache que desempenham uma função no cache são mostrados na tabela a seguir.

CABEÇALHO FUNÇÃO

Idade Uma estimativa da quantidade de tempo em segundos desde


que a resposta foi gerada ou validada com êxito no servidor
de origem.

Expirar A data/hora após o qual a resposta é considerada obsoleta.

Pragma Existe para versões anteriores a compatibilidade com


HTTP/1.0 armazena em cache para a configuração no-cache
comportamento. Se o Cache-Control cabeçalho estiver
presente, o Pragma cabeçalho será ignorado.

Variar Especifica que uma resposta em cache não deve ser enviada a
menos que todos os do Vary correspondem a campos de
cabeçalho na solicitação original da resposta em cache e a
nova solicitação.

Aspectos de cache com base em HTTP solicitar as diretivas de


controle de Cache
O especificação de cache do HTTP 1.1 para o cabeçalho Cache-Control requer um cache cumprir válido
Cache-Control cabeçalho enviado pelo cliente. Um cliente pode fazer solicitações com uma no-cache valor de
cabeçalho e forçar o servidor para gerar uma nova resposta para cada solicitação.
Sempre para respeitar cliente Cache-Control cabeçalhos de solicitação faz sentido se você considerar o objetivo
do cache de HTTP. Sob a especificação oficial, cache destina-se para reduzir a sobrecarga de rede e latência de
atender solicitações em uma rede de clientes, proxies e servidores. Não necessariamente é uma maneira de
controlar a carga em um servidor de origem.
Não há nenhum atual desenvolvedor controle sobre o comportamento de cache ao usar o Middleware de cache
de resposta porque o middleware cumpre o oficial da especificação de cache. Aperfeiçoamentos futuros para o
middleware permitirá a configurar o middleware para ignorar uma solicitação Cache-Control cabeçalho ao
decidir servir uma resposta em cache. Isso oferecerá uma oportunidade para controlar melhor a carga no
servidor quando você usar o middleware.

Outras tecnologias de cache no núcleo do ASP.NET


O armazenamento em cache na memória
O armazenamento em cache na memória usa a memória do servidor para armazenar dados em cache. Esse tipo
de cache é adequado para um único servidor ou vários servidores usando sessões Autoadesivas. Sessões
Autoadesivas significa que as solicitações de um cliente sempre são roteadas para o mesmo servidor para
processamento.
Para obter mais informações, consulte Cache na memória.
Cache distribuído
Use um cache distribuído para armazenar dados na memória quando o aplicativo é hospedado em um farm de
servidor ou de nuvem. O cache é compartilhado entre os servidores que processam solicitações. Um cliente
pode enviar uma solicitação que é tratada por qualquer servidor no grupo, se os dados armazenados em cache
para o cliente estão disponíveis. ASP.NET Core oferece o SQL Server e os caches Redis distribuído.
Para obter mais informações, consulte trabalhar com um cache distribuído.
Auxiliar de marca de cache
Você pode armazenar em cache o conteúdo de um modo de exibição do MVC ou Razor de página com o auxiliar
de marca de Cache. O auxiliar de marca de Cache usa o cache de memória para armazenar dados.
Para obter mais informações, consulte auxiliar de marca de Cache no ASP.NET MVC de núcleo.
Auxiliar de Marca de Cache Distribuído
Você pode armazenar em cache o conteúdo de um modo de exibição do MVC ou página de Razor em nuvem
distribuído ou cenários de farm da web com o auxiliar de marca de Cache distribuído. O auxiliar de marca de
Cache distribuído usa o SQL Server ou Redis para armazenar dados.
Para obter mais informações, consulte auxiliar de marca de Cache distribuído.

Atributo ResponseCache
O ResponseCacheAttribute Especifica os parâmetros necessários para definir os cabeçalhos apropriados no
cache de resposta.

AVISO
Desabilite o cache de conteúdo que contém informações para clientes autenticados. O cache deve ser habilitado apenas
para o conteúdo que não são alterados com base na identidade do usuário ou se um usuário está conectado.

VaryByQueryKeys a resposta armazenada varia de acordo com os valores de determinada lista de chaves de
consulta. Quando um único valor de * é fornecido, o middleware varia respostas por todos os parâmetros de
cadeia de caracteres de consulta de solicitação. VaryByQueryKeys exige o ASP.NET Core 1.1 ou posterior.
O Middleware de cache de resposta deve ser habilitado para definir o VaryByQueryKeys propriedade; caso
contrário, uma exceção de tempo de execução é gerada. Não existe um cabeçalho HTTP correspondente para o
VaryByQueryKeys propriedade. A propriedade é um recurso HTTP manipulado pelo Middleware de
armazenamento em cache a resposta. Para o middleware servir uma resposta em cache, a cadeia de caracteres
de consulta e o valor de cadeia de caracteres de consulta devem corresponder uma solicitação anterior. Por
exemplo, considere a sequência de solicitações e resultados mostrados na tabela a seguir.

SOLICITAÇÃO RESULTADO

http://example.com?key1=value1 retornado do servidor

http://example.com?key1=value1 retornado de middleware

http://example.com?key1=value2 retornado do servidor

A primeira solicitação é retornada pelo servidor e armazenados em cache no middleware. A segunda solicitação
é retornada pelo middleware porque a cadeia de caracteres de consulta corresponde a solicitação anterior. A
terceira solicitação não está no cache de middleware porque o valor de cadeia de caracteres de consulta não
corresponde a uma solicitação anterior.
O ResponseCacheAttribute é usado para configurar e criar (por meio de IFilterFactory ) um
ResponseCacheFilter. O ResponseCacheFilter realiza o trabalho de atualização de cabeçalhos HTTP apropriados
e os recursos da resposta. O filtro:
Remove qualquer cabeçalho existente para Vary , Cache-Control , e Pragma .
Grava os cabeçalhos apropriados com base nas propriedades definidas no ResponseCacheAttribute .
Atualiza a resposta de armazenamento em cache o recurso HTTP se VaryByQueryKeys está definido.
Variar
Esse cabeçalho é apenas gravado quando o VaryByHeader está definida. Ele é definido como o Vary valor da
propriedade. O exemplo a seguir usa o VaryByHeader propriedade:

[ResponseCache(VaryByHeader = "User-Agent", Duration = 30)]


public IActionResult About2()
{

Você pode exibir os cabeçalhos de resposta com as ferramentas de rede do seu navegador. A imagem a seguir
mostra o F12 borda que saída o rede guia quando o About2 método de ação é atualizado:

NoStore e Location.None
NoStore substitui a maioria das outras propriedades. Quando essa propriedade é definida como true ,o
Cache-Control cabeçalho é definido como no-store . Se Location é definido como None :

Cache-Control é definido como no-store,no-cache .


Pragma é definido como no-cache .

Se NoStore é false e Location é None , Cache-Control e Pragma são definidos como no-cache .
Você normalmente define NoStore para true nas páginas de erro. Por exemplo:
[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View();
}

Isso resulta nos seguintes cabeçalhos:

Cache-Control: no-store,no-cache
Pragma: no-cache

Local e duração
Para habilitar o cache, Duration deve ser definido como um valor positivo e Location devem ser Any (o
padrão) ou Client . Nesse caso, o Cache-Control cabeçalho é definido como o valor do local seguido de
max-age da resposta.

OBSERVAÇÃO
Location do opções de Any e Client se traduz em Cache-Control valores de cabeçalho de public e private ,
respectivamente. Conforme observado anteriormente, definindo Location para None define Cache-Control e
Pragma cabeçalhos para no-cache .

Abaixo está um exemplo que mostra os cabeçalhos produzido definindo Duration e deixar o padrão Location
valor:

[ResponseCache(Duration = 60)]
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";

return View();
}

Isso gera o cabeçalho a seguir:

Cache-Control: public,max-age=60

Perfis de cache
Em vez de duplicar ResponseCache configurações em muitos atributos de ação do controlador, perfis de cache
podem ser configuradas como opções ao configurar MVC no ConfigureServices método Startup . Valores
encontrados em um perfil de cache de referência são usados como padrões pelo ResponseCache de atributos e
são substituídos por qualquer propriedade especificada no atributo.
Configurando um perfil de cache:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.CacheProfiles.Add("Default",
new CacheProfile()
{
Duration = 60
});
options.CacheProfiles.Add("Never",
new CacheProfile()
{
Location = ResponseCacheLocation.None,
NoStore = true
});
});
}

Um perfil de cache de referência:

[ResponseCache(Duration = 30)]
public class HomeController : Controller
{
[ResponseCache(CacheProfileName = "Default")]
public IActionResult Index()
{
return View();
}

O ResponseCache atributo pode ser aplicado a ações (métodos) e controladores (classes). Atributos de nível de
método substituem as configurações especificadas em atributos de nível de classe.
No exemplo acima, um atributo de nível de classe especifica uma duração de 30 segundos, enquanto um atributo
de nível de método faz referência a um perfil de cache com uma duração definida como 60 segundos.
O cabeçalho resultante:

Cache-Control: public,max-age=60

Recursos adicionais
Armazenar respostas em Caches
Cache-Control
Cache na memória
Trabalhar com um cache distribuído
Detectar alterações com tokens de alteração
Middleware de Cache de Resposta
Auxiliar de marca de cache
Auxiliar de marca de cache distribuído
Resposta de cache Middleware no núcleo do
ASP.NET
04/05/2018 • 14 min to read • Edit Online

Por Luke Latham e John Luo


Exibir ou baixar código de exemplo (como baixar)
Este artigo explica como configurar o Middleware de cache de resposta em um aplicativo do ASP.NET Core. O
middleware determina quando as respostas são armazenável em cache, repositórios de respostas e respostas
de serve de cache. Para obter uma introdução ao cache de HTTP e o ResponseCache de atributo, consulte cache
de resposta.

Pacote
Para incluir o middleware em um projeto, adicione uma referência para o
Microsoft.AspNetCore.ResponseCaching pacote ou use o Microsoft.AspNetCore.All pacote ( ASP.NET Core 2.0 ou
posterior durante o direcionamento do .NET Core).

Configuração
Em ConfigureServices , adicione o middleware para a coleção de serviço.

public void ConfigureServices(IServiceCollection services)


{
services.AddResponseCaching();
services.AddMvc();
}

Configurar o aplicativo para usar o middleware com o UseResponseCaching método de extensão, o que adiciona
o middleware para o pipeline de processamento de solicitação. O aplicativo de exemplo adiciona um
Cache-Control cabeçalho para a resposta que armazena em cache respostas armazenável em cache por até 10
segundos. O exemplo envia um Vary cabeçalho para configurar o middleware para servir uma resposta em
cache somente se o Accept-Encoding cabeçalho de solicitações subsequentes corresponde a solicitação
original. No exemplo de código que segue, CacheControlHeaderValue e HeaderNames exigem um using
instrução para o Microsoft.Net.Http.Headers namespace.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseResponseCaching();

app.Use(async (context, next) =>


{
context.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
context.Response.Headers[HeaderNames.Vary] = new string[] { "Accept-Encoding" };

await next();
});

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

app.UseStaticFiles();
app.UseMvc();
}

Middleware de cache de resposta só armazena em cache as respostas do servidor que resultam em um código
de status 200 (Okey). Outras respostas, incluindo páginas de erro, são ignorados pelo middleware.

AVISO
Respostas que contêm o conteúdo para clientes autenticados devem ser marcadas como não armazenável em cache para
evitar que o middleware de armazenamento e que atende a essas respostas. Consulte condições para armazenar em
cache para obter detalhes sobre como o middleware determina se uma resposta pode ser armazenada em cache.

Opções
O middleware oferece três opções para controlar o cache de resposta.

OPÇÃO VALOR PADRÃO

UseCaseSensitivePaths Determina se as respostas são armazenados em cache nos


caminhos diferencia maiusculas de minúsculas.
O valor padrão é false .

MaximumBodySize O maior tamanho armazenável em cache para o corpo da


resposta em bytes.
O valor padrão é 64 * 1024 * 1024 (64 MB).

SizeLimit O limite de tamanho para o middleware de cache de


resposta em bytes. O valor padrão é 100 * 1024 * 1024
(100 MB).

O exemplo a seguir configura o middleware para:


Respostas de cache menores ou iguais a 1.024 bytes.
Armazenar as respostas de caminhos diferencia maiusculas de minúsculas (por exemplo, /page1 e /Page1
são armazenadas separadamente).

services.AddResponseCaching(options =>
{
options.UseCaseSensitivePaths = true;
options.MaximumBodySize = 1024;
});

VaryByQueryKeys
Ao usar controladores de API MVC/da Web ou modelos de página de páginas Razor, o ResponseCache atributo
especifica os parâmetros necessários para definir os cabeçalhos apropriados para o cache de resposta. O único
parâmetro do ResponseCache atributo estritamente requer o middleware é VaryByQueryKeys , que não
corresponde a um cabeçalho HTTP real. Para obter mais informações, consulte ResponseCache atributo.
Quando não estiver usando o ResponseCache atributo, o cache de resposta pode variar com o VaryByQueryKeys
recurso. Use o ResponseCachingFeature diretamente a partir de IFeatureCollection do HttpContext :

var responseCachingFeature = context.HttpContext.Features.Get<IResponseCachingFeature>();


if (responseCachingFeature != null)
{
responseCachingFeature.VaryByQueryKeys = new[] { "MyKey" };
}

Usando um único valor igual a * na VaryByQueryKeys varia o cache por todos os parâmetros de consulta de
solicitação.

Cabeçalhos HTTP usados pelo Middleware de cache de resposta


O cache de resposta pelo middleware é configurado usando cabeçalhos HTTP.

CABEÇALHO DETALHES

Autorização A resposta não está armazenada em cache se o cabeçalho


existe.
CABEÇALHO DETALHES

Cache-Control O middleware só considera o armazenamento em cache


respostas marcadas com o public diretiva de cache.
Controlam o cache com os seguintes parâmetros:
idade máxima
max-stale†
nova min
deve-revalidate
sem cache
Nenhum repositório
somente se-armazenado em cache
particulares
públicos
s-maxage
proxy-revalidate‡
†Se nenhum limite é especificado para max-stale , o
middleware não executa nenhuma ação.
‡ proxy-revalidate tem o mesmo efeito que
must-revalidate .

Para obter mais informações, consulte RFC 7231: diretivas


de controle de Cache de solicitação.

Pragma Um Pragma: no-cache cabeçalho na solicitação produz o


mesmo efeito que Cache-Control: no-cache . Esse
cabeçalho é substituído pelas diretivas desse relevantes a
Cache-Control cabeçalho, se presente. Considerado para
compatibilidade com versões anteriores com HTTP/1.0.

Set-Cookie A resposta não está armazenada em cache se o cabeçalho


existe. Qualquer middleware no pipeline de processamento
de solicitação que define um ou mais cookies impede que o
Middleware de cache de resposta do cache a resposta (por
exemplo, o baseada em cookie TempData provedor).

Variar O Vary cabeçalho é usado para variar a resposta


armazenada em cache por outro cabeçalho. Por exemplo,
armazenar em cache respostas de codificação, incluindo o
Vary: Accept-Encoding cabeçalho, que armazena em
cache as respostas para solicitações com cabeçalhos
Accept-Encoding: gzip e
Accept-Encoding: text/plain separadamente. Uma
resposta com um valor de cabeçalho de * nunca é
armazenada.

Expirar Uma resposta considerada obsoleta por esse cabeçalho não


está armazenada ou recuperada a menos que substituído
por outros Cache-Control cabeçalhos.

If-None-Match A resposta completa é servida do cache se o valor não é *


e ETag da resposta não corresponde a nenhum dos
valores fornecidos. Caso contrário, será fornecida uma
resposta 304 (não modificados).
CABEÇALHO DETALHES

If-Modified-Since Se o If-None-Match cabeçalho não estiver presente, uma


resposta completa é servida do cache se a data de resposta
em cache é mais recente do que o valor fornecido. Caso
contrário, será fornecida uma resposta 304 (não
modificados).

Date Quando atendendo do cache, o Date cabeçalho é definido


pelo middleware se ele não foi fornecido na resposta
original.

Tamanho do conteúdo Quando atendendo do cache, o Content-Length


cabeçalho é definido pelo middleware se ele não foi
fornecido na resposta original.

Idade O Age cabeçalho enviado na resposta original será


ignorado. O middleware calcula um novo valor ao oferecer
uma resposta em cache.

Cache respeita as diretivas de solicitação de controle de Cache


O middleware respeita as regras de especificação HTTP 1.1 cache. As regras exigem um cache cumprir válido
Cache-Control cabeçalho enviado pelo cliente. Sob a especificação de um cliente pode fazer solicitações com
uma no-cache valor de cabeçalho e forçar o servidor para gerar uma nova resposta para cada solicitação.
Atualmente, não há nenhum controle de desenvolvedor sobre o comportamento de cache ao usar o
middleware porque o middleware segue a especificação oficial do cache.
Para obter mais controle sobre o comportamento do cache, explore outros recursos de cache do ASP.NET
Core. Consulte os tópicos a seguir:
Cache na memória
Trabalhar com um cache distribuído
Cache auxiliar de marca no núcleo do ASP.NET MVC
Auxiliar de marca de cache distribuído

Solução de problemas
Se o comportamento do cache não está conforme o esperado, confirme se as respostas são armazenável em
cache e é capaz de servido do cache. Examine os cabeçalhos de entrada da solicitação e cabeçalhos de saída da
resposta. Habilitar log para ajudar na depuração.
Quando testar e solucionar problemas de comportamento de cache, um navegador pode definir cabeçalhos de
solicitação que afetam o cache de maneira indesejada. Por exemplo, um navegador pode definir o
Cache-Control cabeçalho para no-cache ou max-age=0 ao atualizar uma página. As ferramentas a seguir
podem definir explicitamente os cabeçalhos de solicitação e são preferenciais para testes de armazenamento
em cache:
Fiddler
Postman
Condições para armazenar em cache
A solicitação deve resultar em uma resposta do servidor com um código de status 200 (Okey).
O método de solicitação deve ser GET ou HEAD.
Middleware de terminal, como Middleware de arquivo estático, não deve processar a resposta antes do
Middleware de cache de resposta.
O Authorization cabeçalho não deve estar presente.
Cache-Control parâmetros de cabeçalho devem ser válidos, e a resposta deve ser marcada public e não
marcado private .
O Pragma: no-cache cabeçalho não deve estar presente se a Cache-Control cabeçalho não estiver presente,
como o Cache-Control cabeçalho substitui o Pragma cabeçalho quando presentes.
O Set-Cookie cabeçalho não deve estar presente.
Vary parâmetros de cabeçalho devem ser válido e não é igual a * .
O Content-Length valor de cabeçalho (se definido) deve corresponder ao tamanho do corpo da resposta.
O IHttpSendFileFeature não é usado.
A resposta não deve ser atualizada conforme especificado pelo Expires cabeçalho e o max-age e s-maxage
diretivas de cache.
Buffer de resposta deve ser bem-sucedida e o tamanho da resposta deve ser menor do que o configurado
ou padrão SizeLimit .
A resposta deve ser armazenado em cache de acordo com o 7234 RFC especificações. Por exemplo, o
no-store diretiva não deve existir nos campos de cabeçalho de solicitação ou resposta. Consulte seção 3:
armazenar respostas em Caches de 7234 RFC para obter detalhes.

OBSERVAÇÃO
O sistema Antiforgery para gerar tokens de seguras para evitar a falsificação de solicitação entre sites (CSRF) ataques de
conjuntos de Cache-Control e Pragma cabeçalhos para no-cache para que as respostas não são armazenadas em
cache. Para obter informações sobre como desabilitar antiforgery tokens para elementos de formulário HTML, consulte
configuração antiforgery do ASP.NET Core.

Recursos adicionais
Inicialização de aplicativos
Middleware
Cache na memória
Trabalhar com um cache distribuído
Detectar alterações com tokens de alteração
Cache de resposta
Auxiliar de marca de cache
Auxiliar de marca de cache distribuído
Middleware de compactação de resposta para o
ASP.NET Core
10/04/2018 • 17 min to read • Edit Online

Por Luke Latham


Exibir ou baixar código de exemplo (como baixar)
Largura de banda de rede é um recurso limitado. Reduzir o tamanho da resposta geralmente aumenta a
capacidade de resposta de um aplicativo com frequência. É uma maneira de reduzir os tamanhos de carga
compactar respostas do aplicativo.

Quando usar o Middleware de compactação de resposta


Use tecnologias de compactação de resposta com base em servidor no IIS, o Apache ou Nginx. O desempenho
do middleware provavelmente não corresponde dos módulos de servidor. Servidor de HTTP. sys e Kestrel
atualmente não oferecem suporte à compactação interna.
Use o Middleware de compactação de resposta quando estiver:
Não é possível usar as seguintes tecnologias de compactação baseada em servidor:
Módulo de compactação dinâmica do IIS
Módulo do Apache mod_deflate
Nginx compactação e descompactação
Hospedagem diretamente em:
Servidor de HTTP. sys (anteriormente chamado WebListener)
Kestrel

Compactação de resposta
Em geral, qualquer resposta não nativamente compactada pode se beneficiar da compactação de resposta. As
respostas que não foi compactadas normalmente incluem: CSS, JavaScript, HTML, XML e JSON. Você não deve
compactar ativos nativamente compactados, como arquivos PNG. Se você tentar compactar ainda mais uma
resposta compactada nativamente, qualquer redução pequena adicional em tempo de tamanho e a transmissão
será provavelmente ser ofuscada pelo tempo necessário para processar a compactação. Não compacte arquivos
menores que cerca de 150 e 1000 bytes (dependendo do conteúdo do arquivo e a eficiência da compactação). A
sobrecarga de compactação de arquivos pequenos pode produzir um arquivo compactado maior do que o
arquivo não compactado.
Quando um cliente pode processar conteúdo compactado, o cliente deve informar o servidor de seus recursos,
enviando o Accept-Encoding cabeçalho com a solicitação. Quando um servidor envia conteúdo compactado, ele
deve incluir informações de Content-Encoding cabeçalho em como a resposta compactada é codificada. Conteúdo
designações de codificação com suporte pelo middleware são mostradas na tabela a seguir.

ACCEPT-ENCODING VALORES DE CABEÇALHO MIDDLEWARE COM SUPORTE DESCRIÇÃO

br Não Formato de dados compactados Brotli


ACCEPT-ENCODING VALORES DE CABEÇALHO MIDDLEWARE COM SUPORTE DESCRIÇÃO

compress Não Formato de dados de "compactar"


UNIX

deflate Não "deflate" dados compactados no


formato de dados "zlib"

exi Não W3C XML eficiente intercâmbio

gzip Sim (padrão) formato de arquivo gzip

identity Sim "Nenhuma codificação" identificador: A


resposta não deve ser codificada.

pack200-gzip Não Formato de transferência de rede para


arquivos mortos de Java

* Sim Nenhum conteúdo disponível não


codificação explicitamente solicitada

Para obter mais informações, consulte o IANA conteúdo codificação lista oficial.
O middleware permite adicionar provedores de compactação adicional para personalizado Accept-Encoding
valores de cabeçalho. Para obter mais informações, consulte provedores personalizados abaixo.
O middleware é capaz de reagir a valor de qualidade (qvalue, q ) quando enviado pelo cliente para priorizar os
esquemas de compactação de relevância. Para obter mais informações, consulte RFC 7231: codificação aceita.
Algoritmos de compactação estão sujeitos a um equilíbrio entre a velocidade de compactação e a eficiência da
compactação de. Eficácia neste contexto refere-se ao tamanho da saída após a compactação. O menor tamanho é
alcançado por mais ideal compactação.
Os cabeçalhos envolvidos na solicitação, enviar, cache e receber conteúdo compactado são descritos na tabela a
seguir.

CABEÇALHO FUNÇÃO

Accept-Encoding Enviada do cliente para o servidor para indicar o conteúdo


aceitável para o cliente de esquemas de codificação.

Content-Encoding Enviados do servidor para o cliente para indicar a codificação


do conteúdo na carga.

Content-Length Quando ocorre a compactação, o Content-Length


cabeçalho for removido, pois as alterações de conteúdo do
corpo quando a resposta é compactada.

Content-MD5 Quando ocorre a compactação, o Content-MD5 cabeçalho


for removido, pois o conteúdo do corpo foi alterado e o hash
não é mais válido.
CABEÇALHO FUNÇÃO

Content-Type Especifica o tipo MIME do conteúdo. Cada resposta deve


especificar seu Content-Type . O middleware verifica esse
valor para determinar se a resposta deve ser compactada. O
middleware Especifica um conjunto de padrão tipos MIME
que ele pode codificar, mas você pode substituir ou adicionar
tipos MIME.

Vary Quando enviadas pelo servidor com um valor de


Accept-Encoding para clientes e proxies, o Vary cabeçalho
indica ao cliente ou proxy que ele deve armazenar em cache
(variar) respostas com base no valor da Accept-Encoding
cabeçalho da solicitação. O resultado de retorno de conteúdo
com o Vary: Accept-Encoding cabeçalho é que ambos
compactado e descompactadas respostas são armazenadas
em cache separadamente.

Você pode explorar os recursos do Middleware de compactação de resposta com o aplicativo de exemplo. O
exemplo ilustra:
A compactação de respostas de aplicativo usando o gzip e provedores personalizados de compactação.
Como adicionar um tipo de MIME para a lista padrão de tipos de MIME para compactação.

Pacote
Para incluir o middleware em seu projeto, adicione uma referência para o
Microsoft.AspNetCore.ResponseCompression pacote ou use o Microsoft.AspNetCore.All pacote. Esse recurso está
disponível para aplicativos direcionados ao ASP.NET Core 1.1 ou posterior.

Configuração
O código a seguir mostra como habilitar o Middleware de compactação de resposta com a compactação gzip
padrão e para tipos MIME padrão.
ASP.NET Core 2.x
ASP.NET Core 1.x

WebHost.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddResponseCompression();
})
.Configure(app =>
{
app.UseResponseCompression();

app.Run(async context =>


{
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync(LoremIpsum.Text);
});
})
.Build();
OBSERVAÇÃO
Usar uma ferramenta como Fiddler, Firebug, ou carteiro para definir o Accept-Encoding cabeçalho de solicitação e analise
os cabeçalhos de resposta, o tamanho e o corpo.

Enviar uma solicitação para o aplicativo de exemplo sem o Accept-Encoding cabeçalho e observe que a resposta é
descompactada. O Content-Encoding e Vary cabeçalhos não estão presentes na resposta.

Enviar uma solicitação para o aplicativo de exemplo com o Accept-Encoding: gzip cabeçalho e observe que a
resposta é compactada. O Content-Encoding e Vary cabeçalhos estão presentes na resposta.

provedores
GzipCompressionProvider
Use o GzipCompressionProvider para compactar respostas com gzip. Este é o provedor de compactação padrão se
nenhum for especificado. Você pode definir a compactação de nível com o GzipCompressionProviderOptions .
O provedor de compactação gzip como padrão o nível de compactação mais rápido ( CompressionLevel.Fastest ),
que não pode produzir a compactação mais eficiente. Se a compactação mais eficiente é desejada, você pode
configurar o middleware para compactação ideal.

NÍVEL DE COMPACTAÇÃO DESCRIÇÃO

CompressionLevel.Fastest A compactação deve ser concluída assim que possível, mesmo


se a saída resultante ideal não é compactada.

CompressionLevel.NoCompression Não há nenhuma compactação deve ser executada.

CompressionLevel.Optimal As respostas devem ser compactadas ideal, mesmo se a


compactação leva mais tempo para concluir.

ASP.NET Core 2.x


ASP.NET Core 1.x

services.AddResponseCompression(options =>
{
options.Providers.Add<GzipCompressionProvider>();
options.Providers.Add<CustomCompressionProvider>();
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "image/svg+xml" });
});

services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});

tipos MIME
O middleware Especifica um conjunto padrão de tipos de MIME para compactação:
text/plain
text/css
application/javascript
text/html
application/xml
text/xml
application/json
text/json

Você pode substituir ou acrescentar tipos MIME com as opções de Middleware de compactação de resposta.
Observe que MIME curinga tipos, como text/* não são suportados. O aplicativo de exemplo adiciona um tipo
MIME para image/svg+xml e compacta e serve o ASP.NET Core imagem da faixa (banner.svg).
ASP.NET Core 2.x
ASP.NET Core 1.x
services.AddResponseCompression(options =>
{
options.Providers.Add<GzipCompressionProvider>();
options.Providers.Add<CustomCompressionProvider>();
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "image/svg+xml" });
});

services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});

Provedores personalizados
Você pode criar implementações personalizadas de compactação com ICompressionProvider . O EncodingName
representa o conteúdo de codificação que este ICompressionProvider produz. O middleware usa essas
informações para escolher o fornecedor de acordo com a lista especificada no Accept-Encoding cabeçalho da
solicitação.
Usando o aplicativo de exemplo, o cliente envia uma solicitação com o Accept-Encoding: mycustomcompression
cabeçalho. O middleware usa a implementação da compactação personalizada e retorna a resposta com uma
Content-Encoding: mycustomcompression cabeçalho. O cliente deve ser capaz de descompactar a codificação
personalizada para que uma implementação personalizada de compactação trabalhar.
ASP.NET Core 2.x
ASP.NET Core 1.x

services.AddResponseCompression(options =>
{
options.Providers.Add<GzipCompressionProvider>();
options.Providers.Add<CustomCompressionProvider>();
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "image/svg+xml" });
});

services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});

public class CustomCompressionProvider : ICompressionProvider


{
public string EncodingName => "mycustomcompression";
public bool SupportsFlush => true;

public Stream CreateStream(Stream outputStream)


{
// Create a custom compression stream wrapper here
return outputStream;
}
}

Enviar uma solicitação para o aplicativo de exemplo com o Accept-Encoding: mycustomcompression cabeçalho e
observar os cabeçalhos de resposta. O Vary e Content-Encoding cabeçalhos estão presentes na resposta. O
corpo da resposta (não mostrado) não é compactado pelo exemplo. Não há uma implementação da compactação
no CustomCompressionProvider classe do exemplo. No entanto, o exemplo mostra onde você implementaria tal um
algoritmo de compactação.
Compactação com protocolo seguro
As respostas compactadas através de conexões seguras podem ser controladas com o EnableForHttps opção,
que é desabilitada por padrão. Usar a compactação com páginas geradas dinamicamente pode levar a problemas
de segurança, como o CRIME e violação ataques.

Adicionar o cabeçalho Vary


Ao compactar respostas com base no Accept-Encoding cabeçalho, há potencialmente várias versões compactadas
da resposta e uma versão descompactada. Para instruir os caches de cliente e proxy que várias versões existem e
devem ser armazenadas, o Vary cabeçalho é adicionado com uma Accept-Encoding valor. No núcleo do
ASP.NET 1. x, adicionando o Vary cabeçalho para a resposta é realizado manualmente. No núcleo do ASP.NET 2.
x, o middleware adiciona o Vary cabeçalho automaticamente quando a resposta é compactada.
ASP.NET Core apenas 1. x

// ONLY REQUIRED FOR ASP.NET CORE 1.x APPS


private void ManageVaryHeader(HttpContext context)
{
// If the Accept-Encoding header is present, add the Vary header
var accept = context.Request.Headers[HeaderNames.AcceptEncoding];
if (!StringValues.IsNullOrEmpty(accept))
{
context.Response.Headers.Append(HeaderNames.Vary, HeaderNames.AcceptEncoding);
}
}

Problema de middleware quando atrás de um proxy reverso Nginx


Quando uma solicitação é delegada por Nginx, o Accept-Encoding cabeçalho é removido. Isso impede que o
middleware da compactação de resposta. Para obter mais informações, consulte NGINX: compactação e
descompactação de. Esse problema é acompanhado por descobrir a compactação de passagem para Nginx
(BasicMiddleware 123).

Trabalhando com compactação dinâmica do IIS


Se você tiver um active IIS compactação módulo dinâmico configurado no nível do servidor que você deseja
desabilitar para um aplicativo, você pode fazer isso com uma adição à sua Web. config arquivo. Para obter mais
informações, consulte Desabilitando módulos do IIS.

Solução de problemas
Usar uma ferramenta como Fiddler, Firebug, ou carteiro, que permitem que você defina o Accept-Encoding
cabeçalho de solicitação e analise os cabeçalhos de resposta, o tamanho e o corpo. O Middleware de
compactação de resposta compacta respostas que atendem às seguintes condições:
O Accept-Encoding cabeçalho estiver presente com um valor de gzip , * , ou codificação personalizada que
corresponda a um provedor personalizado de compactação estabelecida por você. O valor não deve ser
identity ou tem um valor de qualidade (qvalue, q ) configuração de 0 (zero).
O tipo MIME ( Content-Type ) deve ser definido e deve corresponder a um tipo MIME configurado no
ResponseCompressionOptions .
A solicitação não deve incluir o Content-Range cabeçalho.
A solicitação deve usar o protocolo seguro (http), a menos que o protocolo seguro (https) é configurado nas
opções de Middleware de compactação de resposta. Observe o perigo descrito acima ao habilitar a
compactação de conteúdo segura.

Recursos adicionais
Inicialização de aplicativos
Middleware
Rede Mozilla Developer: Codificação aceita
RFC 7231 seção 3.1.2.1: Codings conteúdos
RFC 7230 seção 4.2.3: Codificação de Gzip
Versão de especificação de formato de arquivo GZIP 4.3
Migração para o ASP.NET Core
10/04/2018 • 1 min to read • Edit Online

ASP.NET para ASP.NET Core 1.x


Migrar do ASP.NET MVC para o ASP.NET Core MVC
Migrar configuração
Migrar autenticação e Identity
Migrar da API Web ASP.NET
Migrar módulos HTTP para Middleware
ASP.NET para ASP.NET Core 2.0
ASP.NET para ASP.NET Core 2.0
ASP.NET Core 1.x para 2.0
Migrar do ASP.NET Core 1.x para 2.0
Migrar autenticação e Identity
Migrar do ASP.NET MVC para o núcleo do ASP.NET
MVC
10/04/2018 • 14 min to read • Edit Online

Por Rick Anderson, Daniel Roth, Steve Smith, e Scott Addie


Este artigo mostra como começar a migração de um projeto ASP.NET MVC para MVC do ASP.NET Core. O
processo, ele destaca muitas coisas que foram alterados desde o ASP.NET MVC. Migrando do ASP.NET MVC é
um processo de várias etapas e este artigo aborda a configuração inicial, controladores básico e modos de
exibição, conteúdo estático e dependências do lado do cliente. Artigos adicionais abrangem migrando
configuração e código de identidade encontrada em muitos projetos do ASP.NET MVC.

OBSERVAÇÃO
Os números de versão nos exemplos podem não ser atuais. Talvez seja necessário atualizar seus projetos adequadamente.

Criar o projeto ASP.NET MVC starter


Para demonstrar a atualização, vamos começar criando um aplicativo ASP.NET MVC. Criá-lo com o nome
WebApp1 para o namespace corresponderá o projeto do ASP.NET Core que criamos na próxima etapa.
Opcional: alterar o nome da solução de WebApp1 para Mvc5. O Visual Studio exibirá o nome da nova solução
(Mvc5), tornando mais fácil informar esse projeto da próximo projeto.

Criar o projeto do ASP.NET Core


Criar um novo vazio aplicativo web do ASP.NET Core com o mesmo nome que o projeto anterior (WebApp1)
para corresponder os namespaces em dois projetos. Ter o mesmo namespace torna mais fácil copiar código entre
os dois projetos. Você precisará criar este projeto em um diretório diferente do projeto anterior para usar o
mesmo nome.
Opcional: criar um aplicativo ASP.NET Core novo usando o aplicativo Web modelo de projeto. Nomeie o
projeto WebApp1e selecione uma opção de autenticação de contas de usuário individuais. Renomear este
aplicativo para FullAspNetCore. Criar este projeto economizará tempo na conversão. Você pode examinar o
código gerado pelo modelo para ver o resultado final ou copiar o código para o projeto de conversão. Também
é útil quando você tiver problemas em uma etapa de conversão para comparar com o projeto de modelo
gerado.

Configurar o site para usar o MVC


Instalar o Microsoft.AspNetCore.Mvc e Microsoft.AspNetCore.StaticFiles pacotes do NuGet.
Microsoft.AspNetCore.Mvc é a estrutura MVC do ASP.NET Core. Microsoft.AspNetCore.StaticFiles é o
manipulador de arquivo estático. O tempo de execução do ASP.NET é modular, e você deve optar
explicitamente para servir arquivos estáticos (consulte trabalhar com arquivos estáticos).
Abra o . csproj arquivo (com o botão direito no projeto no Solution Explorer e selecione Editar
WebApp1.csproj) e adicione um PrepareForPublish destino:

O PrepareForPublish destino é necessária para adquirir as bibliotecas de cliente via Bower. Falaremos
sobre isso mais tarde.
Abra o Startup.cs de arquivo e altere o código para coincidir com o seguinte:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace WebApp1
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit
https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}

// This method gets called by the runtime. Use this method to configure the HTTP request
pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseStaticFiles();

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

O UseStaticFiles método de extensão adiciona o manipulador de arquivo estático. Conforme mencionado


anteriormente, o tempo de execução do ASP.NET é modular, e você deve optar explicitamente para servir
arquivos estáticos. O UseMvc método de extensão adiciona o roteamento. Para obter mais informações,
consulte inicialização do aplicativo e roteamento.

Adicionar um controlador e o modo de exibição


Nesta seção, você adicionará um controlador mínimo e o modo de exibição para servir como espaços reservados
para o controlador do ASP.NET MVC e modos de exibição que você vai migrar na próxima seção.
Adicionar um controladores pasta.
Adicionar uma classe do controlador MVC com o nome HomeController para o controladores pasta.
Adicionar um exibições pasta.
Adicionar um exibições/inicial pasta.
Adicionar uma cshtml página de exibição do MVC para o exibições/inicial pasta.

A estrutura do projeto é mostrada abaixo:


Substitua o conteúdo do Views/Home/Index.cshtml arquivo com o seguinte:

<h1>Hello world!</h1>

Execute o aplicativo.

Consulte controladores e exibições para obter mais informações.


Agora que temos um projeto do ASP.NET Core trabalho mínimo, podemos começar a migrar a funcionalidade do
projeto ASP.NET MVC. Será necessário mover o seguinte:
conteúdo do lado do cliente (CSS, fontes e scripts)
controladores
modos de exibição
modelos
Agrupamento
filtros
Log de entrada/saída, de identidade (Isso será feito o seguinte tutorial.)

Controladores e exibições
Copiar cada um dos métodos do ASP.NET MVC HomeController para o novo HomeController . Observe
que, no ASP.NET MVC, tipo de método de ação retorno controlador do modelo interno é ActionResult; no
ASP.NET MVC de núcleo, os métodos de ação retorno IActionResult em vez disso. ActionResult
implementa IActionResult , portanto, não é necessário alterar o tipo de retorno de métodos de ação.
Copie o About.cshtml, Contact.cshtml, e cshtml arquivos de exibição Razor do projeto ASP.NET MVC para o
projeto do ASP.NET Core.
Executar o aplicativo do ASP.NET Core e cada método de teste. Ainda não migramos o arquivo de layout
ou estilos ainda, para que os modos de exibição renderizados conterá apenas o conteúdo nos arquivos de
exibição. Você não terá os links dos arquivos gerados layout para o About e Contact exibe, para que você
precisará invocá-los a partir do navegador (substitua 4492 com o número da porta usado no projeto).
http://localhost:4492/home/about

http://localhost:4492/home/contact

Observe que a falta de estilo e itens de menu. Corrigiremos isso na próxima seção.

Conteúdo estático
Em versões anteriores do ASP.NET MVC, conteúdo estático hospedado da raiz do projeto da web e foi misturado
com os arquivos do servidor. No núcleo do ASP.NET, conteúdo estático é hospedado no wwwroot pasta. Você
desejará copiar o conteúdo estático de seu aplicativo ASP.NET MVC antigo para o wwwroot pasta em seu projeto
do ASP.NET Core. Nessa conversão de exemplo:
Copiar o favicon.ico arquivo de projeto MVC antigo para o wwwroot pasta do projeto do ASP.NET Core.
O ASP.NET MVC antigo projeto usa Bootstrap para seu estilo e repositórios de arquivos a inicialização a conteúdo
e Scripts pastas. O modelo, que gerou o antigo projeto ASP.NET MVC, referencia o Bootstrap no arquivo de layout
(Views/Shared/_Layout.cshtml). Você poderá copiar o bootstrap.js e bootstrap.css projeto de arquivos do ASP.NET
MVC para o wwwroot pasta no novo projeto, mas essa abordagem não usa o melhor mecanismo para gerenciar o
cliente dependências no núcleo do ASP.NET.
No novo projeto, vamos adicionar suporte para inicialização (e outras bibliotecas de cliente) usando Bower:
Adicionar um Bower arquivo de configuração chamado bower. JSON para a raiz do projeto (com o botão
direito no projeto e, em seguida, Adicionar > Novo Item > arquivo de configuração Bower). Adicionar
Bootstrap e jQuery para o arquivo (consulte as linhas destacadas abaixo).
{
"name": "asp.net",
"private": true,
"dependencies": {
"bootstrap": "3.3.6",
"jquery": "2.2.0"
}
}

Após salvar o arquivo, o Bower baixará automaticamente as dependências para o wwwroot/lib pasta. Você pode
usar o pesquisar Gerenciador de soluções para localizar o caminho dos ativos:

Consulte gerenciar pacotes do lado do cliente com Bower para obter mais informações.

Migrar o arquivo de layout


Copiar o viewstart arquivo a partir do projeto ASP.NET MVC antigo exibições pasta para do projeto
ASP.NET Core exibições pasta. O viewstart arquivo não foi alterado no MVC do ASP.NET Core.
Criar um exibições/compartilhadas pasta.
Opcional: cópia viewimports. cshtml do FullAspNetCore do projeto MVC exibições pasta para do projeto
ASP.NET Core Modos de exibição pasta. Remover qualquer declaração de namespace no viewimports.
cshtml arquivo. O viewimports. cshtml arquivo fornece namespaces para todos os arquivos de exibição e
coloca auxiliares de marcação. Os auxiliares de marca são usados no novo arquivo de layout. O
viewimports. cshtml arquivo é novo para o ASP.NET Core.
Copie o cshtml arquivo a partir do projeto ASP.NET MVC antigo exibições/compartilhadas pasta para do
projeto ASP.NET Core exibições/compartilhadas pasta.
Abra cshtml de arquivo e faça as alterações a seguir (o código completo é mostrado abaixo):
Substituir @Styles.Render("~/Content/css") com um <link> elemento carregar bootstrap.css (veja abaixo).
Remova @Scripts.Render("~/bundles/modernizr") .
Comente o @Html.Partial("_LoginPartial") linha (envolvem a linha com @*...*@ ). Retornaremos a ele um
tutorial futuras.
Substituir @Scripts.Render("~/bundles/jquery") com um <script> elemento (veja abaixo).
Substituir @Scripts.Render("~/bundles/bootstrap") com um <script> elemento (veja abaixo).

O link CSS de substituição:

<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />

As marcas de script de substituição:

<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>

A atualização cshtml arquivo é mostrado abaixo:


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title - My ASP.NET Application</title>
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous">
</head>
<body>
<div 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="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
@Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class =
"navbar-brand" })
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
@*@Html.Partial("_LoginPartial")*@
</div>
</div>
</div>
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
</footer>
</div>

<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous"></script>
@RenderSection("scripts", required: false)
</body>
</html>

Exiba o site no navegador. Ele agora deve carregar corretamente, com os estilos esperados em vigor.
Opcional: você talvez queira usar o novo arquivo de layout. Para este projeto, você pode copiar o arquivo de
layout do FullAspNetCore projeto. O novo arquivo de layout usa auxiliares de marcação e tiver outros
aprimoramentos.

Configurar o empacotamento e minimização


Para obter informações sobre como configurar o empacotamento e minimização, consulte empacotamento e
minimização.

Resolver erros HTTP 500


Há muitos problemas que podem causar uma mensagem de erro HTTP 500 que não contêm informações sobre a
origem do problema. Por exemplo, se o Views/_ViewImports.cshtml arquivo contém um namespace que não
existe em seu projeto, você obterá um erro HTTP 500. Para obter uma mensagem de erro detalhada, adicione o
seguinte código:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)


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

app.UseStaticFiles();

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

Consulte usando a página de exceção de desenvolvedor na tratar erros para obter mais informações.

Recursos adicionais
Desenvolvimento no Lado do Cliente
Auxiliares de marcação
Migrar a configuração para o ASP.NET Core
10/04/2018 • 4 min to read • Edit Online

Por Steve Smith e Scott Addie


O artigo anterior, começamos migrar um projeto ASP.NET MVC para MVC do ASP.NET Core. Neste artigo,
vamos migrar a configuração.
Exibir ou baixar código de exemplo (como baixar)

Configuração da instalação
ASP.NET Core não usa mais o global. asax e Web. config arquivos utilizadas de versões anteriores do ASP.NET.
Em versões anteriores do ASP.NET, a lógica de inicialização do aplicativo foi colocada em uma
Application_StartUp método global. asax. Posteriormente, no ASP.NET MVC, um Startup.cs arquivo foi incluído
na raiz do projeto; e ele foi chamado quando o aplicativo foi iniciado. ASP.NET Core adotou essa abordagem
completamente colocando toda lógica de inicialização no Startup.cs arquivo.
O Web. config arquivo também foi substituído no núcleo do ASP.NET. Configuração propriamente dita agora pode
ser configurada como parte do procedimento de inicialização de aplicativo descrito em Startup.cs. Configuração
ainda pode utilizar para arquivos XML, mas normalmente projetos do ASP.NET Core colocará os valores de
configuração em um arquivo no formato JSON, como appSettings. JSON. Sistema de configuração do ASP.NET
Core facilmente pode acessar variáveis de ambiente, que podem fornecer um local mais seguro e robusto para
valores específicos do ambiente. Isso é especialmente verdadeiro para segredos, como cadeias de caracteres de
conexão e chaves de API que não devem ser verificadas no controle de origem. Consulte configuração para saber
mais sobre a configuração no núcleo do ASP.NET.
Neste artigo, estamos começando com o projeto do ASP.NET Core parcialmente migrados de artigo anterior. Para
configurar a configuração, adicione o seguinte construtor e propriedade para o Startup.cs arquivo localizado na
raiz do projeto:

public Startup(IConfiguration configuration)


{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.

Observe que neste ponto, o Startup.cs arquivo não será compilado, pois precisamos adicionar o seguinte using
instrução:

using Microsoft.Extensions.Configuration;

Adicionar uma appSettings. JSON arquivo para a raiz do projeto usando o modelo de item apropriado:
Migrar configurações de Web. config
Nosso projeto ASP.NET MVC incluído a cadeia de caracteres de conexão de banco de dados necessários no Web.
config, além de <connectionStrings> elemento. Em nosso projeto do ASP.NET Core, vamos armazenar essas
informações no appSettings. JSON arquivo. Abra appSettings. JSONe observe que ele já inclui o seguinte:

{
"Data": {
"DefaultConnection": {
"ConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=_CHANGE_ME;Trusted_Connection=True;"
}
}
}

Na linha realçada descrita acima, altere o nome do banco de dados _CHANGE_ME para o nome do banco de
dados.

Resumo
ASP.NET Core coloca toda a lógica de inicialização para o aplicativo em um único arquivo, no qual os serviços
necessários e dependências podem ser definidas e configuradas. Ele substitui o Web. config arquivo com um
recurso de configuração flexíveis que pode aproveitar uma variedade de formatos de arquivo, como JSON, bem
como variáveis de ambiente.
Migrar de autenticação e identidade para o ASP.NET
Core
10/04/2018 • 5 min to read • Edit Online

Por Steve Smith


No anterior do artigo é migrados de configuração de um projeto ASP.NET MVC para MVC do ASP.NET Core.
Neste artigo, migrar os recursos de gerenciamento de registro, logon e usuário.

Defina a identidade e associação


No ASP.NET MVC, os recursos de autenticação e identidade são configurados usando a identidade do ASP.NET
em Startup.Auth.cs e IdentityConfig.cs, localizado na pasta App_Start. No ASP.NET MVC de núcleo, esses
recursos são configurados no Startup.cs.
Instalar o Microsoft.AspNetCore.Identity.EntityFrameworkCore e Microsoft.AspNetCore.Authentication.Cookies
pacotes do NuGet.
Em seguida, abra Startup.cs e atualize o ConfigureServices() método para usar os serviços de identidade do
Entity Framework:

public void ConfigureServices(IServiceCollection services)


{
// Add EF services to the services container.
services.AddEntityFramework(Configuration)
.AddSqlServer()
.AddDbContext<ApplicationDbContext>();

// Add Identity services to the services container.


services.AddIdentity<ApplicationUser, IdentityRole>(Configuration)
.AddEntityFrameworkStores<ApplicationDbContext>();

services.AddMvc();
}

Neste ponto, há dois tipos referenciados no código acima que nós ainda não foram migrados do projeto ASP.NET
MVC: ApplicationDbContext e ApplicationUser . Criar um novo modelos pasta no ASP.NET Core do projeto e
adicionar duas classes a ele correspondente a esses tipos. Você encontrará o ASP.NET MVC versões dessas
classes em /Models/IdentityModels.cs , mas vamos usar um arquivo por classe no projeto migrado porque é mais
clara.
ApplicationUser.cs:

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;

namespace NewMvc6Project.Models
{
public class ApplicationUser : IdentityUser
{
}
}

ApplicationDbContext.cs:
using Microsoft.AspNetCore.Identity.EntityFramework;
using Microsoft.Data.Entity;

namespace NewMvc6Project.Models
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
{
Database.EnsureCreated();
}

protected override void OnConfiguring(DbContextOptionsBuilder options)


{
options.UseSqlServer();
}
}
}

O projeto da Web do ASP.NET Core MVC Starter não inclui mais personalização de usuários ou o
ApplicationDbContext. Ao migrar um aplicativo real, você também precisará migrar todas as propriedades
personalizadas e métodos de classes de DbContext e usuário do seu aplicativo, bem como quaisquer outras
classes de modelo que utiliza o seu aplicativo (por exemplo, se o DbContext tem um DbSet, obviamente você
precisará migrar a classe álbum).
Com esses arquivos no local, o arquivo Startup.cs pode ser feito para compilar atualizando seu usando instruções:

using Microsoft.Framework.ConfigurationModel;
using Microsoft.AspNetCore.Hosting;
using NewMvc6Project.Models;
using Microsoft.AspNetCore.Identity;

Nosso aplicativo agora está pronto para dar suporte a serviços de autenticação e identidade – ele só precisa ter
esses recursos expostos aos usuários.

Migrar o registro e a lógica de logon


Com os serviços de identidade configurados para o aplicativo e o acesso a dados configurado usando o Entity
Framework e o SQL Server, você agora está pronto para adicionar suporte para registro e logon para o aplicativo.
Lembre-se de que anterior no processo de migração é comentada uma referência para loginpartial no layout.
cshtml. Agora é hora para retornar a esse código, remova os comentários e adicione os controladores de
necessários e modos de exibição para dar suporte à funcionalidade de logon.
Atualizar layout. cshtml; Remova o @Html.Partial linha:

<li>@Html.ActionLink("Contact", "Contact", "Home")</li>


</ul>
@*@Html.Partial("_LoginPartial")*@
</div>
</div>

Agora, adicione uma nova página de exibição MVC chamado loginpartial para a pasta exibições/compartilhadas:
Atualizar loginpartial. cshtml com o código a seguir (substitua todo o seu conteúdo):
@inject SignInManager<User> SignInManager
@inject UserManager<User> UserManager

@if (SignInManager.IsSignedIn(User))
{
<form asp-area="" asp-controller="Account" asp-action="LogOff" method="post" id="logoutForm"
class="navbar-right">
<ul class="nav navbar-nav navbar-right">
<li>
<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage">Hello
@UserManager.GetUserName(User)!</a>
</li>
<li>
<button type="submit" class="btn btn-link navbar-btn navbar-link">Log off</button>
</li>
</ul>
</form>
}
else
{
<ul class="nav navbar-nav navbar-right">
<li><a asp-area="" asp-controller="Account" asp-action="Register">Register</a></li>
<li><a asp-area="" asp-controller="Account" asp-action="Login">Log in</a></li>
</ul>
}

Neste ponto, você deve ser capaz de atualizar o site no navegador.

Resumo
ASP.NET Core introduz alterações para os recursos de identidade do ASP.NET. Neste artigo, você viu como migrar
os recursos de gerenciamento de usuário e autenticação de uma identidade do ASP.NET para o ASP.NET Core.
Migrar de API da Web do ASP.NET para o ASP.NET
Core
27/04/2018 • 10 min to read • Edit Online

Por Steve Smith e Scott Addie


APIs da Web são serviços HTTP que alcançam uma ampla gama de clientes, incluindo navegadores e dispositivos
móveis. Núcleo do ASP.NET MVC inclui suporte para a criação de APIs da Web fornecendo uma maneira simples
e consistente de criação de aplicativos web. Neste artigo, vamos demonstrar as etapas necessárias para migrar
uma implementação da API da Web do ASP.NET Web API para MVC do ASP.NET Core.
Exibir ou baixar código de exemplo (como baixar)

Revisão ASP.NET Web API do projeto


Este artigo usa o projeto de exemplo, ProductsApp, criado no artigo guia de Introdução ao ASP.NET Web API 2
como ponto de partida. No projeto, um projeto de API da Web ASP.NET simple é configurado da seguinte
maneira.
Em Global.asax.cs, é feita uma chamada para WebApiConfig.Register :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Routing;

namespace ProductsApp
{
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
}

WebApiConfig é definido em App_Start, e tem apenas uma estática Register método:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;

namespace ProductsApp
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services

// Web API routes


config.MapHttpAttributeRoutes();

config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}

Essa classe configura roteamento de atributo, embora na verdade não está sendo usado no projeto. Ele também
configura a tabela de roteamento que é usada pela API da Web do ASP.NET. Nesse caso, o ASP.NET Web API
esperará URLs para corresponder ao formato /api/ {controller } / {id }, com {id } opcionais.
O ProductsApp projeto inclui apenas um controlador simple, que herda de ApiController e expõe dois métodos:
using ProductsApp.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Http;

namespace ProductsApp.Controllers
{
public class ProductsController : ApiController
{
Product[] products = new Product[]
{
new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 },
new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M },
new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M }
};

public IEnumerable<Product> GetAllProducts()


{
return products;
}

public IHttpActionResult GetProduct(int id)


{
var product = products.FirstOrDefault((p) => p.Id == id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
}
}

Por fim, o modelo produto, usada pelo ProductsApp, é uma classe simple:

namespace ProductsApp.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
}
}

Agora que temos um projeto simple da qual iniciar, podemos demonstrar como migrar este projeto de API da
Web para ASP.NET MVC de núcleo.

Criar o projeto de destino


Usando o Visual Studio, crie uma solução nova e vazia e nomeie- WebAPIMigration. Adicionar existente
ProductsApp projeto a ele, em seguida, adicionar um novo projeto de aplicativo de Web de núcleo ASP.NET à
solução. Nomeie o novo projeto ProductsCore.
Em seguida, escolha o modelo de projeto de API da Web. Migraremos o ProductsApp conteúdo para esse novo
projeto.

Excluir o Project_Readme.html arquivo do novo projeto. Agora, sua solução deve ser assim:
Migrar a configuração
Não usa o ASP.NET Core global. asax, Web. config, ou App_Start pastas. Em vez disso, todas as tarefas de
inicialização são realizadas em Startup.cs na raiz do projeto (consulte inicialização do aplicativo). No ASP.NET
MVC de núcleo, roteamento baseado em atributo agora está incluído por padrão quando UseMvc() é chamado; e
isso é a abordagem recomendada para configurar rotas de API da Web (e é como o projeto de starter API da Web
trata de roteamento).
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace ProductsCore
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();

app.UseMvc();
}
}
}

Supondo que você deseja usar o roteamento de atributo em seu projeto no futuro, nenhuma configuração
adicional é necessária. Simplesmente aplicar os atributos conforme necessário para seus controladores e ações,
como é feito no exemplo ValuesController classe que está incluído no projeto de starter API da Web:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace ProductsCore.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}

// GET api/values/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}

// POST api/values
[HttpPost]
public void Post([FromBody]string value)
{
}

// PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody]string value)
{
}

// DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}

Observe a presença de [controller ] na linha 8. Roteamento baseado em atributo agora oferece suporte a
determinados símbolos, como [controller ] e [ação ]. Esses tokens são substituídos em tempo de execução com o
nome do controlador ou ação, respectivamente, para que o atributo foi aplicado. Isso serve para reduzir o número
de cadeias de caracteres mágicas no projeto e garante que as rotas serão mantidas sincronizadas com os
respectivos controladores e ações quando refatorações renomear automaticamente são aplicadas.
Para migrar o controlador de API de produtos, é necessário primeiro copiar ProductsController para o novo
projeto. Basta inclua o atributo da rota no controlador:

[Route("api/[controller]")]

Você também precisará adicionar o [HttpGet] atributo para os dois métodos, desde que ambos devem ser
chamados por meio de HTTP Get. Incluir a expectativa de um parâmetro de "id" no atributo para GetProduct() :
// /api/products
[HttpGet]
...

// /api/products/1
[HttpGet("{id}")]

Neste ponto, o roteamento está configurado corretamente. No entanto, podemos ainda não é possível testá-lo.
Outras alterações devem ser feitas antes de ProductsController serão compilados.

Migrar modelos e controladores


A última etapa no processo de migração para este projeto de API da Web simple é copiar os controladores e os
modelos usarem. Nesse caso, basta copiar Controllers/ProductsController.cs do projeto original para o novo. Em
seguida, copie toda a pasta de modelos de projeto original para o novo. Ajustar os namespaces para coincidir com
o novo nome do projeto (ProductsCore). Neste ponto, você pode criar o aplicativo e você encontrará um número
de erros de compilação. Eles geralmente devem se encaixam nas seguintes categorias:
ApiController não existe
System.Web.Http namespace não existe
IHttpActionResult não existe
Felizmente, elas são muito fácil corrigir:
Alterar ApiController para controlador (talvez seja necessário adicionar usando Microsoft.AspNetCore.Mvc)
Excluir qualquer uso instrução referindo-se a System.Web.Http
Alterar qualquer método retornando IHttpActionResult para retornar um IActionResult
Depois que essas alterações foram feitas e não utilizados usando instruções removido, o migrados
ProductsController classe tem esta aparência:
using Microsoft.AspNetCore.Mvc;
using ProductsCore.Models;
using System.Collections.Generic;
using System.Linq;

namespace ProductsCore.Controllers
{
[Route("api/[controller]")]
public class ProductsController : Controller
{
Product[] products = new Product[]
{
new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 },
new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M },
new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M }
};

// /api/products
[HttpGet]
public IEnumerable<Product> GetAllProducts()
{
return products;
}

// /api/products/1
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
var product = products.FirstOrDefault((p) => p.Id == id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
}
}

Agora você deve ser capaz de executar o projeto migrado e navegue até /api/produtos; e, você deve ver a lista
completa de 3 produtos. Navegue até /api/products/1 e você verá o primeiro produto.

Resumo
Migrando um projeto ASP.NET Web API simple para o ASP.NET MVC de núcleo é relativamente simples, graças
ao suporte interno para APIs da Web em ASP.NET MVC de núcleo. As principais partes que todos os projetos
ASP.NET Web API precisará migrar são rotas, controladores e modelos, junto com as atualizações para os tipos
usados por controladores e ações.
Migrar manipuladores HTTP e módulos para o
ASP.NET Core middleware
04/05/2018 • 25 min to read • Edit Online

Por Matt Perdeck


Este artigo mostra como migrar ASP.NET existente módulos HTTP e manipuladores de System. webServer para
ASP.NET Core middleware.

Manipuladores revisitados e módulos


Antes de prosseguir para o ASP.NET Core middleware, vejamos primeiro novamente como manipuladores e
módulos HTTP funcionam:

Manipuladores são:
As classes que implementam IHttpHandler
Usado para manipular solicitações com uma extensão, ou o nome de arquivo fornecido como relatório
Configurado em Web. config
Os módulos são:
As classes que implementam IHttpModule
Chamado para cada solicitação
Capaz de curto-circuito (Interromper processamento adicional de uma solicitação)
Capaz de adicionar a resposta HTTP, ou criar seus próprios
Configurado em Web. config
A ordem em que os módulos de processam solicitações de entrada é determinada por:
1. O ciclo de vida do aplicativo, que é um eventos série acionado pelo ASP.NET: BeginRequest,
AuthenticateRequest, etc. Cada módulo pode criar um manipulador de eventos de um ou mais.
2. Para o mesmo evento, a ordem na qual eles foram configurados no Web. config.
Além de módulos, você pode adicionar manipuladores para os eventos de ciclo de vida para o Global.asax.cs
arquivo. Esses manipuladores executar após os manipuladores nos módulos configurados.

Manipuladores e módulos de middleware


Middleware são mais simples do que manipuladores e módulos HTTP:
Módulos, manipuladores, Global.asax.cs, Web. config (exceto para a configuração do IIS ) e o ciclo de vida
do aplicativo serão excluídos
As funções dos módulos e manipuladores de tem sido controladas por middleware
Middleware são configurados usando o código em vez de em Web. config
Ramificação de pipeline permite que você envia solicitações ao middleware específico, com base na URL
não só mas também em cabeçalhos de solicitação, cadeias de caracteres de consulta, etc.
Middleware são muito semelhantes aos módulos:
Invocado em princípio para cada solicitação
Capaz de curto-circuito a uma solicitação por não passar a solicitação para o próximo middleware
Capaz de criar sua própria resposta HTTP
Middleware e módulos são processados em uma ordem diferente:
Ordem de middleware é baseada na ordem em que inserção no pipeline de solicitação, enquanto a ordem
dos módulos baseia-se principalmente em ciclo de vida do aplicativo eventos
Ordem de middleware para respostas é o oposto do que para solicitações, enquanto a ordem dos
módulos é o mesmo para solicitações e respostas
Consulte criar um pipeline de middleware com IApplicationBuilder

Observe como na imagem acima, o middleware de autenticação curto-circuito a solicitação.

Migrando o código de módulo para middleware


Um módulo HTTP existente será semelhante a este:
// ASP.NET 4 module

using System;
using System.Web;

namespace MyApp.Modules
{
public class MyModule : IHttpModule
{
public void Dispose()
{
}

public void Init(HttpApplication application)


{
application.BeginRequest += (new EventHandler(this.Application_BeginRequest));
application.EndRequest += (new EventHandler(this.Application_EndRequest));
}

private void Application_BeginRequest(Object source, EventArgs e)


{
HttpContext context = ((HttpApplication)source).Context;

// Do something with context near the beginning of request processing.


}

private void Application_EndRequest(Object source, EventArgs e)


{
HttpContext context = ((HttpApplication)source).Context;

// Do something with context near the end of request processing.


}
}
}

Conforme mostrado no Middleware página, um middleware ASP.NET Core é uma classe que expõe um Invoke
colocando método um HttpContext e retornar um Task . Seu novo middleware será assim:
// ASP.NET Core middleware

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;

namespace MyApp.Middleware
{
public class MyMiddleware
{
private readonly RequestDelegate _next;

public MyMiddleware(RequestDelegate next)


{
_next = next;
}

public async Task Invoke(HttpContext context)


{
// Do something with context near the beginning of request processing.

await _next.Invoke(context);

// Clean up.
}
}

public static class MyMiddlewareExtensions


{
public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyMiddleware>();
}
}
}

O modelo de middleware anterior foi obtido da seção de gravar middleware.


O MyMiddlewareExtensions classe auxiliar torna mais fácil de configurar o middleware em seu Startup classe. O
UseMyMiddleware método adiciona sua classe de middleware no pipeline de solicitação. Serviços necessários para
o middleware são injetados no construtor do middleware.
O módulo pode encerrar uma solicitação, por exemplo, se o usuário não autorizado:

// ASP.NET 4 module that may terminate the request

private void Application_BeginRequest(Object source, EventArgs e)


{
HttpContext context = ((HttpApplication)source).Context;

// Do something with context near the beginning of request processing.

if (TerminateRequest())
{
context.Response.End();
return;
}
}

Um middleware lida com isso chamando não Invoke no próximo middleware no pipeline. Tenha em mente que
isso não totalmente encerra a solicitação, pois middlewares anterior ainda será invocado quando a resposta torna
sua maneira novamente por meio do pipeline.
// ASP.NET Core middleware that may terminate the request

public async Task Invoke(HttpContext context)


{
// Do something with context near the beginning of request processing.

if (!TerminateRequest())
await _next.Invoke(context);

// Clean up.
}

Ao migrar a funcionalidade do módulo para o seu middleware de novo, você pode achar que seu código não
compilado porque o HttpContext classe tiverem sido alterados significativamente no núcleo do ASP.NET. Mais
tarde, você verá como migrar para o ASP.NET Core HttpContext novo.

Migrando inserção de módulo no pipeline de solicitação


Módulos HTTP normalmente são adicionados ao pipeline de solicitação usando Web. config:

<?xml version="1.0" encoding="utf-8"?>


<!--ASP.NET 4 web.config-->
<configuration>
<system.webServer>
<modules>
<add name="MyModule" type="MyApp.Modules.MyModule"/>
</modules>
</system.webServer>
</configuration>

Converter isso por adicionando seu novo middleware para o pipeline de solicitação no seu Startup classe:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}

app.UseMyMiddleware();

app.UseMyMiddlewareWithParams();

var myMiddlewareOptions = Configuration.GetSection("MyMiddlewareOptionsSection").Get<MyMiddlewareOptions>


();
var myMiddlewareOptions2 =
Configuration.GetSection("MyMiddlewareOptionsSection2").Get<MyMiddlewareOptions>();
app.UseMyMiddlewareWithParams(myMiddlewareOptions);
app.UseMyMiddlewareWithParams(myMiddlewareOptions2);

app.UseMyTerminatingMiddleware();

// Create branch to the MyHandlerMiddleware.


// All requests ending in .report will follow this branch.
app.MapWhen(
context => context.Request.Path.ToString().EndsWith(".report"),
appBranch => {
// ... optionally add more middleware to this branch
appBranch.UseMyHandler();
});

app.MapWhen(
context => context.Request.Path.ToString().EndsWith(".context"),
appBranch => {
appBranch.UseHttpContextDemoMiddleware();
});

app.UseStaticFiles();

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

Ponto no pipeline, em que você insere seu novo middleware exato depende do evento que ela tratada como um
módulo ( BeginRequest , EndRequest , etc.) e sua ordem na lista de módulos em Web. config.
Como anteriormente não mencionado, há mais nenhum ciclo de vida do aplicativo no núcleo do ASP.NET e a
ordem na qual as respostas são processadas pelo middleware diferente daquela usada pelos módulos. Isso pode
tornar sua decisão de ordenação mais difícil.
Se ordenação se torna um problema, você pode dividir seu módulo em vários componentes de middleware que
podem ser ordenados de forma independente.

Migrando o código de manipulador de middleware


Um manipulador HTTP parecida com esta:

// ASP.NET 4 handler

using System.Web;

namespace MyApp.HttpHandlers
{
public class MyHandler : IHttpHandler
{
public bool IsReusable { get { return true; } }

public void ProcessRequest(HttpContext context)


{
string response = GenerateResponse(context);

context.Response.ContentType = GetContentType();
context.Response.Output.Write(response);
}

// ...

private string GenerateResponse(HttpContext context)


{
string title = context.Request.QueryString["title"];
return string.Format("Title of the report: {0}", title);
}

private string GetContentType()


{
return "text/plain";
}
}
}

No seu projeto do ASP.NET Core, isso pode ser traduzido para um middleware semelhante a este:
// ASP.NET Core middleware migrated from a handler

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;

namespace MyApp.Middleware
{
public class MyHandlerMiddleware
{

// Must have constructor with this signature, otherwise exception at run time
public MyHandlerMiddleware(RequestDelegate next)
{
// This is an HTTP Handler, so no need to store next
}

public async Task Invoke(HttpContext context)


{
string response = GenerateResponse(context);

context.Response.ContentType = GetContentType();
await context.Response.WriteAsync(response);
}

// ...

private string GenerateResponse(HttpContext context)


{
string title = context.Request.Query["title"];
return string.Format("Title of the report: {0}", title);
}

private string GetContentType()


{
return "text/plain";
}
}

public static class MyHandlerExtensions


{
public static IApplicationBuilder UseMyHandler(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyHandlerMiddleware>();
}
}
}

Este middleware é muito semelhante para o middleware correspondente a módulos. A única diferença é que não
há aqui nenhuma chamada para _next.Invoke(context) . Isso faz sentido, porque o manipulador não é no final do
pipeline de solicitação, que será a nenhum próximo middleware para invocar.

Migrando inserção manipulador no pipeline de solicitação


Configurar um manipulador HTTP é feita em Web. config e tem a seguinte aparência:
<?xml version="1.0" encoding="utf-8"?>
<!--ASP.NET 4 web.config-->
<configuration>
<system.webServer>
<handlers>
<add name="MyHandler" verb="*" path="*.report" type="MyApp.HttpHandlers.MyHandler"
resourceType="Unspecified" preCondition="integratedMode"/>
</handlers>
</system.webServer>
</configuration>

Você pode convertê-lo adicionando seu novo middleware de manipulador para o pipeline de solicitação no seu
Startup classe, semelhante ao middleware convertido de módulos. O problema com essa abordagem é que ele
poderia enviar todas as solicitações para seu novo middleware de manipulador. No entanto, você precisará
apenas solicitações com uma determinada extensão para alcançar seu middleware. Essa seria a mesma
funcionalidade que tinha com o manipulador HTTP.
Uma solução é ramificar o pipeline de solicitações com uma determinada extensão, usando o MapWhen método
de extensão. Você pode fazer isso no mesmo Configure método em que você adiciona o middleware outros:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}

app.UseMyMiddleware();

app.UseMyMiddlewareWithParams();

var myMiddlewareOptions = Configuration.GetSection("MyMiddlewareOptionsSection").Get<MyMiddlewareOptions>


();
var myMiddlewareOptions2 =
Configuration.GetSection("MyMiddlewareOptionsSection2").Get<MyMiddlewareOptions>();
app.UseMyMiddlewareWithParams(myMiddlewareOptions);
app.UseMyMiddlewareWithParams(myMiddlewareOptions2);

app.UseMyTerminatingMiddleware();

// Create branch to the MyHandlerMiddleware.


// All requests ending in .report will follow this branch.
app.MapWhen(
context => context.Request.Path.ToString().EndsWith(".report"),
appBranch => {
// ... optionally add more middleware to this branch
appBranch.UseMyHandler();
});

app.MapWhen(
context => context.Request.Path.ToString().EndsWith(".context"),
appBranch => {
appBranch.UseHttpContextDemoMiddleware();
});

app.UseStaticFiles();

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

MapWhen usa esses parâmetros:


1. Uma expressão lambda que usa o HttpContext e retorna true se a solicitação deve ir para a ramificação.
Isso significa que você pode se ramificar solicitações não apenas com base em sua extensão, mas também
em cabeçalhos de solicitação, parâmetros de cadeia de caracteres de consulta, etc.
2. Uma expressão lambda que leva um IApplicationBuilder e adiciona todos o middleware para a
ramificação. Isso significa que você pode adicionar middleware adicional para a ramificação na frente do
seu manipulador middleware.
Middleware adicionados ao pipeline antes da ramificação será invocada em todas as solicitações; a ramificação
não terá impacto sobre eles.

Opções de middleware usando o padrão de opções de carregamento


Alguns módulos e manipuladores têm opções de configuração que são armazenadas em Web. config. No
entanto, no ASP.NET Core um novo modelo de configuração é usado no lugar de Web. config.
O novo sistema de configuração oferece essas opções para resolver isso:
Injetar diretamente as opções para o middleware, conforme o próxima seção.
Use o padrão de opções:
1. Crie uma classe para manter suas opções de middleware, por exemplo:

public class MyMiddlewareOptions


{
public string Param1 { get; set; }
public string Param2 { get; set; }
}

2. Armazenar os valores de opção


O sistema de configuração permite que você armazene valores de opção em qualquer local desejado. No
entanto, a maioria dos locais use appSettings. JSON, portanto, vamos essa abordagem:

{
"MyMiddlewareOptionsSection": {
"Param1": "Param1Value",
"Param2": "Param2Value"
}
}

MyMiddlewareOptionsSection aqui é um nome de seção. Ele não precisa ser igual ao nome da sua classe
de opções.
3. Associe os valores de opção com a classe de opções
O padrão de opções usa a estrutura de injeção de dependência do ASP.NET Core para associar o tipo de
opções (como MyMiddlewareOptions ) com um MyMiddlewareOptions objeto que tem as opções reais.
Atualização de seu Startup classe:
a. Se você estiver usando appSettings. JSON, adicioná-lo para o construtor de configuração no
Startup construtor:

public Startup(IHostingEnvironment env)


{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}

b. Configure o serviço de opções:


public void ConfigureServices(IServiceCollection services)
{
// Setup options service
services.AddOptions();

// Load options from section "MyMiddlewareOptionsSection"


services.Configure<MyMiddlewareOptions>(
Configuration.GetSection("MyMiddlewareOptionsSection"));

// Add framework services.


services.AddMvc();
}

c. Associe as opções de sua classe de opções:

public void ConfigureServices(IServiceCollection services)


{
// Setup options service
services.AddOptions();

// Load options from section "MyMiddlewareOptionsSection"


services.Configure<MyMiddlewareOptions>(
Configuration.GetSection("MyMiddlewareOptionsSection"));

// Add framework services.


services.AddMvc();
}

4. Inserir as opções para o construtor de middleware. Isso é semelhante a injeção de opções em um


controlador.

public class MyMiddlewareWithParams


{
private readonly RequestDelegate _next;
private readonly MyMiddlewareOptions _myMiddlewareOptions;

public MyMiddlewareWithParams(RequestDelegate next,


IOptions<MyMiddlewareOptions> optionsAccessor)
{
_next = next;
_myMiddlewareOptions = optionsAccessor.Value;
}

public async Task Invoke(HttpContext context)


{
// Do something with context near the beginning of request processing
// using configuration in _myMiddlewareOptions

await _next.Invoke(context);

// Do something with context near the end of request processing


// using configuration in _myMiddlewareOptions
}
}

O UseMiddleware método de extensão que adiciona o middleware para o IApplicationBuilder cuida de


injeção de dependência.
Isso não é limitado a IOptions objetos. Qualquer objeto que requer o middleware pode ser inserido dessa
maneira.
Carregando opções de middleware injeção direto
O padrão de opções tem a vantagem de que ele cria flexível acoplamento entre valores de opções e seus
consumidores. Depois que você associou a uma classe de opções com os valores reais de opções, qualquer outra
classe pode obter acesso às opções por meio da estrutura de injeção de dependência. Não é necessário para
passar em torno de valores de opções.
Isso divide Embora se você quiser usar o mesmo middleware duas vezes, com opções diferentes. Por exemplo
um autorização middleware usado em diferentes ramificações, permitindo que diferentes funções. Você não pode
associar dois objetos diferentes opções com a classe de opções de um.
A solução é obter os objetos de opções com os valores de opções reais no seu Startup classe e passe-os
diretamente a cada instância de seu middleware.
1. Adicionar uma segunda chave para appSettings. JSON
Para adicionar um segundo conjunto de opções para o appSettings. JSON de arquivo, use uma nova
chave para identificá-lo exclusivamente:

{
"MyMiddlewareOptionsSection2": {
"Param1": "Param1Value2",
"Param2": "Param2Value2"
},
"MyMiddlewareOptionsSection": {
"Param1": "Param1Value",
"Param2": "Param2Value"
}
}

2. Recuperar valores de opções e os transmite para o middleware. O Use... método de extensão (o que
adiciona o middleware no pipeline) é um local lógico para transmitir os valores de opção:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}

app.UseMyMiddleware();

app.UseMyMiddlewareWithParams();

var myMiddlewareOptions =
Configuration.GetSection("MyMiddlewareOptionsSection").Get<MyMiddlewareOptions>();
var myMiddlewareOptions2 =
Configuration.GetSection("MyMiddlewareOptionsSection2").Get<MyMiddlewareOptions>();
app.UseMyMiddlewareWithParams(myMiddlewareOptions);
app.UseMyMiddlewareWithParams(myMiddlewareOptions2);

app.UseMyTerminatingMiddleware();

// Create branch to the MyHandlerMiddleware.


// All requests ending in .report will follow this branch.
app.MapWhen(
context => context.Request.Path.ToString().EndsWith(".report"),
appBranch => {
// ... optionally add more middleware to this branch
appBranch.UseMyHandler();
});

app.MapWhen(
context => context.Request.Path.ToString().EndsWith(".context"),
appBranch => {
appBranch.UseHttpContextDemoMiddleware();
});

app.UseStaticFiles();

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

3. Habilite middleware para utilizar um parâmetro de opções. Forneça uma sobrecarga do Use... método
de extensão (que usa o parâmetro options e passá-lo para UseMiddleware ). Quando UseMiddleware é
chamado com parâmetros, ele passa os parâmetros para o construtor de middleware quando ele cria uma
instância do objeto de middleware.
public static class MyMiddlewareWithParamsExtensions
{
public static IApplicationBuilder UseMyMiddlewareWithParams(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyMiddlewareWithParams>();
}

public static IApplicationBuilder UseMyMiddlewareWithParams(


this IApplicationBuilder builder, MyMiddlewareOptions myMiddlewareOptions)
{
return builder.UseMiddleware<MyMiddlewareWithParams>(
new OptionsWrapper<MyMiddlewareOptions>(myMiddlewareOptions));
}
}

Observe como isso encapsula o objeto de opções em um OptionsWrapper objeto. Isso implementa
IOptions , conforme o esperado pelo construtor de middleware.

Migrando para o novo HttpContext


Anteriormente, você viu que o Invoke método no seu middleware usa um parâmetro de tipo HttpContext :

public async Task Invoke(HttpContext context)

HttpContext foi alterado significativamente no núcleo do ASP.NET. Esta seção mostra como converter as
propriedades mais usadas de System.Web.HttpContext para o novo Microsoft.AspNetCore.Http.HttpContext .
HttpContext
HttpContext se traduz em:

IDictionary<object, object> items = httpContext.Items;

ID da solicitação exclusiva (nenhum equivalente System.Web.HttpContext)


Fornece uma id exclusiva para cada solicitação. Muito útil para incluir nos logs.

string requestId = httpContext.TraceIdentifier;

HttpContext.Request
HttpContext.Request.HttpMethod se traduz em:

string httpMethod = httpContext.Request.Method;

HttpContext.Request.QueryString se traduz em:


IQueryCollection queryParameters = httpContext.Request.Query;

// If no query parameter "key" used, values will have 0 items


// If single value used for a key (...?key=v1), values will have 1 item ("v1")
// If key has multiple values (...?key=v1&key=v2), values will have 2 items ("v1" and "v2")
IList<string> values = queryParameters["key"];

// If no query parameter "key" used, value will be ""


// If single value used for a key (...?key=v1), value will be "v1"
// If key has multiple values (...?key=v1&key=v2), value will be "v1,v2"
string value = queryParameters["key"].ToString();

HttpContext.Request.Url e HttpContext.Request.RawUrl traduzir para:

// using Microsoft.AspNetCore.Http.Extensions;
var url = httpContext.Request.GetDisplayUrl();

HttpContext.Request.IsSecureConnection se traduz em:

var isSecureConnection = httpContext.Request.IsHttps;

HttpContext.Request.UserHostAddress se traduz em:

var userHostAddress = httpContext.Connection.RemoteIpAddress?.ToString();

HttpContext.Request.Cookies se traduz em:

IRequestCookieCollection cookies = httpContext.Request.Cookies;


string unknownCookieValue = cookies["unknownCookie"]; // will be null (no exception)
string knownCookieValue = cookies["cookie1name"]; // will be actual value

HttpContext.Request.RequestContext.RouteData se traduz em:

var routeValue = httpContext.GetRouteValue("key");

HttpContext.Request.Headers se traduz em:

// using Microsoft.AspNetCore.Http.Headers;
// using Microsoft.Net.Http.Headers;

IHeaderDictionary headersDictionary = httpContext.Request.Headers;

// GetTypedHeaders extension method provides strongly typed access to many headers


var requestHeaders = httpContext.Request.GetTypedHeaders();
CacheControlHeaderValue cacheControlHeaderValue = requestHeaders.CacheControl;

// For unknown header, unknownheaderValues has zero items and unknownheaderValue is ""
IList<string> unknownheaderValues = headersDictionary["unknownheader"];
string unknownheaderValue = headersDictionary["unknownheader"].ToString();

// For known header, knownheaderValues has 1 item and knownheaderValue is the value
IList<string> knownheaderValues = headersDictionary[HeaderNames.AcceptLanguage];
string knownheaderValue = headersDictionary[HeaderNames.AcceptLanguage].ToString();

HttpContext.Request.UserAgent se traduz em:


string userAgent = headersDictionary[HeaderNames.UserAgent].ToString();

HttpContext.Request.UrlReferrer se traduz em:

string urlReferrer = headersDictionary[HeaderNames.Referer].ToString();

HttpContext.Request.ContentType se traduz em:

// using Microsoft.Net.Http.Headers;

MediaTypeHeaderValue mediaHeaderValue = requestHeaders.ContentType;


string contentType = mediaHeaderValue?.MediaType.ToString(); // ex. application/x-www-form-urlencoded
string contentMainType = mediaHeaderValue?.Type.ToString(); // ex. application
string contentSubType = mediaHeaderValue?.SubType.ToString(); // ex. x-www-form-urlencoded

System.Text.Encoding requestEncoding = mediaHeaderValue?.Encoding;

HttpContext.Request.Form se traduz em:

if (httpContext.Request.HasFormContentType)
{
IFormCollection form;

form = httpContext.Request.Form; // sync


// Or
form = await httpContext.Request.ReadFormAsync(); // async

string firstName = form["firstname"];


string lastName = form["lastname"];
}

AVISO
Valores de formulário de leitura somente se o tipo de conteúdo sub é x-www-form-urlencoded ou dados do formulário.

HttpContext.Request.InputStream se traduz em:

string inputBody;
using (var reader = new System.IO.StreamReader(
httpContext.Request.Body, System.Text.Encoding.UTF8))
{
inputBody = reader.ReadToEnd();
}

AVISO
Use esse código somente em um middleware de tipo de manipulador, no final de um pipeline.
Você pode ler o corpo bruto conforme mostrado acima apenas uma vez por solicitação. Middleware tentar ler o corpo após
a primeira leitura lê um corpo vazio.
Isso não se aplica a leitura de um formulário como mostrado anteriormente, porque isso é feito de um buffer.

HttpContext.Response
HttpContext.Response.Status e HttpContext.Response.StatusDescription traduzir para:
// using Microsoft.AspNetCore.Http;
httpContext.Response.StatusCode = StatusCodes.Status200OK;

HttpContext.Response.ContentEncoding e HttpContext.Response.ContentType traduzir para:

// using Microsoft.Net.Http.Headers;
var mediaType = new MediaTypeHeaderValue("application/json");
mediaType.Encoding = System.Text.Encoding.UTF8;
httpContext.Response.ContentType = mediaType.ToString();

HttpContext.Response.ContentType em seu próprio também se traduz em:

httpContext.Response.ContentType = "text/html";

HttpContext.Response.Output se traduz em:

string responseContent = GetResponseContent();


await httpContext.Response.WriteAsync(responseContent);

HttpContext.Response.TransmitFile
Serviços de um arquivo é discutida aqui.
HttpContext.Response.Headers
Enviar cabeçalhos de resposta é complicada pelo fato de que se você defini-las depois que nada foi gravado no
corpo da resposta, ele não serão enviados.
A solução é definir um método de retorno de chamada que será chamado direita antes da gravação do início da
resposta. Isso é feito melhor no início do Invoke método no seu middleware. É desse método de retorno de
chamada que define os cabeçalhos de resposta.
O código a seguir define um método de retorno de chamada chamado SetHeaders :

public async Task Invoke(HttpContext httpContext)


{
// ...
httpContext.Response.OnStarting(SetHeaders, state: httpContext);

O SetHeaders método de retorno de chamada terá esta aparência:


// using Microsoft.AspNet.Http.Headers;
// using Microsoft.Net.Http.Headers;

private Task SetHeaders(object context)


{
var httpContext = (HttpContext)context;

// Set header with single value


httpContext.Response.Headers["ResponseHeaderName"] = "headerValue";

// Set header with multiple values


string[] responseHeaderValues = new string[] { "headerValue1", "headerValue1" };
httpContext.Response.Headers["ResponseHeaderName"] = responseHeaderValues;

// Translating ASP.NET 4's HttpContext.Response.RedirectLocation


httpContext.Response.Headers[HeaderNames.Location] = "http://www.example.com";
// Or
httpContext.Response.Redirect("http://www.example.com");

// GetTypedHeaders extension method provides strongly typed access to many headers


var responseHeaders = httpContext.Response.GetTypedHeaders();

// Translating ASP.NET 4's HttpContext.Response.CacheControl


responseHeaders.CacheControl = new CacheControlHeaderValue
{
MaxAge = new System.TimeSpan(365, 0, 0, 0)
// Many more properties available
};

// If you use .Net 4.6+, Task.CompletedTask will be a bit faster


return Task.FromResult(0);
}

HttpContext.Response.Cookies
Cookies de viagem para o navegador em um Set-Cookie cabeçalho de resposta. Como resultado, envio de
cookies requer o retorno de chamada mesmo usada para enviar cabeçalhos de resposta:

public async Task Invoke(HttpContext httpContext)


{
// ...
httpContext.Response.OnStarting(SetCookies, state: httpContext);
httpContext.Response.OnStarting(SetHeaders, state: httpContext);

O SetCookies método de retorno de chamada deve ser semelhante ao seguinte:

private Task SetCookies(object context)


{
var httpContext = (HttpContext)context;

IResponseCookies responseCookies = httpContext.Response.Cookies;

responseCookies.Append("cookie1name", "cookie1value");
responseCookies.Append("cookie2name", "cookie2value",
new CookieOptions { Expires = System.DateTime.Now.AddDays(5), HttpOnly = true });

// If you use .Net 4.6+, Task.CompletedTask will be a bit faster


return Task.FromResult(0);
}

Recursos adicionais
Visão geral de módulos HTTP e de manipuladores HTTP
Configuração
Inicialização de aplicativos
Middleware
Migrar do ASP.NET para o ASP.NET Core 2.0
10/04/2018 • 13 min to read • Edit Online

Por Isaac Levin


Este artigo serve como um guia de referência para migração de aplicativos ASP.NET para o ASP.NET Core 2.0.

Pré-requisitos
.NET Core SDK 2.0 or later

Frameworks de destino
Projetos do ASP.NET Core 2.0 oferecem aos desenvolvedores a flexibilidade de direcionamento para o .NET Core,
.NET Framework ou ambos. Consulte Escolhendo entre o .NET Core e .NET Framework para aplicativos de
servidor para determinar qual estrutura de destino é mais apropriada.
Ao usar o .NET Framework como destino, projetos precisam fazer referência a pacotes NuGet individuais.
Usar o .NET Core como destino permite que você elimine várias referências de pacote explícitas, graças ao
metapacote do ASP.NET Core 2.0. Instale o metapacote Microsoft.AspNetCore.All em seu projeto:

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>

Quando o metapacote é usado, nenhum pacote referenciado no metapacote é implantado com o aplicativo. O
repositório de tempo de execução do .NET Core inclui esses ativos e eles são pré-compilados para melhorar o
desempenho. Consulte Metapacote do Microsoft.AspNetCore.All para ASP.NET Core 2.x para obter mais detalhes.

Diferenças de estrutura do projeto


O formato de arquivo .csproj foi simplificado no ASP.NET Core. Algumas alterações importantes incluem:
A inclusão explícita de arquivos não é necessária para que eles possam ser considerados como parte do
projeto. Isso reduz o risco de conflitos de mesclagem XML ao trabalhar em grandes equipes.
Não há referências baseadas em GUID a outros projetos, o que melhora a legibilidade do arquivo.
O arquivo pode ser editado sem descarregá-lo no Visual Studio:
Substituição do arquivo Global.asax
O ASP.NET Core introduziu um novo mecanismo para inicializar um aplicativo. O ponto de entrada para
aplicativos ASP.NET é o arquivo Global.asax. Tarefas como configuração de roteamento e registros de filtro e de
área são tratadas no arquivo Global.asax.

public class MvcApplication : System.Web.HttpApplication


{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}

Essa abordagem associa o aplicativo e o servidor no qual ele é implantado de uma forma que interfere na
implementação. Em um esforço para desassociar, a OWIN foi introduzida para fornecer uma maneira mais limpa
de usar várias estruturas juntas. A OWIN fornece um pipeline para adicionar somente os módulos necessários. O
ambiente de hospedagem leva uma função Startup para configurar serviços e pipeline de solicitação do aplicativo.
Startup registra um conjunto de middleware com o aplicativo. Para cada solicitação, o aplicativo chama cada um
dos componentes de middleware com o ponteiro de cabeçalho de uma lista vinculada para um conjunto existente
de manipuladores. Cada componente de middleware pode adicionar um ou mais manipuladores para a pipeline de
tratamento de solicitação. Isso é feito retornando uma referência para o manipulador que é o novo cabeçalho da
lista. Cada manipulador é responsável por se lembrar do próximo manipulador na lista e por invocá-lo. Com o
ASP.NET Core, o ponto de entrada para um aplicativo é Startup e você não tem mais uma dependência de
Global.asax. Ao usar a OWIN com o .NET Framework, use algo parecido com o seguinte como um pipeline:
using Owin;
using System.Web.Http;

namespace WebApi
{
// Note: By default all requests go through this OWIN pipeline. Alternatively you can turn this off by
adding an appSetting owin:AutomaticAppStartup with value “false”.
// With this turned off you can still have OWIN apps listening on specific routes by adding routes in
global.asax file using MapOwinPath or MapOwinRoute extensions on RouteTable.Routes
public class Startup
{
// Invoked once at startup to configure your application.
public void Configuration(IAppBuilder builder)
{
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute("Default", "{controller}/{customerID}", new { controller = "Customer",
customerID = RouteParameter.Optional });

config.Formatters.XmlFormatter.UseXmlSerializer = true;
config.Formatters.Remove(config.Formatters.JsonFormatter);
// config.Formatters.JsonFormatter.UseDataContractJsonSerializer = true;

builder.UseWebApi(config);
}
}
}

Isso configura as rotas padrão e usa XmlSerialization em Json por padrão. Adicione outro Middleware para este
pipeline conforme necessário (carregamento de serviços, definições de configuração, arquivos estáticos, etc.).
O ASP.NET Core usa uma abordagem semelhante, mas não depende de OWIN para manipular a entrada. Em vez
disso, isso é feito por meio do método Main de Program.cs (semelhante a aplicativos de console) e Startup é
carregado por lá.

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;

namespace WebApplication2
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}

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


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

Startup deve incluir um método Configure . Em Configure , adicione o middleware necessário ao pipeline. No
exemplo a seguir (com base no modelo de site da Web padrão), vários métodos de extensão são usados para
configurar o pipeline com suporte para:
BrowserLink
Páginas de erro
Arquivos estáticos
ASP.NET Core MVC
Identidade

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)


{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}

app.UseStaticFiles();

app.UseIdentity();

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

O host e o aplicativo foram separados, o que fornece a flexibilidade de mover para uma plataforma diferente no
futuro.
Observação: para obter uma referência mais aprofundada sobre o Startup do ASP.NET Core e middleware,
consulte Startup no ASP.NET Core

Armazenando configurações
O ASP.NET dá suporte ao armazenamento de configurações. Essas configurações são usadas, por exemplo, para
dar suporte ao ambiente no qual os aplicativos foram implantados. Uma prática comum era armazenar todos os
pares chave-valor personalizados na seção <appSettings> do arquivo Web.config:

<appSettings>
<add key="UserName" value="User" />
<add key="Password" value="Password" />
</appSettings>

Aplicativos leem essas configurações usando a coleção ConfigurationManager.AppSettings no namespace


System.Configuration :

string userName = System.Web.Configuration.ConfigurationManager.AppSettings["UserName"];


string password = System.Web.Configuration.ConfigurationManager.AppSettings["Password"];

O ASP.NET Core pode armazenar dados de configuração para o aplicativo em qualquer arquivo e carregá-los
como parte da inicialização de middleware. O arquivo padrão usado em modelos de projeto é appsettings.json:
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
// Here is where you can supply custom configuration settings, Since it is is JSON, everything is
represented as key: value pairs
// Name of section is your choice
"AppConfiguration": {
"UserName": "UserName",
"Password": "Password"
}
}

Carregar esse arquivo em uma instância de IConfiguration dentro de seu aplicativo é feito em Startup.cs:

public Startup(IConfiguration configuration)


{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

O aplicativo lê de Configuration para obter as configurações:

string userName = Configuration.GetSection("AppConfiguration")["UserName"];


string password = Configuration.GetSection("AppConfiguration")["Password"];

Existem extensões para essa abordagem para tornar o processo mais robusto, tais como o uso de DI (injeção de
dependência) para carregar um serviço com esses valores. A abordagem de DI fornece um conjunto fortemente
tipado de objetos de configuração.

// Assume AppConfiguration is a class representing a strongly-typed version of AppConfiguration section


services.Configure<AppConfiguration>(Configuration.GetSection("AppConfiguration"));

Observação: para uma referência mais detalhada sobre configuração do ASP.NET Core, consulte Configuração no
ASP.NET Core.

Injeção de dependência nativa


Uma meta importante ao criar aplicativos escalonáveis e grandes é o acoplamento flexível de componentes e
serviços. A injeção de dependência é uma técnica popular para conseguir isso e é também um componente nativo
do ASP.NET Core.
Em aplicativos ASP.NET, os desenvolvedores contam com uma biblioteca de terceiros para implementar injeção de
dependência. Um biblioteca desse tipo é a Unity, fornecida pelas Diretrizes da Microsoft.
Um exemplo de configuração da injeção de dependência com Unity é a implementação de IDependencyResolver ,
que encapsula uma UnityContainer :
using Microsoft.Practices.Unity;
using System;
using System.Collections.Generic;
using System.Web.Http.Dependencies;

public class UnityResolver : IDependencyResolver


{
protected IUnityContainer container;

public UnityResolver(IUnityContainer container)


{
if (container == null)
{
throw new ArgumentNullException("container");
}
this.container = container;
}

public object GetService(Type serviceType)


{
try
{
return container.Resolve(serviceType);
}
catch (ResolutionFailedException)
{
return null;
}
}

public IEnumerable<object> GetServices(Type serviceType)


{
try
{
return container.ResolveAll(serviceType);
}
catch (ResolutionFailedException)
{
return new List<object>();
}
}

public IDependencyScope BeginScope()


{
var child = container.CreateChildContainer();
return new UnityResolver(child);
}

public void Dispose()


{
Dispose(true);
}

protected virtual void Dispose(bool disposing)


{
container.Dispose();
}
}

Crie uma instância de sua UnityContainer , registre seu serviço e defina o resolvedor de dependência de
HttpConfiguration para a nova instância de UnityResolver para o contêiner:
public static void Register(HttpConfiguration config)
{
var container = new UnityContainer();
container.RegisterType<IProductRepository, ProductRepository>(new HierarchicalLifetimeManager());
config.DependencyResolver = new UnityResolver(container);

// Other Web API configuration not shown.


}

Injete IProductRepository quando necessário:

public class ProductsController : ApiController


{
private IProductRepository _repository;

public ProductsController(IProductRepository repository)


{
_repository = repository;
}

// Other controller methods not shown.


}

Já que a injeção de dependência é parte do ASP.NET Core, você pode adicionar o serviço no método
ConfigureServices de Startup.cs:

public void ConfigureServices(IServiceCollection services)


{
// Add application services.
services.AddTransient<IProductRepository, ProductRepository>();
}

O repositório pode ser injetado em qualquer lugar, como ocorria com a Unity.
Observação: para uma referência detalhada sobre a injeção de dependência no ASP.NET Core, consulte Injeção
de dependência no ASP.NET Core

Servindo arquivos estáticos


Uma parte importante do desenvolvimento da Web é a capacidade de servir ativos estáticos, do lado do cliente.
Os exemplos mais comuns de arquivos estáticos são HTML, CSS, Javascript e imagens. Esses arquivos precisam
ser salvos no local de publicação do aplicativo (ou CDN ) e referenciados para que eles possam ser carregados por
uma solicitação. Esse processo foi alterado no ASP.NET Core.
No ASP.NET, arquivos estáticos são armazenados em vários diretórios e referenciados nas exibições.
No ASP.NET Core, arquivos estáticos são armazenados na "raiz da Web" (<raiz do conteúdo>/wwwroot), a menos
que configurado de outra forma. Os arquivos são carregados no pipeline de solicitação invocando o método de
extensão UseStaticFiles de Startup.Configure :

public void Configure(IApplicationBuilder app)


{
app.UseStaticFiles();
}

Observação: se você usar o .NET Framework como destino, instale o pacote NuGet
Microsoft.AspNetCore.StaticFiles .
Por exemplo, um ativo de imagem na pasta wwwroot/imagens está acessível para o navegador em um local como
http://<app>/images/<imageFileName> .

Observação: para obter uma referência mais aprofundada sobre como servir arquivos estáticos no ASP.NET
Core, veja Trabalhar com arquivos estáticos no ASP.NET Core.

Recursos adicionais
Fazendo a portabilidade de Bibliotecas para o .NET Core
Migrar do ASP.NET Core 1.x para 2.0
04/05/2018 • 13 min to read • Edit Online

Por Scott Addie


Neste artigo, vamos orientá-lo pela atualização de um projeto existente ASP.NET Core 1.x para o ASP.NET Core
2.0. A migração do aplicativo para o ASP.NET Core 2.0 permite que você aproveite muitos novos recursos e
melhorias de desempenho.
Os aplicativos ASP.NET Core 1.x existentes baseiam-se em modelos de projeto específicos à versão. Conforme a
estrutura do ASP.NET Core evolui, os modelos do projeto e o código inicial contido neles também. Além de
atualizar a estrutura ASP.NET Core, você precisa atualizar o código para o aplicativo.

Pré-requisitos
Consulte a Introdução ao ASP.NET Core.

Atualizar TFM (Moniker da Estrutura de Destino)


Projetos direcionados ao .NET Core devem usar o TFM de uma versão maior ou igual ao .NET Core 2.0. Pesquise
o nó <TargetFramework> no arquivo .csproj e substitua seu texto interno por netcoreapp2.0 :

<TargetFramework>netcoreapp2.0</TargetFramework>

Projetos direcionados ao .NET Framework devem usar o TFM de uma versão maior ou igual ao .NET Framework
4.6.1. Pesquise o nó <TargetFramework> no arquivo .csproj e substitua seu texto interno por net461 :

<TargetFramework>net461</TargetFramework>

OBSERVAÇÃO
O .NET Core 2.0 oferece uma área de superfície muito maior do que o .NET Core 1.x. Se você estiver direcionando o .NET
Framework exclusivamente devido a APIs ausentes no .NET Core 1.x, o direcionamento do .NET Core 2.0 provavelmente
funcionará.

Atualizar a versão do SDK do .NET Core em global.json


Se a solução depender de um arquivo global.json para direcionar uma versão específica do SDK do .NET Core,
atualize sua propriedade version para que ela use a versão 2.0 instalada no computador:

{
"sdk": {
"version": "2.0.0"
}
}

Referências do pacote de atualização


O arquivo .csproj em um projeto 1.x lista cada pacote NuGet usado pelo projeto.
Em um projeto ASP.NET Core 2.0 direcionado ao .NET Core 2.0, uma única referência de metapacote no arquivo
.csproj substitui a coleção de pacotes:

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>

Todos os recursos do ASP.NET Core 2.0 e do Entity Framework Core 2.0 são incluídos no metapacote.
Os projetos do ASP.NET Core 2.0 direcionados ao .NET Framework devem continuar a referenciar pacotes NuGet
individuais. Atualize o atributo Version de cada nó <PackageReference /> para 2.0.0.
Por exemplo, esta é a lista de nós <PackageReference /> usados em um projeto ASP.NET Core 2.0 típico
direcionado ao .NET Framework:

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.ViewCompilation" Version="2.0.0"
PrivateAssets="All" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="2.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.0"
PrivateAssets="All" />
</ItemGroup>

Atualizar ferramentas da CLI do .NET Core


No arquivo .csproj, atualize o atributo Version de cada nó <DotNetCliToolReference /> para 2.0.0.
Por exemplo, esta é a lista de ferramentas da CLI usadas em um projeto ASP.NET Core 2.0 típico direcionado ao
.NET Core 2.0:

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

Renomear a propriedade de Fallback de Destino do Pacote


O arquivo .csproj de um projeto 1.x usou um nó PackageTargetFallback e uma variável:

<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>

Renomeie o nó e a variável como AssetTargetFallback :


<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>

Atualizar o método Main em Program.cs


Em projetos 1.x, o método Main de Program.cs era parecido com este:

using System.IO;
using Microsoft.AspNetCore.Hosting;

namespace AspNetCoreDotNetCore1App
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.UseApplicationInsights()
.Build();

host.Run();
}
}
}

Em projetos 2.0, o método Main de Program.cs foi simplificado:

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;

namespace AspNetCoreDotNetCore2App
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}

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


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

A adoção desse novo padrão do 2.0 é altamente recomendada e é necessária para que os recursos de produto
como as Migrações do Entity Framework (EF ) Core funcionem. Por exemplo, a execução de Update-Database na
janela do Console do Gerenciador de Pacotes ou de dotnet ef database update na linha de comando (em projetos
convertidos no ASP.NET Core 2.0) gera o seguinte erro:

Unable to create an object of type '<Context>'. Add an implementation of


'IDesignTimeDbContextFactory<Context>' to the project, or see https://go.microsoft.com/fwlink/?linkid=851728
for additional patterns supported at design time.
Adicionar provedores de configuração
Nos projetos 1.x, a adição de provedores de configuração em um aplicativo foi realizada por meio do construtor
Startup . As etapas incluíram a criação de uma instância de ConfigurationBuilder , o carregamento de provedores
aplicáveis (variáveis de ambiente, configurações do aplicativo, etc.) e a inicialização de um membro de
IConfigurationRoot .

public Startup(IHostingEnvironment env)


{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

if (env.IsDevelopment())
{
builder.AddUserSecrets<Startup>();
}

builder.AddEnvironmentVariables();
Configuration = builder.Build();
}

public IConfigurationRoot Configuration { get; }

O exemplo anterior carrega o membro Configuration com definições de configuração do appsettings.json, bem
como qualquer arquivo appsettings.<EnvironmentName>.json correspondente à propriedade
IHostingEnvironment.EnvironmentName . Estes arquivos estão localizados no mesmo caminho que Startup.cs.

Nos projetos 2.0, o código de configuração clichê inerente aos projetos 1.x é executado de modo oculto. Por
exemplo, as variáveis de ambiente e as configurações do aplicativo são carregadas na inicialização. O código
Startup.cs equivalente é reduzido à inicialização IConfiguration com a instância injetada:

public Startup(IConfiguration configuration)


{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

Para remover os provedores padrão adicionados por WebHostBuilder.CreateDefaultBuilder , invoque o método


Clear na propriedade IConfigurationBuilder.Sources dentro de ConfigureAppConfiguration . Para adicionar os
provedores de volta, utilize o método ConfigureAppConfiguration em Program.cs:

public static void Main(string[] args)


{
BuildWebHost(args).Run();
}

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


WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureAppConfiguration((hostContext, config) =>
{
// delete all default configuration providers
config.Sources.Clear();
config.AddJsonFile("myconfig.json", optional: true);
})
.Build();
A configuração usada pelo método CreateDefaultBuilder no trecho de código anterior pode ser vista aqui.
Para obter mais informações, consulte Configuração no ASP.NET Core.

Mover código de inicialização do banco de dados


Em projetos do 1.x usando o EF Core 1.x, um comando como dotnet ef migrations add faz o seguinte:
1. Cria uma instância Startup
2. Invoca o método ConfigureServices para registrar todos os serviços com injeção de dependência (incluindo
tipos DbContext )
3. Executa suas tarefas de requisito
Em projetos do 2.0 usando o EF Core 2.0, o Program.BuildWebHost é chamado para obter os serviços do aplicativo.
Ao contrário do 1.x, isso tem o efeito colateral adicional de chamar o Startup.Configure . Caso seu aplicativo do 1.x
tenha chamado o código de inicialização do banco de dados no seu método Configure , problemas inesperados
poderão ocorrer. Por exemplo, se o banco de dados não existir ainda, o código de propagação será executado antes
da execução do comando EF Core Migrations. Esse problema fará com que um comando
dotnet ef migrations list falhe se o banco de dados ainda não existir.

Considere o seguinte código de inicialização de propagação do 1.x no método Configure de Startup.cs:

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

SeedData.Initialize(app.ApplicationServices);

Em projetos do 2.0, mova a chamada SeedData.Initialize para o método Main do Program.cs:

var host = BuildWebHost(args);

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


{
var services = scope.ServiceProvider;

try
{
// 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();

A partir do 2.0, é uma prática inadequada realizar qualquer ação no BuildWebHost , exceto compilação e
configuração do host da Web. Tudo relacionado à execução do aplicativo deve ser tratado fora do BuildWebHost —,
normalmente no método Main do Program.cs.

Examinar a configuração de compilação do Modo de Exibição do Razor


Um tempo de inicialização mais rápido do aplicativo e pacotes publicados menores são de extrema importância
para você. Por esses motivos, a opção Compilação de exibição do Razor é habilitada por padrão no ASP.NET Core
2.0.
A configuração da propriedade MvcRazorCompileOnPublish como verdadeiro não é mais necessária. A menos que
você esteja desabilitando a compilação de exibição, a propriedade poderá ser removida do arquivo .csproj.
Ao direcionar o .NET Framework, você ainda precisa referenciar explicitamente o pacote NuGet
Microsoft.AspNetCore.Mvc.Razor.ViewCompilation no arquivo .csproj:

<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.ViewCompilation" Version="2.0.0" PrivateAssets="All"


/>

Depender dos recursos “Light-Up” do Application Insights


A instalação fácil da instrumentação de desempenho do aplicativo é importante. Agora, você pode contar com os
novos recursos “light-up” do Application Insights disponíveis nas ferramentas do Visual Studio 2017.
Os projetos do ASP.NET Core 1.1 criados no Visual Studio 2017 adicionaram o Application Insights por padrão.
Se não estiver usando o SDK do Application Insights diretamente, fora de Program.cs e Startup.cs, siga estas
etapas:
1. Se você estiver direcionando ao .NET Core, remova o nó <PackageReference /> seguinte do arquivo .csproj:

<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.0" />

2. Se você estiver direcionando ao .NET Core, remova a invocação do método de extensão


UseApplicationInsights de Program.cs:

public static void Main(string[] args)


{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.UseApplicationInsights()
.Build();

host.Run();
}

3. Remova a chamada à API do lado do cliente do Application Insights de _Layout.cshtml. Ela inclui as duas
seguintes linhas de código:

@inject Microsoft.ApplicationInsights.AspNetCore.JavaScriptSnippet JavaScriptSnippet


@Html.Raw(JavaScriptSnippet.FullScript)

Se você estiver usando o SDK do Application Insights diretamente, continue fazendo isso. O metapacote do 2.0
inclui a última versão do Application Insights. Portanto, um erro de downgrade do pacote será exibido se você
estiver referenciando uma versão mais antiga.

Adotar melhorias de autenticação/identidade


O ASP.NET Core 2.0 tem um novo modelo de autenticação e uma série de alterações significativas no ASP.NET
Core Identity. Se você criar o projeto com a opção Contas de Usuário Individuais habilitada ou se adicionar
manualmente a autenticação ou a identidade, consulte Migrar a autenticação e a identidade para o ASP.NET Core
2.0.

Recursos adicionais
Últimas alterações no ASP.NET Core 2.0
Migrar de autenticação e identidade para o ASP.NET
2.0 de núcleo
04/05/2018 • 13 min to read • Edit Online

Por Scott Addie e Kung Hao


ASP.NET Core 2.0 tem um novo modelo para autenticação e identidade que simplifica a configuração por meio de
serviços. Aplicativos de 1. x do ASP.NET Core que usam autenticação ou identidade podem ser atualizados para
usar o novo modelo conforme descrito abaixo.

Middleware de autenticação e serviços


Em projetos de 1. x, a autenticação é configurada por meio do middleware. Um método de middleware é invocado
para cada esquema de autenticação que você deseja dar suporte.
O exemplo a seguir 1. x configura a autenticação do Facebook com identidade na Startup.cs:

public void ConfigureServices(IServiceCollection services)


{
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
}

public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)


{
app.UseIdentity();
app.UseFacebookAuthentication(new FacebookOptions {
AppId = Configuration["auth:facebook:appid"],
AppSecret = Configuration["auth:facebook:appsecret"]
});
}

Em 2.0 projetos, a autenticação é configurada por meio de serviços. Cada esquema de autenticação é registrada
no ConfigureServices método Startup.cs. O UseIdentity método é substituído pelo UseAuthentication .
O exemplo a seguir 2.0 configura a autenticação do Facebook com identidade na Startup.cs:

public void ConfigureServices(IServiceCollection services)


{
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();

// If you want to tweak Identity cookies, they're no longer part of IdentityOptions.


services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/LogIn");
services.AddAuthentication()
.AddFacebook(options =>
{
options.AppId = Configuration["auth:facebook:appid"];
options.AppSecret = Configuration["auth:facebook:appsecret"];
});
}

public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory) {


app.UseAuthentication();
}
O UseAuthentication método adiciona um componente de middleware de autenticação único que é responsável
pela autenticação automática e o tratamento de solicitações de autenticação remota. Ele substitui todos os
componentes de middleware individuais com um componente de middleware único, comuns.
Abaixo estão 2.0 instruções de migração de cada esquema de autenticação principal.
Autenticação baseada em cookie
Selecione uma das duas opções abaixo e faça as alterações necessárias na Startup.cs:
1. Usar cookies com identidade
Substituir UseIdentity com UseAuthentication no Configure método:

app.UseAuthentication();

Invocar o AddIdentity método o ConfigureServices método para adicionar os serviços de


autenticação de cookie.
Opcionalmente, invoque o ConfigureApplicationCookie ou ConfigureExternalCookie método o
ConfigureServices método para ajustar as configurações de cookie de identidade.

services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/LogIn");

2. Usar cookies sem identidade


Substitua o UseCookieAuthentication chamada do método de Configure método com
UseAuthentication :

app.UseAuthentication();

Invocar o AddAuthentication e AddCookie métodos de ConfigureServices método:

// If you don't want the cookie to be automatically authenticated and assigned to


HttpContext.User,
// remove the CookieAuthenticationDefaults.AuthenticationScheme parameter passed to
AddAuthentication.
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Account/LogIn";
options.LogoutPath = "/Account/LogOff";
});

Autenticação do portador do JWT


Faça as seguintes alterações em Startup.cs:
Substitua o UseJwtBearerAuthentication chamada do método de Configure método com
UseAuthentication :

app.UseAuthentication();
Invocar o AddJwtBearer método o ConfigureServices método:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Audience = "http://localhost:5001/";
options.Authority = "http://localhost:5000/";
});

Este trecho de código não usa a identidade, portanto o esquema padrão deve ser definido passando
JwtBearerDefaults.AuthenticationScheme para o AddAuthentication método.

Autenticação do OpenID conectar (OIDC )


Faça as seguintes alterações em Startup.cs:
Substitua o UseOpenIdConnectAuthentication chamada do método de Configure método com
UseAuthentication :

app.UseAuthentication();

Invocar o AddOpenIdConnect método o ConfigureServices método:

services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.Authority = Configuration["auth:oidc:authority"];
options.ClientId = Configuration["auth:oidc:clientid"];
});

autenticação do Facebook
Faça as seguintes alterações em Startup.cs:
Substitua o UseFacebookAuthentication chamada do método de Configure método com
UseAuthentication :

app.UseAuthentication();

Invocar o AddFacebook método o ConfigureServices método:

services.AddAuthentication()
.AddFacebook(options =>
{
options.AppId = Configuration["auth:facebook:appid"];
options.AppSecret = Configuration["auth:facebook:appsecret"];
});

Autenticação do Google
Faça as seguintes alterações em Startup.cs:
Substitua o UseGoogleAuthentication chamada do método de Configure método com UseAuthentication :
app.UseAuthentication();

Invocar o AddGoogle método o ConfigureServices método:

services.AddAuthentication()
.AddGoogle(options =>
{
options.ClientId = Configuration["auth:google:clientid"];
options.ClientSecret = Configuration["auth:google:clientsecret"];
});

Autenticação de Account da Microsoft


Faça as seguintes alterações em Startup.cs:
Substitua o UseMicrosoftAccountAuthentication chamada do método de Configure método com
UseAuthentication :

app.UseAuthentication();

Invocar o AddMicrosoftAccount método o ConfigureServices método:

services.AddAuthentication()
.AddMicrosoftAccount(options =>
{
options.ClientId = Configuration["auth:microsoft:clientid"];
options.ClientSecret = Configuration["auth:microsoft:clientsecret"];
});

Autenticação do Twitter
Faça as seguintes alterações em Startup.cs:
Substitua o UseTwitterAuthentication chamada do método de Configure método com UseAuthentication :

app.UseAuthentication();

Invocar o AddTwitter método o ConfigureServices método:

services.AddAuthentication()
.AddTwitter(options =>
{
options.ConsumerKey = Configuration["auth:twitter:consumerkey"];
options.ConsumerSecret = Configuration["auth:twitter:consumersecret"];
});

Esquemas de autenticação padrão de configuração


Em 1. x, o AutomaticAuthenticate e AutomaticChallenge propriedades do AuthenticationOptions classe base foram
se destina a ser definido em um esquema de autenticação. Não havia uma maneira válida para impor isso.
No 2.0, essas duas propriedades foram removidas como propriedades individuais AuthenticationOptions
instância. Eles podem ser configurados no AddAuthentication chamada de método do ConfigureServices método
Startup.cs:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);

No trecho de código anterior, o esquema padrão é definido como


CookieAuthenticationDefaults.AuthenticationScheme ("Cookies").

Como alternativa, use uma versão sobrecarregada do AddAuthentication método para definir mais de uma
propriedade. No exemplo a seguir método sobrecarregado, o esquema padrão é definido como
CookieAuthenticationDefaults.AuthenticationScheme . O esquema de autenticação também pode ser especificado
dentro de sua individuais [Authorize] atributos ou as políticas de autorização.

services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
});

Defina um esquema padrão 2.0 se uma das seguintes condições for verdadeira:
Você deseja que o usuário a ser conectado automaticamente
Você usa o [Authorize] políticas de autorização ou atributo sem especificar esquemas

Uma exceção a essa regra é o AddIdentity método. Este método adiciona cookies para você e define o padrão
autenticar e desafio esquemas para o cookie do aplicativo IdentityConstants.ApplicationScheme . Além disso, ele
define o esquema de entrada padrão para o cookie externo IdentityConstants.ExternalScheme .

Use as extensões de autenticação HttpContext


O IAuthenticationManager interface é o ponto de entrada principal para o sistema de autenticação de 1. x. Ele foi
substituído por um novo conjunto de HttpContext métodos de extensão no Microsoft.AspNetCore.Authentication
namespace.
Por exemplo, 1. x projetos referência um Authentication propriedade:

// Clear the existing external cookie to ensure a clean login process


await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);

Em 2.0 projetos, importar o Microsoft.AspNetCore.Authentication namespace e exclua o Authentication


referências de propriedade:

// Clear the existing external cookie to ensure a clean login process


await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

Autenticação do Windows (http. sys / IISIntegration)


Há duas variações da autenticação do Windows:
1. O host só permite que os usuários autenticados
2. O host permite que ambos anônimos e usuários autenticados
A primeira descrita acima é afetada pelas 2.0 alterações.
A segunda descrita acima é afetada pelas 2.0 alterações. Por exemplo, você pode ser permitir que usuários
anônimos em seu aplicativo no IIS ou HTTP.sys mas autorizar usuários no nível do controlador de camada. Nesse
cenário, defina o esquema padrão IISDefaults.AuthenticationScheme no ConfigureServices método Startup.cs:

services.AddAuthentication(IISDefaults.AuthenticationScheme);

Falha ao definir o esquema padrão adequadamente impede que a solicitação de autorização ao desafio de
trabalho.

Instâncias de IdentityCookieOptions
Um efeito colateral das 2.0 alterações é o switch usando nomeados opções em vez de instâncias de opções do
cookie. A capacidade de personalizar os nomes de esquema de cookie de identidade é removida.
Por exemplo, 1. x projetos usam injeção de construtor para passar um IdentityCookieOptions parâmetro em
AccountController.cs. O esquema de autenticação de cookie externa é acessado da instância fornecida:

public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IOptions<IdentityCookieOptions> identityCookieOptions,
IEmailSender emailSender,
ISmsSender smsSender,
ILoggerFactory loggerFactory)
{
_userManager = userManager;
_signInManager = signInManager;
_externalCookieScheme = identityCookieOptions.Value.ExternalCookieAuthenticationScheme;
_emailSender = emailSender;
_smsSender = smsSender;
_logger = loggerFactory.CreateLogger<AccountController>();
}

A injeção de construtor mencionados acima se torna desnecessária em 2.0 projetos e o _externalCookieScheme


campo pode ser excluído:

public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IEmailSender emailSender,
ISmsSender smsSender,
ILoggerFactory loggerFactory)
{
_userManager = userManager;
_signInManager = signInManager;
_emailSender = emailSender;
_smsSender = smsSender;
_logger = loggerFactory.CreateLogger<AccountController>();
}

O IdentityConstants.ExternalScheme constante pode ser usada diretamente:

// Clear the existing external cookie to ensure a clean login process


await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

Adicionar propriedades de navegação IdentityUser POCO


As propriedades de navegação principal do Entity Framework (EF ) da base de IdentityUser POCO (objeto Plain
Old CLR ) foram removidas. Seu projeto de 1. x usado essas propriedades, adicione-as manualmente ao projeto
2.0:

/// <summary>
/// Navigation property for the roles this user belongs to.
/// </summary>
public virtual ICollection<IdentityUserRole<int>> Roles { get; } = new List<IdentityUserRole<int>>();

/// <summary>
/// Navigation property for the claims this user possesses.
/// </summary>
public virtual ICollection<IdentityUserClaim<int>> Claims { get; } = new List<IdentityUserClaim<int>>();

/// <summary>
/// Navigation property for this users login accounts.
/// </summary>
public virtual ICollection<IdentityUserLogin<int>> Logins { get; } = new List<IdentityUserLogin<int>>();

Para evitar que chaves estrangeiras duplicadas ao executar migrações de núcleo EF, adicione o seguinte ao seu
IdentityDbContext classe OnModelCreating método (após o base.OnModelCreating(); chamar ):

protected override void OnModelCreating(ModelBuilder builder)


{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);

builder.Entity<ApplicationUser>()
.HasMany(e => e.Claims)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);

builder.Entity<ApplicationUser>()
.HasMany(e => e.Logins)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);

builder.Entity<ApplicationUser>()
.HasMany(e => e.Roles)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
}

Substituir GetExternalAuthenticationSchemes
O método síncrono GetExternalAuthenticationSchemes foi removido em favor de uma versão assíncrona. 1. x
projetos têm o seguinte código ManageController.cs:

var otherLogins = _signInManager.GetExternalAuthenticationSchemes().Where(auth => userLogins.All(ul =>


auth.AuthenticationScheme != ul.LoginProvider)).ToList();

Esse método é exibida no cshtml muito:


var loginProviders = SignInManager.GetExternalAuthenticationSchemes().ToList();
<div>
<p>
@foreach (var provider in loginProviders)
{
<button type="submit" class="btn btn-default" name="provider"
value="@provider.AuthenticationScheme" title="Log in using your @provider.DisplayName
account">@provider.AuthenticationScheme</button>
}
</p>
</div>
</form>
}

Em 2.0 projetos, use o GetExternalAuthenticationSchemesAsync método:

var schemes = await _signInManager.GetExternalAuthenticationSchemesAsync();


var otherLogins = schemes.Where(auth => userLogins.All(ul => auth.Name != ul.LoginProvider)).ToList();

No cshtml, o AuthenticationScheme propriedade acessada o foreach loop muda para Name :

var loginProviders = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList();


<div>
<p>
@foreach (var provider in loginProviders)
{
<button type="submit" class="btn btn-default" name="provider" value="@provider.Name"
title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
</p>
</div>
</form>
}

Alteração da propriedade ManageLoginsViewModel


Um ManageLoginsViewModel objeto é usado no ManageLogins ação de ManageController.cs. Em do 1. x projetos, o
objeto OtherLogins é do tipo de retorno de propriedade IList<AuthenticationDescription> . Esse tipo de retorno
requer uma importação de Microsoft.AspNetCore.Http.Authentication :

using System.Collections.Generic;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Identity;

namespace AspNetCoreDotNetCore1App.Models.ManageViewModels
{
public class ManageLoginsViewModel
{
public IList<UserLoginInfo> CurrentLogins { get; set; }

public IList<AuthenticationDescription> OtherLogins { get; set; }


}
}

Em 2.0 projetos, o tipo de retorno é alterado para IList<AuthenticationScheme> . Esse novo tipo de retorno requer
substituir o Microsoft.AspNetCore.Http.Authentication importar com um Microsoft.AspNetCore.Authentication
importar.
using System.Collections.Generic;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;

namespace AspNetCoreDotNetCore2App.Models.ManageViewModels
{
public class ManageLoginsViewModel
{
public IList<UserLoginInfo> CurrentLogins { get; set; }

public IList<AuthenticationScheme> OtherLogins { get; set; }


}
}

Recursos adicionais
Para obter detalhes adicionais e discussão, consulte o discussões sobre autenticação 2.0 problema no GitHub.
Novidades do ASP.NET Core 2.0
28/02/2018 • 12 min to read • Edit Online

Este artigo destaca as alterações mais significativas no ASP.NET Core 2.0, com links para a documentação
relevante.

Páginas do Razor
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.
Para obter mais informações, consulte a introdução e o tutorial:
Introdução a Páginas do Razor
Começando com Páginas do Razor

Metapacote do ASP.NET Core


Um novo metapacote do ASP.NET Core inclui todos os pacotes feitos e com suporte pelas equipes do ASP.NET
Core e do Entity Framework Core, juntamente com as respectivas dependências internas e de terceiros. Você não
precisa mais escolher recursos individuais do ASP.NET Core por pacote. Todos os recursos estão incluídos no
pacote Microsoft.AspNetCore.All. Os modelos padrão usam este pacote.
Para obter mais informações, consulte Metapacote do Microsoft.AspNetCore.All para ASP.NET Core 2.0.

Repositório de tempo de execução


Aplicativos que usam o metapacote Microsoft.AspNetCore.All aproveitam automaticamente o novo repositório de
tempo de execução do .NET Core. O repositório contém todos os ativos de tempo de execução necessários para
executar aplicativos ASP.NET Core 2.0. Quando você usa o metapacote Microsoft.AspNetCore.All , nenhum ativo
dos pacotes NuGet do ASP.NET Core referenciados são implantados com o aplicativo porque eles já estão no
sistema de destino. Os ativos no repositório de tempo de execução também são pré-compilados para melhorar o
tempo de inicialização do aplicativo.
Para obter mais informações, consulte Repositório de tempo de execução

.NET Standard 2.0


Os pacotes do ASP.NET Core 2.0 são direcionados ao .NET Standard 2.0. Os pacotes podem ser referenciados por
outras bibliotecas do .NET Standard 2.0 e podem ser executados em implementações em conformidade com o
.NET Standard 2.0, incluindo o .NET Core 2.0 e o .NET Framework 4.6.1.
O metapacote Microsoft.AspNetCore.All aborda apenas o .Net Core 2.0 porque ele foi projetado para ser utilizado
com o repositório de tempo de execução do .Net Core 2.0.

Atualização da configuração
Uma instância de IConfiguration é adicionada ao contêiner de serviços por padrão no ASP.NET Core 2.0. O
IConfiguration no contêiner de serviços torna mais fácil para os aplicativos recuperarem os valores de
configuração do contêiner.
Para obter informações sobre o status da documentação planejada, consulte o problema do GitHub.
Atualização de registro em log
No ASP.NET Core 2.0, o log será incorporado no sistema de DI (injeção de dependência) por padrão. Você
adiciona provedores e configura a filtragem no arquivo Program.cs em vez de usar o arquivo Startup.cs. E o
ILoggerFactory padrão dá suporte à filtragem de forma que lhe permite usar uma abordagem flexível para
filtragem entre provedores e filtragem específica do provedor.
Para obter mais informações, consulte Introdução ao registro em log.

Atualização de autenticação
Um novo modelo de autenticação torna mais fácil configurar a autenticação para um aplicativo usando a DI.
Novos modelos estão disponíveis para configurar a autenticação para aplicativos Web e APIs Web usando [Azure
AD B2C ] (https://azure.microsoft.com/services/active-directory-b2c/).
Para obter informações sobre o status da documentação planejada, consulte o problema do GitHub.

Atualização de identidade
Facilitamos a criação de APIs Web seguras usando a identidade do ASP.NET Core 2.0. Você pode adquirir tokens
de acesso para acessar suas APIs Web usando a MSAL (Biblioteca de Autenticação da Microsoft).
Para obter mais informações sobre alterações de autenticação no 2.0, consulte os seguintes recursos:
Confirmação de conta e de recuperação de senha no ASP.NET Core
Habilitar a geração de código QR para aplicativos de autenticação no ASP.NET Core
Migrando Autenticação e Identidade para o ASP.NET Core 2.0

Modelos do SPA
Modelos de projeto de SPA (aplicativo de página único) para Angular, Aurelia, Knockout.js, React.js e React.js com
Redux estão disponíveis. O modelo Angular foi atualizado para Angular 4. Os modelos Angular e React estão
disponíveis por padrão. Para obter informações sobre como obter os outros modelos, consulte Criar um novo
projeto de SPA. Para obter informações sobre como criar um SPA no ASP.NET Core, consulte Usando
JavaScriptServices para criar aplicativos de página única.

Melhorias do Kestrel
O servidor Web Kestrel tem novos recursos que o tornam mais adequado como um servidor voltado para a
Internet. Uma série de opções de configuração de restrição de servidor serão adicionadas na nova propriedade
Limits da classe KestrelServerOptions . Adicione limites para o seguinte:

Número máximo de conexões de cliente


Tamanho máximo do corpo da solicitação
Taxa de dados mínima do corpo da solicitação
Para obter mais informações, consulte Implementação do servidor Web Kestrel no ASP.NET Core.

WebListener renomeado para HTTP.sys


Os pacotes Microsoft.AspNetCore.Server.WebListener e Microsoft.Net.Http.Server foram mesclados em um novo
pacote Microsoft.AspNetCore.Server.HttpSys . Os namespaces foram atualizados para corresponderem.
Para obter mais informações, consulte Implementação do servidor Web HTTP.sys no ASP.NET Core.
Suporte aprimorado a cabeçalho HTTP
Ao usar o MVC para transmitir um FileStreamResult ou um FileContentResult , agora você tem a opção de
definir uma ETag ou uma data LastModified no conteúdo que você transmitir. Você pode definir esses valores no
conteúdo retornado com código semelhante ao seguinte:

var data = Encoding.UTF8.GetBytes("This is a sample text from a binary array");


var entityTag = new EntityTagHeaderValue("\"MyCalculatedEtagValue\"");
return File(data, "text/plain", "downloadName.txt", lastModified: DateTime.UtcNow.AddSeconds(-5), entityTag:
entityTag);

O arquivo retornado para os visitantes será decorado com os cabeçalhos HTTP apropriados para os valores ETag
e LastModified .
Se um visitante de aplicativo solicita o conteúdo com um cabeçalho de solicitação de intervalo, o ASP.NET
reconhece isso e manipula esse cabeçalho. Se o conteúdo solicitado puder ser parcialmente entregue, o ASP.NET
ignorará adequadamente e retornará apenas o conjunto de bytes solicitado. Você não precisa gravar nenhum
manipulador especial em seus métodos para adaptar ou manipular esse recurso; ele é manipulado
automaticamente para você.

Inicialização de hospedagem e o Application Insights


Ambientes de hospedagem podem injetar dependências de pacote extras e executar código durante a inicialização
do aplicativo, sem que o aplicativo precise tomar uma dependência explicitamente ou chamar algum método. Esse
recurso pode ser usado para habilitar determinados ambientes para recursos de "esclarecimento" exclusivos para
esse ambiente sem que o aplicativo precise saber antecipadamente.
No ASP.NET Core 2.0, esse recurso é usado para habilitar o diagnóstico do Application Insights automaticamente
durante a depuração no Visual Studio e (depois de optar por isto) quando em execução nos Serviços de
Aplicativos do Azure. Como resultado, os modelos de projeto não adicionam mais código e pacotes do Application
Insights por padrão.
Para obter informações sobre o status da documentação planejada, consulte o problema do GitHub.

Uso automático de tokens antifalsificação


O ASP.NET Core sempre ajudou a fazer a codificação HTML de seu conteúdo por padrão, mas com a nova versão,
estamos dando um passo adicional para ajudar a impedir ataques de XSRF (falsificação de solicitação entre sites).
O ASP.NET Core agora emitirá tokens antifalsificação por padrão e os validará em ações de POST do formulário e
em páginas sem configuração adicional.
Para obter mais informações, consulte Impedindo ataques de falsificação de solicitação entre sites (CSRF/XSRF )
no ASP.NET Core.

Pré-compilação automática
A pré-compilação da exibição do Razor é habilitada durante a publicação por padrão, reduzindo o tamanho da
saída de publicação e o tempo de inicialização do aplicativo.

Suporte ao Razor para C# 7.1


O mecanismo de exibição Razor foi atualizado para funcionar com o novo compilador Roslyn. Isso inclui suporte
para recursos do C# 7.1 como expressões padrão, nomes de tupla inferidos e correspondência de padrões com
genéricos. Para usar o C# 7.1 em seu projeto, adicione a seguinte propriedade no arquivo de projeto e, em
seguida, recarregue a solução:
<LangVersion>latest</LangVersion>

Para obter informações sobre o status dos recursos do C# 7.1, consulte o repositório GitHub do Roslyn.

Outras atualizações de documentação para 2.0


Perfis de publicação do Visual Studio para a implantação do aplicativo ASP.NET Core
Gerenciamento de chaves
Configurando a autenticação do Facebook
Configurando a autenticação do Twitter
Configurando a autenticação do Google
Configurando a autenticação da Conta da Microsoft

Diretrizes de migração
Para obter diretrizes sobre como migrar aplicativos ASP.NET Core 1.x para o ASP.NET Core 2.0, consulte os
seguintes recursos:
Migrando do ASP.NET Core 1.x para o ASP.NET Core 2.0
Migrando Autenticação e Identidade para o ASP.NET Core 2.0

Informações adicionais
Para obter uma lista de alterações, consulte as Notas de versão do ASP.NET Core 2.0.
Para se conectar ao progresso e aos planos da equipe de desenvolvimento do ASP.NET Core, fique ligado no
ASP.NET Community Standup.
Novidades do ASP.NET Core 1.1
08/02/2018 • 1 min to read • Edit Online

O ASP.NET Core 1.1 inclui os seguintes novos recursos:


Middleware de regravação de URL
Middleware de Cache de Resposta
Componentes de exibição como auxiliares de marcação
Middleware como filtros MVC
Provedor de TempData baseado em cookie
Provedor de logs do Serviço de Aplicativo do Azure
Provedor de configuração do Azure Key Vault
Repositórios de chaves da Proteção de Dados de armazenamento do Azure e do Redis
Servidor WebListener para Windows
Suporte a WebSockets

Escolhendo entre as versões 1.0 e 1.1 do ASP.NET Core


O ASP.NET Core 1.1 tem mais recursos do que o 1.0. Em geral, recomendamos o uso da última versão.

Informações adicionais
Notas de versão do ASP.NET Core 1.1.0
Para se conectar ao progresso e aos planos da equipe de desenvolvimento do ASP.NET Core, fique ligado no
ASP.NET Community Standup.

You might also like