Professional Documents
Culture Documents
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
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.
3. Execute o aplicativo.
Use os seguintes comandos para executar o aplicativo:
cd aspnetcoreapp
dotnet run
@page
@model AboutModel
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"]</h2>
<h3>@Model.Message</h3>
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
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();
}
@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
using Microsoft.AspNetCore.Mvc.RazorPages;
using System;
namespace RazorPagesIntro.Pages
{
public class IndexModel2 : PageModel
{
public string Message { get; private set; } = "PageModel in C#";
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:
/Pages/Index.cshtml / ou /Index
/Pages/Contact.cshtml /Contact
/Pages/Store/Contact.cshtml /Store/Contact
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.
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; }
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; }
}
}
using Microsoft.EntityFrameworkCore;
namespace RazorPagesContacts.Data
{
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions options)
: base(options)
{
}
@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>
namespace RazorPagesContacts.Pages
{
public class CreateModel : PageModel
{
private readonly AppDbContext _db;
[BindProperty]
public Customer Customer { get; set; }
_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");
}
[BindProperty]
public Customer Customer { get; set; }
_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)]
@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>
namespace RazorPagesContacts.Pages
{
public class IndexModel : PageModel
{
private readonly AppDbContext _db;
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:
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";
}
<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;
[BindProperty]
public Customer Customer { get; set; }
if (Customer == null)
{
return RedirectToPage("/Index");
}
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:
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 .
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.
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 ).
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);
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.AllowMappingHeadRequestsToGetHandler = true;
});
<!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
@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;
@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>
@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>
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
return 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
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;
[TempData]
public string Message { get; set; }
[BindProperty]
public Customer Customer { get; set; }
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
Message = $"Customer {Customer.Name} added";
return RedirectToPage("./Index");
}
}
<h3>Msg: @Model.Message</h3>
[TempData]
public string Message { get; set; }
<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;
[BindProperty]
public Customer Customer { get; set; }
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
return RedirectToPage("/Index");
}
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 .
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:
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();
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:
POST /api/todo Adicionar um novo item Item de tarefas pendentes Item de tarefas pendentes
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"]
using Microsoft.EntityFrameworkCore;
namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
}
}
namespace TodoApi
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));
services.AddMvc();
}
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 .
namespace TodoApi.Controllers
{
[Route("api/[controller]")]
public class TodoController : Controller
{
private readonly TodoContext _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.
[HttpGet]
public IEnumerable<TodoItem> GetAll()
{
return _context.TodoItems.ToList();
}
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 :
"{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 .
[HttpPost]
public IActionResult Create([FromBody] TodoItem item)
{
if (item == null)
{
return BadRequest();
}
_context.TodoItems.Add(item);
_context.SaveChanges();
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 :
{
"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();
}
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();
}
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:
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
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
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
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.
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.
appsettings.json Configuraçã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.
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; }
}
}
services.AddDbContext<MovieContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MovieContext")));
services.AddMvc();
}
Install-Package Microsoft.VisualStudio.Web.CodeGeneration.Design
Add-Migration Initial
Update-Database
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:
PARÂMETRO DESCRIÇÃO
-m O nome do modelo.
Testar o aplicativo
Executar o aplicativo e acrescentar /Movies à URL no navegador ( http://localhost:port/movies ).
Teste o link Criar.
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:
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
@{
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>
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>
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;
[BindProperty]
public Movie Movie { get; set; }
_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:
_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:
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.
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.
}
namespace RazorPagesMovie
{
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);
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();
}
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: 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
using System;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { 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;
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?}"
_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 ):
[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.
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
A primeira linha do método OnGetAsync cria uma consulta LINQ para selecionar os filmes:
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.
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:
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.
@{
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>
<table class="table">
<thead>
@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>
@{
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");}
}
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"
},
Add-Migration Rating
Update-Database
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.
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 .
[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; }
}
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.
[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:
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:
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.
[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
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.
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.
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;
if (property != null)
{
var displayAttribute =
property.GetCustomAttribute(typeof(DisplayAttribute)) as DisplayAttribute;
if (displayAttribute != null)
{
fieldDisplayName = $"{displayAttribute.Name} ";
}
}
if (formFile.ContentType.ToLower() != "text/plain")
{
modelState.AddModelError(formFile.Name,
$"The {fieldDisplayName}file ({fileName}) must be a text file.");
}
return string.Empty;
}
}
}
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.
namespace RazorPagesMovie.Models
{
public class Schedule
{
public int ID { get; set; }
public string Title { 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)
{
}
Add-Migration AddScheduleTable
Update-Database
@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;
[BindProperty]
public FileUpload FileUpload { get; set; }
var publicScheduleData =
await FileHelpers.ProcessFormFile(FileUpload.UploadPublicSchedule, ModelState);
var privateScheduleData =
await FileHelpers.ProcessFormFile(FileUpload.UploadPrivateSchedule, ModelState);
return RedirectToPage("./Index");
}
}
}
[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:
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:
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);
_context.Schedule.Add(schedule);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
@{
ViewData["Title"] = "Delete Schedule";
}
<h2>Delete Schedule</h2>
<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;
[BindProperty]
public Schedule Schedule { get; set; }
if (Schedule == null)
{
return NotFound();
}
return Page();
}
if (Schedule != null)
{
_context.Schedule.Remove(Schedule);
await _context.SaveChangesAsync();
}
return RedirectToPage("./Index");
}
}
}
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.
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:
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
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:
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
namespace MvcMovie.Controllers
{
public class HelloWorldController : Controller
{
//
// GET: /HelloWorld/
//
// GET: /HelloWorld/Welcome/
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]
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:
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
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>
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 .
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) .
<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>© 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>
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";
}
Você os tornará ligeiramente diferentes para que possa ver qual parte do código altera qual parte do aplicativo.
@{
ViewData["Title"] = "Movie List";
}
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:
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).
using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;
namespace MvcMovie.Controllers
{
public class HelloWorldController : Controller
{
public IActionResult Index()
{
return View();
}
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>
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
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; }
}
}
SqlException: Cannot open database "MvcMovieContext-<GUID removed>" requested by the login. The login failed.
Login failed for user 'Rick'.
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.
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):
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; }
}
}
Crie duas mais entradas de filme adicionais. Experimente os links Editar, Detalhes e Excluir, que estão todos
funcionais.
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:
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.
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
return View(movie);
}
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.
Se for encontrado um filme, uma instância do modelo Movie será passada para a exibição Details :
return View(movie);
@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
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.
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.
}
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);
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();
}
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
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; }
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;
using System;
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { 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.
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>
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
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();
}
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.
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.
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
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
A primeira linha do método de ação Index cria uma consulta LINQ para selecionar os filmes:
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.
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (!String.IsNullOrEmpty(id))
{
movies = movies.Where(s => s.Title.Contains(id));
}
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));
}
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<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 .
namespace MvcMovie.Models
{
public class MovieGenreViewModel
{
public List<Movie> movies;
public SelectList genres;
public string movieGenre { get; set; }
}
}
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (!String.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
return View(movieGenreVM);
}
O código a seguir é uma consulta LINQ que recupera todos os gêneros do banco de dados.
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())
@{
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.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)
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
[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>
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
Atualize a classe Movie para aproveitar os atributos de validação Required , StringLength , RegularExpression e
Range internos.
[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.
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.
// 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.
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 />
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.
[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:
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:
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; }
[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
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == 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?}");
});
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
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
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.
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
Visão geral
Este tutorial cria a seguinte API:
POST /api/todo Adicionar um novo item Item de tarefas pendentes Item de tarefas pendentes
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.
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.
<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; }
}
}
using Microsoft.EntityFrameworkCore;
namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
}
}
namespace TodoApi
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));
services.AddMvc();
}
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;
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.
[HttpGet]
public IEnumerable<TodoItem> GetAll()
{
return _context.TodoItems.ToList();
}
[
{
"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 :
"{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 ).
_context.TodoItems.Add(item);
_context.SaveChanges();
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 :
{
"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();
}
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();
}
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
Visão geral
Este tutorial cria a seguinte API:
POST /api/todo Adicionar um novo item Item de tarefas pendentes Item de tarefas pendentes
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"]
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; }
}
}
using Microsoft.EntityFrameworkCore;
namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
}
}
namespace TodoApi
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));
services.AddMvc();
}
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;
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.
[HttpGet]
public IEnumerable<TodoItem> GetAll()
{
return _context.TodoItems.ToList();
}
[
{
"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 :
"{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"]
[{"key":1,"name":"Item1","isComplete":false}]
[HttpPost]
public IActionResult Create([FromBody] TodoItem item)
{
if (item == null)
{
return BadRequest();
}
_context.TodoItems.Add(item);
_context.SaveChanges();
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" :
Atualização
[HttpPut("{id}")]
public IActionResult Update(long id, [FromBody] TodoItem item)
{
if (item == null || item.Id != id)
{
return BadRequest();
}
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();
}
#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:
POST /api/todo Adicionar um novo item Item de tarefas pendentes Item de tarefas pendentes
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"]
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)
{
}
}
}
namespace TodoApi
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));
services.AddMvc();
}
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 .
namespace TodoApi.Controllers
{
[Route("api/[controller]")]
public class TodoController : Controller
{
private readonly TodoContext _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.
[HttpGet]
public IEnumerable<TodoItem> GetAll()
{
return _context.TodoItems.ToList();
}
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 :
"{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 .
[HttpPost]
public IActionResult Create([FromBody] TodoItem item)
{
if (item == null)
{
return BadRequest();
}
_context.TodoItems.Add(item);
_context.SaveChanges();
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 :
{
"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();
}
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();
}
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
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.
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; }
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();
}
_toDoList.Add(todoItem1);
_toDoList.Add(todoItem2);
_toDoList.Add(todoItem3);
}
}
}
services.AddSingleton<IToDoRepository,ToDoRepository>();
}
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;
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:
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();
}
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
using Swashbuckle.AspNetCore.Swagger;
No método Configure de Startup.cs, habilite o middleware para servir o documento JSON gerado e o
SwaggerUI:
// 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();
}
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" }
});
});
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.
// 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();
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"
}
}
}
},
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();
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:
// 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;
}
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
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.
<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>© 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 »</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 »</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; }
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
}
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
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { 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.
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
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)
{
}
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"
}
}
}
using ContosoUniversity.Models;
using System;
using System.Linq;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
context.Database.EnsureCreated();
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);
host.Run();
}
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.
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>
dotnet restore
dotnet aspnet-codegenerator razorpage -m Student -dc SchoolContext -udl -outDir Pages\Students --
referenceScriptLibraries
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
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.
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.
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
<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>
@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();
}
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.
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_):
if (await TryUpdateModelAsync<Student>(
emptyStudent,
"student", // Prefix for form value.
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
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; }
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.
[BindProperty]
public Student Student { get; set; }
if (Student == null)
{
return NotFound();
}
return Page();
}
if (await TryUpdateModelAsync<Student>(
studentToUpdate,
"student",
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return Page();
}
}
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.
[BindProperty]
public Student Student { get; set; }
public string ErrorMessage { get; set; }
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.
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>
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}"
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
Caso tenha problemas que não consiga resolver, baixe o aplicativo concluído para este estágio.
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;
}
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";
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;
}
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 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";
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;
}
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:
@{
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) .
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;
}
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>
<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.
namespace ContosoUniversity
{
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }
this.AddRange(items);
}
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.
CurrentFilter = searchString;
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.
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.
@page
@model ContosoUniversity.Pages.Students.IndexModel
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
@{
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>
<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>
using System;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class EnrollmentDateGroup
{
[DataType(DataType.Date)]
public DateTime? EnrollmentDate { get; set; }
namespace ContosoUniversity.Pages
{
public class AboutModel : PageModel
{
private readonly SchoolContext _context;
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";
}
<table>
<tr>
<th>
Enrollment Date
</th>
<th>
Students
</th>
</tr>
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
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.
{
"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:
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:
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");
});
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:
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])
);
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.
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.
Solução de problemas
Baixe o aplicativo concluído para este estágio.
O aplicativo gera a seguinte exceção:
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
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; }
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:
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; }
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; }
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:
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
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;
}
}
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 :
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; }
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:
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.
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; }
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.
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.
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
[Range(0, 5)]
public int Credits { 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:
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 .
Um curso pode ter qualquer quantidade de estudantes inscritos; portanto, a propriedade de navegação
Enrollments é uma coleção:
Um curso pode ser ministrado por vários instrutores; portanto, a propriedade de navegação CourseAssignments é
uma coleção:
namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { 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; }
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 :
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)
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
A entidade CourseAssignment
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; }
}
}
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)
{
}
modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
}
}
}
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.
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
}
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
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.
{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity3;Trusted_Connection=True;MultipleActiveResultSets=tr
ue"
},
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.
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 .
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
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.
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:
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>
Execute o aplicativo e selecione a guia Cursos para ver a lista com nomes de departamentos.
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 :
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; }
}
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; }
}
}
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;
if (id != null)
{
InstructorID = id.Value;
}
}
}
}
@{
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
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.
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.
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:
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;
}
}
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;
}
</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:
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();
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 :
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
namespace ContosoUniversity.Pages.Courses
{
public class DepartmentNamePageModel : PageModel
{
public SelectList DepartmentNameSL { get; set; }
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 .
namespace ContosoUniversity.Pages.Courses
{
public class CreateModel : DepartmentNamePageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
[BindProperty]
public Course Course { get; set; }
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");
}
O código anterior:
Deriva de DepartmentNamePageModel .
Usa TryUpdateModelAsync para impedir o excesso de postagem.
Substitui ViewData["DepartmentID"] por DepartmentNameSL (da classe base).
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;
[BindProperty]
public Course Course { get; set; }
if (Course == null)
{
return NotFound();
}
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");
}
@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");}
}
[BindProperty]
public Course Course { get; set; }
if (Course == null)
{
return NotFound();
}
return Page();
}
if (Course != null)
{
_context.Courses.Remove(Course);
await _context.SaveChangesAsync();
}
return RedirectToPage("./Index");
}
}
if (Course == null)
{
return NotFound();
}
return Page();
}
@{
ViewData["Title"] = "Delete";
}
<h2>Delete</h2>
<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>
[BindProperty]
public Instructor Instructor { get; set; }
if (Instructor == null)
{
return NotFound();
}
return Page();
}
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.
@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");}
}
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
{
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;
[BindProperty]
public Instructor Instructor { get; set; }
if (Instructor == null)
{
return NotFound();
}
PopulateAssignedCourseData(_context, Instructor);
return Page();
}
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;
<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;
[BindProperty]
public Instructor Instructor { get; set; }
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();
}
}
}
@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;
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
namespace ContosoUniversity.Pages.Instructors
{
public class DeleteModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
[BindProperty]
public Instructor Instructor { get; set; }
if (Instructor == null)
{
return NotFound();
}
return Page();
}
_context.Instructors.Remove(instructor);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
en-us/
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 .
É 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; }
[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; }
[Timestamp]
public byte[] RowVersion { 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.
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:
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:
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");
});
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>
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;
[BindProperty]
public Department Department { get; set; }
// Replace ViewData["InstructorID"]
public SelectList InstructorNameSL { get; set; }
if (Department == null)
{
return NotFound();
}
return Page();
}
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();
}
return Page();
}
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.
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();
}
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 :
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();
}
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.
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.
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.
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; }
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();
}
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>
<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>
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
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.
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>© 2017 - Contoso University</p>
<p>© 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>
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 »</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 »</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.
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; }
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
}
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 ).
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; }
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.
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
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)
{
}
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"
}
}
}
using ContosoUniversity.Models;
using System;
using System.Linq;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
context.Database.EnsureCreated();
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.
host.Run();
}
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.
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;
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:
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.
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.
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
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.
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
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 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 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.
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.
[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.)
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.
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>
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.)
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.
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
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.
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:
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).
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.
<p>
<a asp-action="Create">Create New</a>
</p>
<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.
namespace ContosoUniversity
{
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }
this.AddRange(items);
}
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.
if (searchString != null)
{
page = 1;
}
else
{
searchString = currentFilter;
}
ViewData["CurrentFilter"] = searchString;
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.
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.
@model PaginatedList<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"]" 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:
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.
namespace ContosoUniversity.Models.SchoolViewModels
{
public class EnrollmentDateGroup
{
[DataType(DataType.Date)]
public DateTime? EnrollmentDate { get; set; }
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:
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";
}
<table>
<tr>
<th>
Enrollment Date
</th>
<th>
Students
</th>
</tr>
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
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.
<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.)
{
"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:
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.
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.
modelBuilder.Entity("ContosoUniversity.Models.Course", b =>
{
b.Property<int>("CourseID");
b.Property<int>("Credits");
b.Property<string>("Title");
b.HasKey("CourseID");
b.ToTable("Course");
});
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.
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])
);
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.
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
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; }
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:
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; }
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:
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; }
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:
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.
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;
}
}
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
:
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.
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; }
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:
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).
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; }
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.
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; }
[Range(0, 5)]
public int Credits { 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.
Um curso pode ter qualquer quantidade de estudantes inscritos; portanto, a propriedade de navegação
Enrollments é uma coleção:
Um curso pode ser ministrado por vários instrutores; portanto, a propriedade de navegação CourseAssignments
é uma coleção (o tipo CourseAssignment é explicado posteriormente):
namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { 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; }
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:
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)
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
}
Um registro destina-se a um único aluno e, portanto, há uma propriedade de chave estrangeira StudentID e
uma propriedade de navegação Student :
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
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; }
}
}
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
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.
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.
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.
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();
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 .
Se você tiver tentado executar o comando database update neste ponto (não faça isso ainda), receberá o
seguinte erro:
À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.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.
{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity3;Trusted_Connection=True;MultipleActiveResultSets=true"
},
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.
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
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.
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>
Execute o aplicativo e selecione a guia Cursos para ver a lista com nomes de departamentos.
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; }
}
}
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.
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 .
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 .
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:
Em vez de:
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;
}
@{
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>
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.
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 .
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.
</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.
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
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.
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:
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 .
return View(course);
}
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
return View(course);
}
<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>
<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.
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();
}
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);
}
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;
}
<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.
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();
}
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;
}
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;
}
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;
}
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;
}
if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.SingleOrDefault(i =>
i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
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;
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.
_context.Instructors.Remove(instructor);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
// 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:
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;
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
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.
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; }
[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; }
[Timestamp]
public byte[] RowVersion { 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
@{
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.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int? id, byte[] rowVersion)
{
if (id == null)
{
return NotFound();
}
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}");
}
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.
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.
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.
@{
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");}
}
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.
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>
<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>
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.
@{
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
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.
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; }
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; }
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; }
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
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.
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.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);
}
(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.
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
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.
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);
}
using System.Data.Common;
Execute o aplicativo e acesse a página Sobre. Ela exibe os mesmos dados que antes.
[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();
}
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.
_context.ChangeTracker.AutoDetectChangesEnabled = false;
if (searchString != null)
{
page = 1;
}
else
{
searchString = currentFilter;
}
ViewData["CurrentFilter"] = searchString;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
if (string.IsNullOrEmpty(sortOrder))
{
sortOrder = "LastName";
}
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:
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
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:
Pré-requisitos
Instale o seguinte:
SDK do .NET Core 2.0.0 ou posterior
Visual Studio para Mac
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.
appsettings.json Configuraçã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.
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; }
}
}
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"ConnectionStrings": {
"MovieContext": "Data Source=MvcMovie.db"
}
}
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.
<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>
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:
PARÂMETRO DESCRIÇÃO
-m O nome do modelo.
Testar o aplicativo
Executar o aplicativo e acrescentar /Movies à URL no navegador ( http://localhost:port/movies ).
Teste o link Criar.
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:
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
@{
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>
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>
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;
[BindProperty]
public Movie Movie { get; set; }
_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:
_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");}
}
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();
}
}
}
}
namespace RazorPagesMovie
{
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);
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();
}
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
using System;
using System.ComponentModel.DataAnnotations;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
Os links Editar, Detalhes e Excluir são gerados pelo Auxiliar de Marcação de Âncora no arquivo
Pages/Movies/Index.cshtml.
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?}"
_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 ):
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.
@{
Layout = "_Layout";
}
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
A primeira linha do método OnGetAsync cria uma consulta LINQ para selecionar os filmes:
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.
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:
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.
@{
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>
<table class="table">
<thead>
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
Pré-requisitos
Instale o seguinte:
SDK do .NET Core 2.0.0 ou posterior
Visual Studio Code
Extensão C# do VS Code
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.
appsettings.json Configuraçã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.
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; }
}
}
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"ConnectionStrings": {
"MovieContext": "Data Source=MvcMovie.db"
}
}
services.AddDbContext<MovieContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("MovieContext")));
services.AddMvc();
}
<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>
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
PARÂMETRO DESCRIÇÃO
-m O nome do modelo.
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:
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
@{
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>
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>
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;
[BindProperty]
public Movie Movie { get; set; }
_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:
_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");}
}
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();
}
}
}
}
namespace RazorPagesMovie
{
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);
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();
}
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
using System;
using System.ComponentModel.DataAnnotations;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
Os links Editar, Detalhes e Excluir são gerados pelo Auxiliar de Marcação de Âncora no arquivo
Pages/Movies/Index.cshtml.
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?}"
_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 ):
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.
@{
Layout = "_Layout";
}
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
A primeira linha do método OnGetAsync cria uma consulta LINQ para selecionar os filmes:
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.
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:
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.
@{
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>
<table class="table">
<thead>
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
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
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
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.
namespace MvcMovie.Controllers
{
public class HelloWorldController : Controller
{
//
// GET: /HelloWorld/
//
// GET: /HelloWorld/Welcome/
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]
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:
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
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.
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
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 .
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) .
<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>© 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>
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";
}
Você os tornará ligeiramente diferentes para que possa ver qual parte do código altera qual parte do aplicativo.
@{
ViewData["Title"] = "Movie List";
}
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:
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).
using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;
namespace MvcMovie.Controllers
{
public class HelloWorldController : Controller
{
public IActionResult Index()
{
return View();
}
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>
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
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; }
}
}
<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.
dotnet restore
dotnet aspnet-codegenerator controller -name MoviesController -m Movie -dc MvcMovieContext --
relativeFolderPath Controllers --useDefaultLayout --referenceScriptLibraries
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; }
}
}
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
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();
}
}
}
}
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);
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();
}
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
using System;
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
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.
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>
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
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();
}
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.
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.
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
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
A primeira linha do método de ação Index cria uma consulta LINQ para selecionar os filmes:
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:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (!String.IsNullOrEmpty(id))
{
movies = movies.Where(s => s.Title.Contains(id));
}
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 :
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<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 .
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; }
}
}
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (!String.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
return View(movieGenreVM);
}
O código a seguir é uma consulta LINQ que recupera todos os gêneros do banco de dados.
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).
@{
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.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)
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
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>
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
},
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
Atualize a classe Movie para aproveitar os atributos de validação Required , StringLength , RegularExpression e
Range internos.
[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.
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.
// 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.
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 />
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.
[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:
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:
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; }
[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
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == 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?}");
});
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
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.
using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;
namespace MvcMovie.Controllers
{
public class HelloWorldController : Controller
{
//
// GET: /HelloWorld/
//
// GET: /HelloWorld/Welcome/
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]
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:
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.
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>
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.
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>© 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>
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";
}
Você os tornará ligeiramente diferentes para que possa ver qual parte do código altera qual parte do aplicativo.
@{
ViewData["Title"] = "Movie List";
}
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:
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).
using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;
namespace MvcMovie.Controllers
{
public class HelloWorldController : Controller
{
public IActionResult Index()
{
return View();
}
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>
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
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; }
}
}
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)
{
}
using Microsoft.EntityFrameworkCore;
using MvcMovie.Models;
namespace MvcMovie
{
public class Startup
{
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.
dotnet restore
dotnet aspnet-codegenerator controller -name MoviesController -m Movie -dc MvcMovieContext --
relativeFolderPath Controllers --useDefaultLayout --referenceScriptLibraries
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
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();
}
}
}
}
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);
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();
}
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
using System;
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
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.
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>
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
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();
}
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.
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.
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
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
A primeira linha do método de ação Index cria uma consulta LINQ para selecionar os filmes:
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:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (!String.IsNullOrEmpty(id))
{
movies = movies.Where(s => s.Title.Contains(id));
}
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 :
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<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 .
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; }
}
}
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (!String.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
return View(movieGenreVM);
}
O código a seguir é uma consulta LINQ que recupera todos os gêneros do banco de dados.
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).
@{
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.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)
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
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>
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
},
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
Atualize a classe Movie para aproveitar os atributos de validação Required , StringLength , RegularExpression e
Range internos.
[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.
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.
// 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.
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 />
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.
[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:
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:
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; }
[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
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == 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?}");
});
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
Visão geral
Este tutorial cria a seguinte API:
POST /api/todo Adicionar um novo item Item de tarefas pendentes Item de tarefas pendentes
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; }
}
}
using Microsoft.EntityFrameworkCore;
namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
}
}
namespace TodoApi
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));
services.AddMvc();
}
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;
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.
[HttpGet]
public IEnumerable<TodoItem> GetAll()
{
return _context.TodoItems.ToList();
}
[
{
"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 :
"{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"]
[{"key":1,"name":"Item1","isComplete":false}]
[HttpPost]
public IActionResult Create([FromBody] TodoItem item)
{
if (item == null)
{
return BadRequest();
}
_context.TodoItems.Add(item);
_context.SaveChanges();
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" :
Atualização
[HttpPut("{id}")]
public IActionResult Update(long id, [FromBody] TodoItem item)
{
if (item == null || item.Id != id)
{
return BadRequest();
}
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();
}
Visão geral
Este tutorial cria a seguinte API:
POST /api/todo Adicionar um novo item Item de tarefas pendentes Item de tarefas pendentes
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.
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.
<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>
namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
}
using Microsoft.EntityFrameworkCore;
namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
}
}
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();
}
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;
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.
[HttpGet]
public IEnumerable<TodoItem> GetAll()
{
return _context.TodoItems.ToList();
}
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 :
"{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 ).
[HttpPost]
public IActionResult Create([FromBody] TodoItem item)
{
if (item == null)
{
return BadRequest();
}
_context.TodoItems.Add(item);
_context.SaveChanges();
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 :
{
"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();
}
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();
}
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
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.
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; }
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();
}
_toDoList.Add(todoItem1);
_toDoList.Add(todoItem2);
_toDoList.Add(todoItem3);
}
}
}
services.AddSingleton<IToDoRepository,ToDoRepository>();
}
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;
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:
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();
}
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();
}
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();
}
É 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:
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.
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.
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.
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.
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:
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;
}
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();
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:
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.
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 RequestSetOptionsMiddleware(
RequestDelegate next, IOptions<AppOptions> injectedOptions)
{
_next = next;
_injectedOptions = injectedOptions;
}
if (!string.IsNullOrWhiteSpace(option))
{
_injectedOptions.Value.Option = WebUtility.HtmlEncode(option);
}
await _next(httpContext);
}
}
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
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.
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;
}
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
// 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();
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.
// GET: /characters/
public IActionResult Index()
{
PopulateCharactersIfNoneExist();
var characters = _characterRepository.ListAll();
return View(characters);
}
using System.Collections.Generic;
using DependencyInjectionSample.Models;
namespace DependencyInjectionSample.Interfaces
{
public interface ICharacterRepository
{
IEnumerable<Character> ListAll();
void Add(Character character);
}
}
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;
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.
using System;
namespace DependencyInjectionSample.Interfaces
{
public interface IOperation
{
Guid OperationId { get; }
}
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>();
}
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; }
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;
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 .
OBSERVAÇÃO
Prefira solicitar dependências como parâmetros de construtor para acessar a coleção
RequestServices .
// 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 .
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.
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
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.
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;
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
app.Map("/map2", HandleMapTest2);
SOLICITAÇÃO RESPOSTA
SOLICITAÇÃO RESPOSTA
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:
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.
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.
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;
}
}
}
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;
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
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 .
using Microsoft.AspNetCore.Builder;
namespace Culture
{
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
}
}
}
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:
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
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.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
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
app.Map("/map2", HandleMapTest2);
SOLICITAÇÃO RESPOSTA
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:
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.
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.
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:
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
}
}
namespace Culture
{
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
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 .
using Microsoft.AspNetCore.Builder;
namespace Culture
{
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
}
}
}
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:
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
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;
if (!string.IsNullOrWhiteSpace(keyValue))
{
db.Add(new Request()
{
DT = DateTime.UtcNow,
MiddlewareActivation = "ConventionalMiddleware",
Value = keyValue
});
await db.SaveChangesAsync();
}
await _next(context);
}
}
if (!string.IsNullOrWhiteSpace(keyValue))
{
_db.Add(new Request()
{
DT = DateTime.UtcNow,
MiddlewareActivation = "IMiddlewareMiddleware",
Value = keyValue
});
await _db.SaveChangesAsync();
}
await next(context);
}
}
Não é possível passar objetos para o middleware ativado por alocador com UseMiddleware :
services.AddTransient<IMiddlewareMiddleware>();
services.AddMvc();
}
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
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 :
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:
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:
[Authorize]
public IActionResult BannerImage()
{
var file = Path.Combine(Directory.GetCurrentDirectory(),
"MyStaticFiles", "images", "banner1.svg");
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"
});
}
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"
});
}
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.
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();
app.UseFileServer(enableDirectoryBrowsing: true);
app.UseFileServer(new FileServerOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "MyStaticFiles")),
RequestPath = "/StaticFiles",
EnableDirectoryBrowsing = true
});
}
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
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"
});
}
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.
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
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
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}");
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" });
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" });
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.
routeBuilder.MapRoute(
"Track Package Route",
"package/{operation:regex(^(track|create|detonate)$)}/{id:int}");
URI RESPOSTA
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.
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.
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.
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.
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
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.
context.Response.ContentType = "text/html";
await context.Response.WriteAsync("Menu<hr/>");
await context.Response.WriteAsync($"<a href='{path}'>Create Package 123</a><br/>");
});
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
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.
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
app.UseRewriter(options);
}
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);
}
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.
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 .
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).
app.UseRewriter(options);
}
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
app.UseRewriter(options);
}
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
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.
app.UseRewriter(options);
}
app.UseRewriter(options);
}
<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>
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.
app.UseRewriter(options);
}
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;
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;
}
}
app.UseRewriter(options);
}
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;
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);
}
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;
}
}
}
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
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.
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
<environment include="Development">
<div><environment include="Development"></div>
</environment>
<environment exclude="Development">
<div><environment exclude="Development"></div>
</environment>
<environment include="Staging,Development,Staging_2">
<div>
<environment include="Staging,Development,Staging_2">
</div>
</environment>
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 :
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.
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
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.
if (env.IsProduction() || env.IsStaging())
{
throw new Exception("Not development.");
}
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
}
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
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;
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();
}
}
{
"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
Quando o ambiente está configurado como Staging , o seguinte método Configure lê o valor de MyConfig :
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.
Configuration = builder.Build();
Console.WriteLine($"Hello {Configuration["Profile:MachineName"]}");
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;
Configuration = builder.Build();
Console.WriteLine($"Hello {Configuration["Profile:MachineName"]}");
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
using System;
using System.IO;
using Microsoft.Extensions.Configuration;
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:
[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();
Assert.Equal("Rick", settings.Profile.Machine);
Assert.Equal(11, settings.Window.Height);
Assert.Equal(11, settings.Window.Width);
Assert.Equal("connectionstring", settings.Connection.Value);
}
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
namespace CustomConfigurationProvider
{
public class EFConfigSource : IConfigurationSource
{
private readonly Action<DbContextOptionsBuilder> _optionsAction;
namespace CustomConfigurationProvider
{
public class EFConfigProvider : ConfigurationProvider
{
public EFConfigProvider(Action<DbContextOptionsBuilder> optionsAction)
{
OptionsAction = optionsAction;
}
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));
}
}
}
using System;
using System.IO;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using CustomConfigurationProvider;
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"
}
key1=value_from_ef_1
key2=value_from_ef_2
key3=value_from_json_3
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();
}
}
MachineName: MairaPC
Left: 1980
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 :
Argumento único
O valor deve seguir um sinal de igual ( = ). O valor pode ser nulo (por exemplo, mykey= ).
A chave pode ter um prefixo.
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:
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.
& #8224;Uma chave com um prefixo de traço único ( - ) deve ser fornecida em mapeamentos de comutador, como
descrito abaixo.
Comando de exemplo:
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 (
- ).
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
MachineName: RickPC
Left: 1980
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
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.
@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["key"]: @Configuration["key"]</p>
</body>
</html>
@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["key"]: @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;
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();
}
}
{
"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
{
"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 :
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.
Configuration = builder.Build();
Console.WriteLine($"Hello {Configuration["Profile:MachineName"]}");
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;
Configuration = builder.Build();
Console.WriteLine($"Hello {Configuration["Profile:MachineName"]}");
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
using System;
using System.IO;
using Microsoft.Extensions.Configuration;
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:
[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();
Assert.Equal("Rick", settings.Profile.Machine);
Assert.Equal(11, settings.Window.Height);
Assert.Equal(11, settings.Window.Width);
Assert.Equal("connectionstring", settings.Connection.Value);
}
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
namespace CustomConfigurationProvider
{
public class EFConfigSource : IConfigurationSource
{
private readonly Action<DbContextOptionsBuilder> _optionsAction;
namespace CustomConfigurationProvider
{
public class EFConfigProvider : ConfigurationProvider
{
public EFConfigProvider(Action<DbContextOptionsBuilder> optionsAction)
{
OptionsAction = optionsAction;
}
namespace CustomConfigurationProvider
{
public static class EntityFrameworkExtensions
{
public static IConfigurationBuilder AddEntityFrameworkConfig(
this IConfigurationBuilder builder, Action<DbContextOptionsBuilder> setup)
{
return builder.Add(new EFConfigSource(setup));
}
}
}
using System;
using System.IO;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using CustomConfigurationProvider;
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();
}
}
key1=value_from_ef_1
key2=value_from_ef_2
key3=value_from_json_3
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();
}
}
MachineName: MairaPC
Left: 1980
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 :
Argumento único
O valor deve seguir um sinal de igual ( = ). O valor pode ser nulo (por exemplo, mykey= ).
A chave pode ter um prefixo.
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:
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.
& #8224;Uma chave com um prefixo de traço único ( - ) deve ser fornecida em mapeamentos de
comutador, como descrito abaixo.
Comando de exemplo:
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;
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();
}
}
dotnet run
MachineName: RickPC
Left: 1980
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
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.
@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["key"]: @Configuration["key"]</p>
</body>
</html>
@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["key"]: @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
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):
{
"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:
Index.cshtml.cs:
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");
}
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:
{
"option1": "value1_from_json",
"option2": -1,
"subsection": {
"suboption1": "subvalue1_from_json",
"suboption2": 200
}
}
O método OnGet do modelo de página retorna uma cadeia de caracteres com os valores de subopção
(Pages/Index.cshtml.cs):
Quando o aplicativo é executado, o método OnGet retorna uma cadeia de caracteres que mostra os valores da
classe de subopção:
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");
}
<h1>@ViewData["Title"]</h1>
A seguinte imagem mostra is valores option1 e option2 iniciais carregados do arquivo appsettings.json:
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:
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");
}
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 .
services.ConfigureAll<MyOptions>(myOptions =>
{
myOptions.Option1 = "ConfigureAll replacement value";
});
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";
});
Consulte também
Configuração
Registro em log no ASP.NET Core
04/05/2018 • 42 min to read • Edit Online
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:
webHost.Run();
}
Você encontrará informações sobre cada provedor de log interno e links para provedores de log de terceiros
mais adiante no artigo.
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:
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;
Na maioria das vezes será mais fácil usar ILogger<T> , conforme mostrado no exemplo a seguir.
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.
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:
info: TodoApi.Controllers.TodoController[1002]
Getting item invalidid
warn: TodoApi.Controllers.TodoController[4000]
GetById(invalidid) NOT FOUND
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 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:
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.
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.
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.
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:
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.
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);
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.
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
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).
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;
_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:
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 :
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 :
_logger.QuoteAdded(Quote.Text);
return RedirectToPage();
}
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):
_quoteDeleteFailed = LoggerMessage.Define<int>(
LogLevel.Error,
new EventId(5, nameof(QuoteDeleteFailed)),
"Quote delete failed (Id = {Id})");
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):
_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.
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):
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 ):
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 :
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
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 .
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.
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:
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.
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;
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;
}
// 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();
<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.
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:
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:
while (true)
{
MainAsync().GetAwaiter().GetResult();
}
}
token.RegisterChangeCallback(state =>
((TaskCompletionSource<object>)state).TrySetResult(null), tcs);
await tcs.Task.ConfigureAwait(false);
Console.WriteLine("quotes.txt changed");
}
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 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 .
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:
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.
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")
...
WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "assembly1;assembly2")
...
WebHost.CreateDefaultBuilder(args)
.PreferHostingUrls(false)
...
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.
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.
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:
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:
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:
using (host)
{
Console.ReadLine();
}
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();
}
Start(Action routeBuilder)
Use uma instância de IRouteBuilder (Microsoft.AspNetCore.Routing) para usar o middleware de
roteamento:
SOLICITAÇÃO RESPOSTA
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();
}
StartWith(Action app)
Forneça um delegado para configurar um IApplicationBuilder :
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 :
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:
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 :
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");
}
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 .
MÉTODO AÇÃO
services.AddSingleton<IStartup, Startup>();
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>")
...
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
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.
services.AddSession();
}
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).
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.
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
});
}
É 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();
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:
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;
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:
Outros códigos podem acessar o valor armazenado em HttpContext.Items usando a chave exposta pela classe
do middleware:
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.
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.
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
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.
Caso precise configurar as opções do Kestrel, chame UseKestrel em Program.cs, conforme mostrado no
seguinte exemplo:
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.
.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");
});
})
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");
});
})
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();
}
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();
if (serverAddressesFeature != null)
{
await context.Response
.WriteAsync("<p>Listening on the following addresses: " +
string.Join(", ", serverAddressesFeature.Addresses) +
"</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 .
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/
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
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.
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
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.
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.
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:
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:
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.
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:
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
namespace Localization.StarterWeb.Controllers
{
[Route("api/[controller]")]
public class AboutController : Controller
{
private readonly IStringLocalizer<AboutController> _localizer;
[HttpGet]
public string Get()
{
return _localizer["About Title"];
}
}
}
namespace Localization.StarterWeb.Controllers
{
public class BookController : Controller
{
private readonly IHtmlLocalizer<BookController> _localizer;
return View();
}
{
public class TestController : Controller
{
private readonly IStringLocalizer _localizer;
private readonly IStringLocalizer _localizer2;
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:
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
@{
ViewData["Title"] = Localizer["About"];
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
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:
CHAVE VALOR
@using Microsoft.AspNetCore.Mvc.Localization
@using Localization.StarterWeb.Services
@{
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
[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:
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.
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.
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization();
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();
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 .
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.
services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new[]
{
new CultureInfo(enUSCulture),
new CultureInfo("fr")
};
@using Microsoft.AspNetCore.Builder
@using Microsoft.AspNetCore.Http.Features
@using Microsoft.AspNetCore.Localization
@using Microsoft.AspNetCore.Mvc.Localization
@using Microsoft.Extensions.Options
@{
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}";
}
[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.
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
#: 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}\""
Registrando o serviço
Adicione os serviços necessários ao método ConfigureServices de Startup.cs:
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")
};
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:
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:
Para aceitar localizações para o tcheco, adicione "cs" à lista de culturas com suporte no método
ConfigureServices :
Edite o arquivo Views/Home/About.cshtml para renderizar cadeias de caracteres localizadas no plural para várias
cardinalidades:
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 :
Il y a un élément.
Il y a 2 éléments.
Il y a 5 éléments.
Para /Home/About?culture=cs :
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:
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.
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
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.
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++;
}
}
}
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);
if (!_appsettingsHash.SequenceEqual(appsettingsHash) ||
!_appsettingsEnvHash.SequenceEqual(appsettingsEnvHash))
{
_appsettingsHash = appsettingsHash;
_appsettingsEnvHash = appsettingsEnvHash;
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):
ChangeToken.OnChange<string>(
() => config.GetReloadToken(),
(state) => InvokeChanged(state),
"Not monitoring");
}
if (!_appsettingsHash.SequenceEqual(appsettingsHash) ||
!_appsettingsEnvHash.SequenceEqual(appsettingsEnvHash))
{
state = $"State updated at {DateTime.Now}";
CurrentState = state;
_appsettingsHash = appsettingsHash;
_appsettingsEnvHash = appsettingsEnvHash;
services.AddSingleton<IConfigurationMonitor, ConfigurationMonitor>();
public IndexModel(
IConfiguration config,
IConfigurationMonitor monitor,
FileService fileService)
{
_config = config;
_monitor = monitor;
_fileService = fileService;
}
return RedirectToPage();
}
return RedirectToPage();
}
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>();
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);
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>();
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
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 .
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
});
});
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>();
});
}
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();
}
}
}
app.Run(context =>
{
return context.Response.WriteAsync("Hello World");
});
}
while (!webSocket.CloseStatus.HasValue)
{
// Echo anything we receive
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, received.Count),
received.MessageType, received.EndOfMessage, 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 .
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
owin.ResponseHeaders IDictionary<string,string[]>
owin.ResponseBody Stream
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
Opaque v0.3.0
CHAVE VALOR (TIPO) DESCRIÇÃO
opaque.Version String
opaque.Stream Stream
opaque.CallCancelled CancellationToken
WebSocket v0.3.0
CHAVE VALOR (TIPO) DESCRIÇÃO
websocket.Version String
websocket.CallCancelled CancellationToken
Recursos adicionais
Middleware
Servidores
Introdução ao WebSockets no ASP.NET Core
08/02/2018 • 7 min to read • Edit Online
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();
});
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.
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.
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
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#
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
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
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
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();
}
@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
using Microsoft.AspNetCore.Mvc.RazorPages;
using System;
namespace RazorPagesIntro.Pages
{
public class IndexModel2 : PageModel
{
public string Message { get; private set; } = "PageModel in C#";
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:
/Pages/Index.cshtml / ou /Index
/Pages/Contact.cshtml /Contact
/Pages/Store/Contact.cshtml /Store/Contact
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.
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; }
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; }
}
}
using Microsoft.EntityFrameworkCore;
namespace RazorPagesContacts.Data
{
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions options)
: base(options)
{
}
@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>
namespace RazorPagesContacts.Pages
{
public class CreateModel : PageModel
{
private readonly AppDbContext _db;
[BindProperty]
public Customer Customer { get; set; }
_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");
}
[BindProperty]
public Customer Customer { get; set; }
_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)]
@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>
namespace RazorPagesContacts.Pages
{
public class IndexModel : PageModel
{
private readonly AppDbContext _db;
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:
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";
}
<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;
[BindProperty]
public Customer Customer { get; set; }
if (Customer == null)
{
return RedirectToPage("/Index");
}
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:
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 .
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.
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 ).
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);
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.AllowMappingHeadRequestsToGetHandler = true;
});
<!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
@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;
@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>
@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>
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
return 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
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 :
[TempData]
public string Message { get; set; }
[BindProperty]
public Customer Customer { get; set; }
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
Message = $"Customer {Customer.Name} added";
return RedirectToPage("./Index");
}
}
<h3>Msg: @Model.Message</h3>
[TempData]
public string Message { get; set; }
@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;
[BindProperty]
public Customer Customer { get; set; }
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
return RedirectToPage("/Index");
}
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 .
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:
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();
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
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.
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());
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:
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:
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 .
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:
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:
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:
options.Conventions.ConfigureFilter(new AddHeaderWithFactory());
AddHeaderWithFactory.cs:
public class AddHeaderWithFactory : IFilterFactory
{
// Implement IFilterFactory
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return new AddHeaderFilter();
}
Solicite a página About da amostra em localhost:5000/About e inspecione os cabeçalhos para exibir o resultado:
if (!IsHandler(method))
{
return null;
}
if (!TryParseHandlerMethod(
method.Name, out var httpMethod, out var handlerName))
{
return null;
}
handlerModel.Parameters.Add(parameterModel);
}
return handlerModel;
}
if (length == 0)
{
// The method is named "Async". Exit processing.
return false;
}
services.AddSingleton<IPageApplicationModelProvider,
CustomPageApplicationModelProvider>();
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();
}
return RedirectToPage();
}
return RedirectToPage();
}
if (message != null)
{
_db.Messages.Remove(message);
await _db.SaveChangesAsync();
}
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>
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
{
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
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.
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.
[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.
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; }
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 :
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>
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.
OBSERVAÇÃO
O JsonInputFormatter é o formatador padrão e se baseia no Json.NET.
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; }
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 .
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:
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:
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" });
}
}
<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.
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);
}
})
$.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:
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 .
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 .
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:
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.");
}
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.
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
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.
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");
return View(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.
@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:
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.
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:
return View();
}
@{
// 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:
return View();
}
@ViewBag.Greeting World!
<address>
@ViewBag.Address.Name<br>
@ViewBag.Address.Street<br>
@ViewBag.Address.City, @ViewBag.Address.State @ViewBag.Address.PostalCode
</address>
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.">
...
<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.
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>
<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>
<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>@GenericMethod<int>()</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:
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>
<p>@GenericMethod<int>()</p>
<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>")
<span>Hello World</span>
<span>Hello World</span>
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>")
<span>Hello World</span>
<p>@quote</p>
@{
quote = "Hate cannot drive out hate, only love can do that. - Martin Luther King, Jr.";
}
<p>@quote</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>
}
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 @: :
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>
}
@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>
}
@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;
}
@foreach
@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>
}
@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 -->
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";
}
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
O Razor expõe uma propriedade Model para acessar o modelo passado para a exibição:
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
using Microsoft.AspNetCore.Mvc.Razor;
@inherits CustomRazorPage<TModel>
<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>
@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";
}
}
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Razor;
Auxiliares de Marca
Há três diretivas que relacionadas aos Auxiliares de marca.
DIRETIVA FUNÇÃO
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
using Microsoft.AspNetCore.Razor.Language;
// Look at generatedCode
return csharpDocument;
}
}
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
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.
<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
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>© 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>
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";
}
@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.
@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
@{
Layout = "_Layout";
}
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
<label asp-for="Email"></label>
<label for="Email">Email</label>
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, AuthoringTagHelpers
@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 :
@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> .
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.
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 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:
gera:
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.
<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:
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:
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:
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:
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>
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>
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> .
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("MyBold")]
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; }
}
}
using System;
using AuthoringTagHelpers.Models;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace AuthoringTagHelpers.TagHelpers
{
public class WebsiteInformationTagHelper : TagHelper
{
public WebsiteContext Info { get; set; }
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>
OBSERVAÇÃO
Na marcação do Razor mostrada abaixo:
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:
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace AuthoringTagHelpers.TagHelpers
{
[HtmlTargetElement(Attributes = nameof(Condition))]
public class ConditionTagHelper : TagHelper
{
public bool Condition { get; set; }
@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>
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; }
O operador nameof protegerá o código, caso ele seja refatorado (recomendamos alterar o nome para RedCondition ).
OBSERVAÇÃO
A classe AutoLinkerHttpTagHelper é direcionada a elementos p e usa o Regex para criar a âncora.
@{
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>
[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:
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.
// 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
Amostra:
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:
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.
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).
Bool type=”checkbox”
DateTime type=”datetime”
Byte type=”number”
int 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):
[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
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() .
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:
[DataType(DataType.Password)]
public string Password { get; set; }
@model RegisterAddressViewModel
O método de ação:
@model Person
@{
var index = (int)ViewData["index"];
}
@model string
<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />
@model List<ToDoItem>
</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.
Amostra:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}
@model DescriptionViewModel
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}
@model SimpleViewModel
<span asp-validation-for="Email"></span>
<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>
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:
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
Amostra:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(CountryViewModel model)
{
if (ModelState.IsValid)
{
var msg = model.Country + " selected";
return RedirectToAction("IndexSuccess", new { message = msg});
}
A exibição Index :
@model CountryViewModel
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.
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
}
}
@model CountryEnumViewModel
É 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
}
}
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" };
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; }
@model CountryViewModelIEnumerable
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
O modelo Views/Shared/EditorTemplates/CountryViewModel.cshtml:
@model CountryViewModel
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
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.
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
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
[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();
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:
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á:
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:
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:
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}");
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>
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:
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();
O Auxiliar de Marca de Âncora gera uma rota diretamente para essa ação de controlador usando a URL
/Speaker/Evaluations. O HTML gerado:
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 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:
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:
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:
O HTML gerado:
<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:
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:
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
<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:
"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
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
"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
"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
"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
".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
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
nome (obrigatório )
TIPO DE ATRIBUTO VALOR DE EXEMPLO
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>
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
<environment names="Staging,Production">
<strong>HostingEnvironment.EnvironmentName is Staging or Production</strong>
</environment>
<environment include="Staging,Production">
<strong>HostingEnvironment.EnvironmentName is Staging or Production</strong>
</environment>
<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
<img src="~/images/asplogo.png"
asp-append-version="true" />
<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
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.
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.
@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:
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
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
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>
@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:
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;
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/>
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"};
}
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
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:
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;
[ViewComponent(Name = "PriorityList")]
public class XYZ : ViewComponent
</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:
@model IEnumerable<ViewComponentSample.Models.TodoItem>
Atualize Views/TodoList/Index.cshtml:
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;
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>
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
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
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 .
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 :
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
app.UseMvcWithDefaultRoute();
app.UseMvc(routes =>
{
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
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?}");
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:
[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:
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.
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.
[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.
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 :
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) { ... }
}
[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("[controller]/[action]")]
public class ProductsController : Controller
{
[HttpGet] // Matches '/Products/List'
public IActionResult List() {
// ...
}
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:
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 { ... }
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.
[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
// 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;
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;
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:
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;
[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;
DICA
Para criar uma URL absoluta, use uma sobrecarga que aceita um protocol :
Url.Action("Buy", "Products", new { id = 17 }, protocol: Request.Scheme)
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.
Os métodos de fábrica dos resultados da ação seguem um padrão semelhante aos métodos em
IUrlHelper .
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?}");
});
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.
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);
}
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.
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
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);
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:
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.
// 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
OBSERVAÇÃO
Tenha cuidado ao armazenar dados binários em bancos de dados relacionais, pois isso pode afetar negativamente o
desempenho.
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:
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>();
}
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;
if (hasContentDispositionHeader)
{
if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
{
targetFilePath = Path.GetTempFileName();
using (var targetStream = System.IO.File.Create(targetFilePath))
{
await section.Body.CopyToAsync(targetStream);
// Drains any remaining section body that has not been consumed and
// reads the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
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:
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
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;
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 :
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:
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.
return View();
}
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 :
// 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();
services.AddMvc();
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;
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
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;
return View(model);
}
[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
});
}
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());
}
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
[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;
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);
}
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;
[HttpGet("forsession/{sessionId}")]
public async Task<IActionResult> ForSession(int sessionId)
{
var session = await _sessionRepository.GetByIdAsync(sessionId);
if (session == null)
{
return NotFound(sessionId);
}
return Ok(result);
}
[HttpPost("create")]
public async Task<IActionResult> Create([FromBody]NewIdeaModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
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);
// 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);
}
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>();
}
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
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"))
{
}
Client = _server.CreateClient();
Client.BaseAddress = new Uri("http://localhost");
}
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;
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;
[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;
}
[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
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;
As convenções de modelo de aplicativo são aplicadas como opções quando o MVC é adicionado em
ConfigureServices em Startup .
namespace AppModelSample.Conventions
{
public class ControllerDescriptionAttribute : Attribute, IControllerModelConvention
{
private readonly string _description;
[ControllerDescription("Controller Description")]
public class DescriptionAttributesController : Controller
{
public string Index()
{
return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
}
using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace AppModelSample.Conventions
{
public class ActionDescriptionAttribute : Attribute, IActionModelConvention
{
private readonly string _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"];
}
}
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;
}
}
}
namespace AppModelSample.Conventions
{
public class CustomActionNameAttribute : Attribute, IActionModelConvention
{
private readonly string _actionName;
// 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.
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
}
}
}
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.";
}
}
}
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.
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
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
}
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:
namespace FiltersSample.Filters
{
public class AddHeaderAttribute : ResultFilterAttribute
{
private readonly string _name;
private readonly string _value;
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:
[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
services.AddScoped<AddHeaderFilterWithDi>();
}
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.
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.
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"
};
}
[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();
}
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))
{
}
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:
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 CustomExceptionFilterAttribute(
IHostingEnvironment hostingEnvironment,
IModelMetadataProvider modelMetadataProvider)
{
_hostingEnvironment = hostingEnvironment;
_modelMetadataProvider = modelMetadataProvider;
}
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.
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.
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
/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?}");
});
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
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
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>
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
// 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.
Os tipos de entidade:
public static class EntityTypes
{
public static IReadOnlyList<TypeInfo> Types => new List<TypeInfo>()
{
typeof(Sprocket).GetTypeInfo(),
typeof(Widget).GetTypeInfo(),
};
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;
}
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.");
}
}
}
namespace AppPartsSample.Controllers
{
public class FeaturesController : Controller
{
private readonly ApplicationPartManager _partManager;
return View(viewModel);
}
}
}
Saída de exemplo:
Associação de modelos personalizada
08/02/2018 • 12 min to read • Edit Online
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);
}
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;
}
if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(modelName,
valueProviderResult);
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;
}
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;
}
}
}
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 .
services.AddMvc(options =>
{
// add custom binder to beginning of collection
options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
});
}
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
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.
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
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.
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.
[Fact]
public async Task ReturnHelloWorld()
{
// Act
var response = await _client.GetAsync("/");
response.EnsureSuccessStatusCode();
// 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();
}
[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>
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;
_next = next;
_options = options;
_primeService = primeService;
}
IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UsePrimeChecker();
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
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).
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.
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:
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):
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.
}
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();
// 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));
[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.
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:
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):
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);
}
[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);
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):
[Required]
[DataType(DataType.Text)]
[StringLength(200, ErrorMessage = "There's a 200 character limit on messages. Please shorten your
message.")]
public string Text { get; set; }
}
// 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
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;
return View(model);
}
[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
});
}
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());
}
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
[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;
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);
}
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;
[HttpGet("forsession/{sessionId}")]
public async Task<IActionResult> ForSession(int sessionId)
{
var session = await _sessionRepository.GetByIdAsync(sessionId);
if (session == null)
{
return NotFound(sessionId);
}
return Ok(result);
}
[HttpPost("create")]
public async Task<IActionResult> Create([FromBody]NewIdeaModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
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);
// 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);
}
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>();
}
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
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"))
{
}
Client = _server.CreateClient();
Client.BaseAddress = new Uri("http://localhost");
}
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;
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;
[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;
}
[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
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
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.
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>© 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 »</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 »</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; }
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
}
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 .
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; }
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.
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
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"
}
}
}
using ContosoUniversity.Models;
using System;
using System.Linq;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
context.Database.EnsureCreated();
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.
namespace ContosoUniversity
{
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);
host.Run();
}
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.
Install-Package Microsoft.VisualStudio.Web.CodeGeneration.Design
Install-Package Microsoft.VisualStudio.Web.CodeGeneration.Utils
<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>
dotnet restore
dotnet aspnet-codegenerator razorpage -m Student -dc SchoolContext -udl -outDir Pages\Students --
referenceScriptLibraries
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.
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.
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.
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
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.
<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).
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.
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.
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:
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
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
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:
var paths = {
webroot: "./wwwroot/"
};
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 cssmin Um módulo que minimiza arquivos CSS. Para obter mais
informações, consulte gulp cssmin.
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("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("."));
});
A tabela a seguir fornece uma explicação das tarefas especificado no código acima:
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.
var paths = {
webroot: "./wwwroot/"
};
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("."));
});
{
"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:
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).
gulp.task("first", function () {
console.log('first task! <-----');
});
gulp.task("series:first", function () {
console.log('first task! <-----');
});
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 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
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).
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;
}
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"
}
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.
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.
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");
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.
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.
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.
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.
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.
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
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 .
@{
ViewData["Title"] = "About";
}
<div class="list-group">
<a class="list-group-item" href="#"><i class="fa fa-home fa-fw" aria-hidden="true"></i> Home</a>
<a class="list-group-item" href="#"><i class="fa fa-book fa-fw" aria-hidden="true"></i> Library</a>
<a class="list-group-item" href="#"><i class="fa fa-pencil fa-fw" aria-hidden="true"></i>
Applications</a>
<a class="list-group-item" href="#"><i class="fa fa-cog fa-fw" aria-hidden="true"></i> 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.
{
"name": "asp.net",
"private": true,
"dependencies": {
"jquery": "3.1.1",
"bootstrap": "3.3.7"
}
}
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
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>
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
npm
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>© 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>
OBSERVAÇÃO
Se você estiver usando qualquer um dos plug-ins do Bootstrap jQuery, você também precisará fazer referência a jQuery.
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).
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
.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"):
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.
@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:
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:
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;
}
}
.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:
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");
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:
$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;
}
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;
}
.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>
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
ORIGINAL RENOMEADO
imageTagAndImageID t
imageContext a
imageElement r
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.
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 ==========
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 ==========
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.
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.
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>
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
"use strict";
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
npm i -g gulp-cli
"use strict";
var regex = {
css: /\.css$/,
html: /\.(html|htm)$/,
js: /\.js$/
js: /\.js$/
};
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);
});
}
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
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();
}
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.
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:
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
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.
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
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>
<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';
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';
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.
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");
}
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
});
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.
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 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.
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 start with count 0, then increments by 1 when clicked', async(() => {
const countElement = fixture.nativeElement.querySelector('strong');
expect(countElement.textContent).toEqual('0');
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:
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 :
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:
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.
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 .
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.
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.
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:
@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();
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 }
]
};
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 :
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:
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.
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.
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
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
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.
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
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.
namespace SignalRChat.Hubs
{
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}
namespace SignalRChat
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
services.AddMvc();
services.AddSignalR();
}
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"> </div>
<div class="row">
<div class="col-6"> </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"> </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:
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
OBSERVAÇÃO
ASP.NET Core 2.1 is in preview and not recommended for production use.
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");
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
MÉTODO DESCRIÇÃO
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.
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.
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
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)
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.
<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.
namespace SignalRChat
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
services.AddMvc();
services.AddSignalR();
}
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 .
O código anterior no connection.on é executado quando o código do lado do servidor chamá-lo usando o
SendAsync método.
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.
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.
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
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
NAVEGADOR VERSÃO
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.
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; }
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();
}
_toDoList.Add(todoItem1);
_toDoList.Add(todoItem2);
_toDoList.Add(todoItem3);
}
}
}
services.AddSingleton<IToDoRepository,ToDoRepository>();
}
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;
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:
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();
}
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.
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'))]"
]
}
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
Configurar
Abra uma conta do Azure gratuita se você não tiver uma.
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....
Selecione Novo... na caixa de diálogo Configurar Banco de Dados SQL para criar um novo banco de
dados.
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:
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
Pré-requisitos
Para concluir este tutorial, você precisará de:
Uma assinatura do Microsoft Azure
.NET Core
Cliente de linha de comando Git
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.
OBSERVAÇÃO
É seguro ignorar quaisquer alertas do Git sobre os términos das linhas.
Windows
Outros
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:
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.
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
OBSERVAÇÃO
A versão mais recente do .NET Core é 2.0.
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.
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.
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 .
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
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
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
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.
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.
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
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:
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
<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.
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.
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.
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.
Navegar no site
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:
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.
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.
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:
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
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.
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.
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.
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
<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.
<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.
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.
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
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.
CGI Não
CgiModule
†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.
AnonymousIdentification
DefaultAuthentication
FileAuthorization
Perfil
RoleManager
ScriptModule-4.0
UrlAuthorization
WindowsAuthentication
<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
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
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);
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:
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
if (isService)
{
host.RunAsService();
}
else
{
host.Run();
}
}
2. Criar um método de extensão para IWebHost que passa personalizado WebHostService para
ServiceBase.Run :
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 :
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
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.
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseAuthentication();
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:
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:
[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.
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.
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.
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
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
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
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.
app.UseAuthentication();
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.
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] .
Reinicie o Apache:
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:
[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.
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.
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.
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:
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 .
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.
Para impor a SSL, instale o mod_rewrite módulo para habilitar a regravação de URL:
<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.
Reinicie o Apache:
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.
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.
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
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
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:
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:
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:
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
Cabeçalhos encaminhados
Por convenção, os proxies encaminham informações em cabeçalhos HTTP.
CABEÇALHO DESCRIÇÃO
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
}
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.
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
O padrão é X-Forwarded-For .
OPÇÃO DESCRIÇÃO
O padrão é X-Forwarded-Host .
O padrão é X-Forwarded-Proto .
O padrão é 1.
O padrão é X-Original-For .
O padrão é X-Original-Host .
OPÇÃO DESCRIÇÃO
O padrão é X-Original-Proto .
OPÇÃO DESCRIÇÃO
O padrão é X-Forwarded-For .
OPÇÃO DESCRIÇÃO
O padrão é X-Forwarded-Host .
O padrão é X-Forwarded-Proto .
O padrão é 1.
O padrão é X-Original-For .
O padrão é X-Original-Host .
OPÇÃO DESCRIÇÃO
O padrão é X-Original-Proto .
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:
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:
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:
// Headers
await context.Response.WriteAsync($"Request Headers:{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
<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 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 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):
MSDeploy (atualmente isso só funciona no Windows como MSDeploy não plataforma cruzada):
Pacote MSDeploy (atualmente isso só funciona no Windows como MSDeploy não plataforma cruzada):
<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:
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:
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:
Usando o MSBuild:
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:
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.
</ItemGroup>
</Project>
<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
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
†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:
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
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.
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.
<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": {}
}
}
}
}
"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
%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.
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.
NOME DESCRIÇÃO
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)
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.
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;
});
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);
}
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);
}
}
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");
}
// 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;
});
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.
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.
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:
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.
entrar
Tokens
IdentityOptions.Tokens Especifica o TokenOptions com as propriedades mostradas na tabela.
PROPRIEDADE DESCRIÇÃO
User
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
// User settings
options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
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;
});
PROPRIEDADE DESCRIÇÃO
{
"iisSettings": {
"windowsAuthentication": true,
"anonymousAuthentication": false,
"iisExpress": {
"applicationUrl": "http://localhost:52171/",
"sslPort": 0
}
} // additional options trimmed
}
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:
HTTP.sys
Se usar o HTTP. sys, adicione o seguinte para o ConfigureServices método:
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}";
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.
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)
{
}
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
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
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 :
A lógica de implementação para criar o usuário está no _usersTable.CreateAsync método, como mostrado abaixo.
if(rows > 0)
{
return IdentityResult.Success;
}
return IdentityResult.Failed(new IdentityError { Description = $"Could not insert user {user.Email}." });
}
using System;
namespace CustomIdentityProviderSample.CustomProvider
{
public class ApplicationRole
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; }
}
}
// 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
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.
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.
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
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:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication().AddFacebook(facebookOptions =>
{
facebookOptions.AppId = Configuration["Authentication:Facebook:AppId"];
facebookOptions.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
});
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
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.
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
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication().AddTwitter(twitterOptions =>
{
twitterOptions.ConsumerKey = Configuration["Authentication:Twitter:ConsumerKey"];
twitterOptions.ConsumerSecret = Configuration["Authentication:Twitter:ConsumerSecret"];
});
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
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:
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.
Os valores para esses tokens podem ser encontrados no arquivo JSON baixado na etapa anterior em
web.client_id e web.client_secret .
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication().AddGoogle(googleOptions =>
{
googleOptions.ClientId = Configuration["Authentication:Google:ClientId"];
googleOptions.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
});
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
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:
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
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication().AddMicrosoftAccount(microsoftOptions =>
{
microsoftOptions.ClientId = Configuration["Authentication:Microsoft:ApplicationId"];
microsoftOptions.ClientSecret = Configuration["Authentication:Microsoft:Password"];
});
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.
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
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.
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.
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";
// 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()
// ...
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:
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
Exigir HTTPS
Consulte exigir HTTPS.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
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:
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>"
}
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/Account/Manage");
options.Conventions.AuthorizePage("/Account/Logout");
});
services.AddSingleton<IEmailSender, EmailSender>();
services.Configure<AuthMessageSenderOptions>(Configuration);
}
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 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);
}
}
}
Talvez seja necessário expandir a barra de navegação para ver o nome de usuário.
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.
@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")
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.
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;
}
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;
}
SMSSender.Userkey = Options.SMSAccountIdentification;
SMSSender.Password = Options.SMSAccountPassword;
SMSSender.Originator = Options.SMSAccountFrom;
SMSSender.AddRecipient(number);
SMSSender.MessageData = message;
SMSSender.SendTextSMS();
return Task.FromResult(0);
}
}
}
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.
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
services.Configure<IdentityOptions>(options =>
{
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
});
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();
app.UseAuthentication();
Opções de AddCookie
O CookieAuthenticationOptions classe é usada para configurar as opções de provedor de autenticação.
OPÇÃO DESCRIÇÃO
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
...
});
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
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
//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);
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity));
ValidatePrincipal(CookieValidatePrincipalContext)
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 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
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).
Pré-requisitos
A seguir é necessários para este passo a passo:
Assinatura do Microsoft Azure
Visual Studio de 2017 (qualquer ediçã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.
CONFIGURAÇÃO VALOR
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.
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
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
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.
CONFIGURAÇÃO VALOR
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.
Nome Carteiro
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.
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 :
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
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.
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:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
//.AddRazorPagesOptions(options =>
//{
// options.Conventions.AuthorizeFolder("/Account/Manage");
// options.Conventions.AuthorizePage("/Account/Logout");
//});
services.AddSingleton<IEmailSender, EmailSender>();
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.
}
}
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();
}
// 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);
}
}
return user.Id;
}
if (!await roleManager.RoleExistsAsync(role))
{
IR = await roleManager.CreateAsync(new IdentityRole(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
},
namespace ContactManager.Authorization
{
public class ContactIsOwnerAuthorizationHandler
: AuthorizationHandler<OperationAuthorizationRequirement, Contact>
{
UserManager<ApplicationUser> _userManager;
public ContactIsOwnerAuthorizationHandler(UserManager<ApplicationUser>
userManager)
{
_userManager = userManager;
}
if (resource.OwnerID == _userManager.GetUserId(context.User))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
}
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;
}
return Task.CompletedTask;
}
}
}
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;
}
return Task.CompletedTask;
}
}
}
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
//.AddRazorPagesOptions(options =>
//{
// options.Conventions.AuthorizeFolder("/Account/Manage");
// options.Conventions.AuthorizePage("/Account/Logout");
//});
services.AddSingleton<IEmailSender, EmailSender>();
// Authorization handlers.
services.AddScoped<IAuthorizationHandler,
ContactIsOwnerAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler,
ContactAdministratorsAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler,
ContactManagerAuthorizationHandler>();
}
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};
}
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 :
Contact.OwnerID = UserManager.GetUserId(User);
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)
{
}
// 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);
}
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.
[BindProperty]
public Contact Contact { get; set; }
if (Contact == null)
{
return NotFound();
}
var isAuthorized = await AuthorizationService.AuthorizeAsync(
User, Contact,
ContactOperations.Update);
if (!isAuthorized.Succeeded)
{
return new ChallengeResult();
}
return Page();
}
if (contact == null)
{
return NotFound();
}
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");
}
[BindProperty]
public Contact Contact { get; set; }
if (Contact == null)
{
return NotFound();
}
return Page();
}
if (contact == null)
{
return NotFound();
}
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
@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>
}
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>
<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>
if (Contact == null)
{
return NotFound();
}
return Page();
}
if (contact == null)
{
return NotFound();
}
return RedirectToPage("./Index");
}
}
USER OPÇÕES
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.
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();
}
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
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.
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.
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.
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.
// 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:
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()
{
}
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:
[Authorize]
public ActionResult Logout()
{
}
}
[Authorize]
public class AccountController : Controller
{
[AllowAnonymous]
public ActionResult Login()
{
}
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.
[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()
{
}
}
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdministratorRole", policy => policy.RequireRole("Administrator"));
});
}
[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:
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.
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.
services.AddAuthorization(options =>
{
options.AddPolicy("Founders", policy =>
policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
});
}
[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:
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();
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;
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;
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;
return true;
}
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>();
}
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.
using Microsoft.AspNetCore.Authorization;
BadgeEntryHandler.cs
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;
using System.Security.Claims;
using System.Threading.Tasks;
TemporaryStickerHandler.cs
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;
using System.Security.Claims;
using System.Threading.Tasks;
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.
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:
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.
OBSERVAÇÃO
O código a seguir exemplos pressupõem a autenticação foi executado e o conjunto de User propriedade.
if (Document == null)
{
return new NotFoundResult();
}
if (authorizationResult.Succeeded)
{
return Page();
}
else if (User.Identity.IsAuthenticated)
{
return new ForbidResult();
}
else
{
return new ChallengeResult();
}
}
return Task.CompletedTask;
}
}
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:
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.
if (Document == null)
{
return new NotFoundResult();
}
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
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
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:
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 .
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.
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
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
/*
* 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
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.
/*
* SAMPLE OUTPUT
*
* Enter input: Hello world!
* Protect returned: CfDJ8ICcgQwZZhlAlTZT...OdfH66i1PnGmpCR5e441xQ
* Unprotect returned: Hello world!
*/
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.
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
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
/*
* 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;
// 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.
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;
// 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.");
/*
* 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
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:
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:
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:
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:
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.
services.AddDataProtection()
.UseCryptographicAlgorithms(
new AuthenticatedEncryptorConfiguration()
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
});
serviceCollection.AddDataProtection()
.UseCustomCryptographicAlgorithms(
new ManagedAuthenticatedEncryptorConfiguration()
{
// A type that subclasses SymmetricAlgorithm
EncryptionAlgorithmType = typeof(Aes),
// Specified in bits
EncryptionAlgorithmKeySize = 256,
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.
// 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.
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.
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
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.
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
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.
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.
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.
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.
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
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;
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
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.
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.
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
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
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
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;
// 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;
/*
* 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
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
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.)
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.
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:
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.
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.)
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
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
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.
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.
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;
// 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.");
/*
* 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.
services.AddMvc();
}
Consulte também o código de teste do Azure.
Para configurar o Redis, chame um do PersistKeysToRedis sobrecargas conforme mostrado abaixo.
services.AddMvc();
}
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.
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).
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.
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.
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.
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
/*
* 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
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.
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.
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
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 .
[!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.
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.
<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.
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();
}
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();
}
Linux: ~/.microsoft/usersecrets/<userSecretsId>/secrets.json
macOS: ~/.microsoft/usersecrets/<userSecretsId>/secrets.json
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
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.
config.AddAzureKeyVault(
$"https://{builtConfig["Vault"]}.vault.azure.net/",
builtConfig["ClientId"],
builtConfig["ClientSecret"]);
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:
config.AddAzureKeyVault(
$"https://{builtConfig["Vault"]}.vault.azure.net/",
builtConfig["ClientId"],
builtConfig["ClientSecret"],
new PrefixKeyVaultSecretManager(versionPrefix));
public class PrefixKeyVaultSecretManager : IKeyVaultSecretManager
{
private readonly string _prefix;
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:
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();
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
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 .
<form method="post">
...
</form>
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>
<!form method="post">
...
</!form>
@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:
Adicionar explicitamente um token antiforgery para um <form> elemento sem o uso de auxiliares
de marcação com o auxiliar HTML @Html.AntiForgeryToken :
Em cada um dos casos anteriores, o ASP.NET Core adiciona um campo de formulário oculto
semelhante à seguinte:
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
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);
});
}
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;
}
}
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()));
[Authorize]
[AutoValidateAntiforgeryToken]
public class ManageController : Controller
{
[HttpPost]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> DoSomethingSafe(SomeViewModel model)
{
// no antiforgery token required
}
}
<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:
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 "";
}
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 .
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).
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.
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
@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:
<"123">
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.
@{
var untrustedInput = "<\"123\">";
}
<div
id="injectedData"
data-untrustedinput="@untrustedInput" />
<script>
var injectedData = document.getElementById("injectedData");
// All clients
var clientSideUntrustedInputOldStyle =
injectedData.getAttribute("data-untrustedinput");
document.write(clientSideUntrustedInputOldStyle);
document.write("<br />")
document.write(clientSideUntrustedInputHtml5);
</script>
<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>
<"123">
<"123">
@using System.Text.Encodings.Web;
@inject JavaScriptEncoder encoder;
@{
var untrustedInput = "<\"123\">";
}
<script>
document.write("@encoder.Encode(untrustedInput)");
</script>
<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.
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;
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
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.
http://example.com/bar.html
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:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
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"));
});
}
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
Este exemplo adiciona uma política CORS denominada "AllowSpecificOrigin". Para selecionar a política, passe o
nome para UseCors .
[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";
}
options.AddPolicy("AllowSpecificOrigins",
builder =>
{
builder.WithOrigins("http://example.com", "http://www.contoso.com");
});
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();
});
options.AddPolicy("AllowAllHeaders",
builder =>
{
builder.WithOrigins("http://example.com")
.AllowAnyHeader();
});
options.AddPolicy("ExposeResponseHeaders",
builder =>
{
builder.WithOrigins("http://example.com")
.WithExposedHeaders("x-custom-header");
});
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.
options.AddPolicy("SetPreflightExpiration",
builder =>
{
builder.WithOrigins("http://example.com")
.SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
});
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
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).
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.
Models/IdentityModels.cs:
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
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;
<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>
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;
});
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);
return RedirectToAction("GetCallbackEntry");
}
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);
_cache.Set(CacheKeys.Child,
DateTime.Now,
new CancellationChangeToken(cts.Token));
}
return RedirectToAction("GetDependentEntries");
}
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
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.
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;
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.
/// <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.
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.Extensions.Caching.SqlConfig.Tools" Version="1.0.0-msbuild3-
final" />
</ItemGroup>
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":
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 ).
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
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.
DIRETIVA AÇÃO
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)
Outros cabeçalhos de cache que desempenham uma função no cache são mostrados na tabela a seguir.
CABEÇALHO FUNÇÃO
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.
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
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:
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 :
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();
}
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();
}
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
});
});
}
[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
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.
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();
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.
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 :
Usando um único valor igual a * na VaryByQueryKeys varia o cache por todos os parâmetros de consulta de
solicitação.
CABEÇALHO DETALHES
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
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.
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
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();
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.
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;
});
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.
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
OBSERVAÇÃO
Os números de versão nos exemplos podem não ser atuais. Talvez seja necessário atualizar seus projetos adequadamente.
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?}");
});
}
}
}
<h1>Hello world!</h1>
Execute o aplicativo.
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.
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<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.
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
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:
// 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
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();
}
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.
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>
}
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
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);
}
}
}
namespace ProductsApp
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
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 }
};
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.
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;
}
// 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.
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
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.
using System;
using System.Web;
namespace MyApp.Modules
{
public class MyModule : IHttpModule
{
public void Dispose()
{
}
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;
await _next.Invoke(context);
// Clean up.
}
}
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
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.
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();
app.UseMyTerminatingMiddleware();
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.
// ASP.NET 4 handler
using System.Web;
namespace MyApp.HttpHandlers
{
public class MyHandler : IHttpHandler
{
public bool IsReusable { get { return true; } }
context.Response.ContentType = GetContentType();
context.Response.Output.Write(response);
}
// ...
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
}
context.Response.ContentType = GetContentType();
await context.Response.WriteAsync(response);
}
// ...
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.
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();
app.UseMyTerminatingMiddleware();
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?}");
});
}
{
"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:
await _next.Invoke(context);
{
"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();
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>();
}
Observe como isso encapsula o objeto de opções em um OptionsWrapper objeto. Isso implementa
IOptions , conforme o esperado pelo construtor de middleware.
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:
HttpContext.Request
HttpContext.Request.HttpMethod se traduz em:
// using Microsoft.AspNetCore.Http.Extensions;
var url = httpContext.Request.GetDisplayUrl();
// using Microsoft.AspNetCore.Http.Headers;
// using Microsoft.Net.Http.Headers;
// 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();
// using Microsoft.Net.Http.Headers;
if (httpContext.Request.HasFormContentType)
{
IFormCollection form;
AVISO
Valores de formulário de leitura somente se o tipo de conteúdo sub é x-www-form-urlencoded ou dados do formulário.
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;
// 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 = "text/html";
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 :
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:
responseCookies.Append("cookie1name", "cookie1value");
responseCookies.Append("cookie2name", "cookie2value",
new CookieOptions { Expires = System.DateTime.Now.AddDays(5), HttpOnly = true });
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
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.
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();
}
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
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>
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:
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.
Observação: para uma referência mais detalhada sobre configuração do ASP.NET Core, consulte Configuração no
ASP.NET Core.
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);
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:
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
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
Pré-requisitos
Consulte a Introdução ao ASP.NET Core.
<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á.
{
"sdk": {
"version": "2.0.0"
}
}
<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>
<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>
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
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();
}
}
}
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
namespace AspNetCoreDotNetCore2App
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
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:
if (env.IsDevelopment())
{
builder.AddUserSecrets<Startup>();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
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:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
SeedData.Initialize(app.ApplicationServices);
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.
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:
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.
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
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:
app.UseAuthentication();
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
app.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.
app.UseAuthentication();
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();
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();
services.AddAuthentication()
.AddGoogle(options =>
{
options.ClientId = Configuration["auth:google:clientid"];
options.ClientSecret = Configuration["auth:google:clientsecret"];
});
app.UseAuthentication();
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();
services.AddAuthentication()
.AddTwitter(options =>
{
options.ConsumerKey = Configuration["auth:twitter:consumerkey"];
options.ConsumerSecret = Configuration["auth:twitter:consumersecret"];
});
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 .
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>();
}
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>();
}
/// <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 ):
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:
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; }
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; }
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
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:
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ê.
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.
Para obter informações sobre o status dos recursos do C# 7.1, consulte o repositório GitHub do Roslyn.
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
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.